基于Zeppelin-Solidity的众筹合约调用代币函数触发Revert错误求助
看起来你在基于Zeppelin Solidity开发众筹合约时遇到了代币操作触发revert的问题,结合Solidity 0.4.15版本和Zeppelin早期合约的特性,我整理了几个最可能的排查方向,以及对应的验证步骤:
1. 代币合约的权限控制问题
这是最常见的触发revert的原因。如果你的SSSToken继承了Zeppelin的MintableToken,那么mint函数默认只有拥有minter权限的地址才能调用。如果你的众筹合约(SSSTokenSale)没有被设置为合法的minter,调用token.mint就会直接触发revert。
- 排查步骤:
- 打开你的
SSSToken.sol代码,确认mint函数是否带有onlyMinter或onlyOwner修饰符; - 检查众筹合约的部署流程:是否在部署
SSSToken后,调用了addMinter(sssTokenSaleAddress)或者transferOwnership(sssTokenSaleAddress)(如果用的是owner权限控制mint); - 如果你是在众筹合约的构造函数里初始化代币,确保构造函数中已经完成了权限授权(比如调用
token.addMinter(address(this)))。
- 打开你的
2. Crowdsale构造函数参数初始化错误
Zeppelin的Crowdsale基类构造函数需要三个核心参数:rate(ETH兑换代币的比率)、wallet(众筹资金接收地址)、token(关联的代币合约地址)。如果其中任何一个参数错误,都会导致后续代币操作失败:
- 排查步骤:
- 确认你部署
SSSTokenSale时,传入的token地址是已经正确部署的SSSToken实例地址,而不是空地址或错误地址; - 检查
rate是否设置为大于0的数值(如果rate为0,计算出的代币数量为0,可能触发后续逻辑的revert); - 验证
wallet地址是否为合法的外部账户地址(合约地址也可接收ETH,但需确保其有处理ETH的逻辑,不过此处重点在代币参数的正确性)。
- 确认你部署
3. SafeERC20与代币合约的兼容性问题
你引入了SafeERC20,它的safeTransfer、safeMint等函数会严格检查代币合约的返回值。但在Solidity 0.4.15时期,部分ERC20合约的transfer或mint函数并没有返回bool类型的结果,这会导致SafeERC20在检查返回值时触发revert。
- 排查步骤:
- 查看
SSSToken的transfer和mint函数定义,确认是否有returns (bool)的声明; - 如果代币合约没有返回bool,你可以暂时替换为普通的
token.transfer()调用(跳过SafeERC20的检查),或者修改代币合约,让函数返回bool值以符合标准; - 注意:Zeppelin早期版本的
Crowdsale基类可能已经默认使用了SafeERC20,所以需要确保你的代币合约兼容。
- 查看
4. 众筹时间窗口的限制
Zeppelin的Crowdsale基类默认包含了openingTime和closingTime的时间控制,如果当前区块时间不在众筹的有效时间段内,调用buyTokens(以及后续的代币铸造/转账)都会触发revert。
- 排查步骤:
- 检查你的众筹合约是否继承了
TimedCrowdsale,或者手动实现了时间控制逻辑; - 验证当前区块时间是否在
openingTime之后、closingTime之前; - 如果是测试环境(比如Remix、Ganache),可以手动调整区块时间来测试。
- 检查你的众筹合约是否继承了
5. 调试工具定位具体错误
如果以上排查都没找到问题,建议用调试工具定位具体的revert原因:
- 在Remix中部署合约时,开启“Debug”模式,执行触发revert的函数,查看调用栈中哪一行代码触发了revert;
- 检查是否有
require或assert条件不满足,比如require(tokens > 0)、require(msg.value > 0)等; - 确认代币合约的总供应量是否有上限,如果mint后的总供应量超过上限,也会触发revert。
示例代码参考(权限授权部分)
如果你的SSSToken是MintableToken,在众筹合约的构造函数中需要添加权限授权:
pragma solidity ^0.4.15; import './SSSToken.sol'; import 'zeppelin-solidity/contracts/crowdsale/Crowdsale.sol'; contract SSSTokenSale is Crowdsale { function SSSTokenSale(uint256 _rate, address _wallet, SSSToken _token) Crowdsale(_rate, _wallet, _token) public { // 授权众筹合约为代币的minter _token.addMinter(address(this)); // 如果是用owner权限控制,转移所有权到众筹合约 // _token.transferOwnership(address(this)); } }
内容的提问来源于stack exchange,提问作者Kwang

