Gnosis Safe创建时启用自定义模块失败求助(DelegateCall方式)
问题排查方向
- 检查Delegatecall上下文权限:Gnosis Safe v1.4.1的
enableModule函数带有onlyOwner修饰器。在setup阶段执行delegatecall时,msg.sender是部署脚本的发起地址,若该地址不在Safe的owners列表中,调用enableModule会触发权限校验失败回滚。 - 验证Calldata编码正确性:确认
SafeAccountConfig中data参数的函数签名、参数类型与SafeModuleSetup合约的目标函数完全匹配(比如是否把address类型参数误编码为bytes)。可通过ethers.utils.defaultAbiCoder.decode解码data,核对参数是否正确。 - 检查SafeModuleSetup的存储兼容性:Delegatecall会复用调用者(Safe)的存储,若
SafeModuleSetup合约的存储变量布局与Safe不一致,直接操作存储会破坏Safe的状态,导致回滚。需确保SafeModuleSetup中操作的变量位置与Safe对应(比如modules映射、moduleCount变量的定义顺序)。 - 查看具体回滚原因:在部署脚本中添加错误捕获,打印revert reason(
e.reason),或使用Hardhat的--verbose参数运行部署命令,获取交易的详细执行轨迹,定位具体失败点。 - 检查Gas限制:添加模块的操作需要额外Gas,若部署时未指定足够的Gas上限,会触发OutOfGas回滚。可在部署时显式设置
gasLimit参数。
解决方案
绕过Owner权限校验(推荐)
直接在SafeModuleSetup中操作Safe的存储,而非调用Safe的enableModule函数(避免onlyOwner校验)。示例合约:pragma solidity ^0.7.6; // 与Safe v1.4.1的Solidity版本保持一致 contract SafeModuleSetup { // 严格匹配Safe的存储结构 mapping(address => bool) public modules; uint256 public moduleCount; event ModuleEnabled(address indexed module); function enableTimelockModule(address module) external { require(!modules[module], "Module already enabled"); modules[module] = true; moduleCount++; emit ModuleEnabled(module); } }部署脚本中编码
data时,调用上述enableTimelockModule函数,传入TimelockModule的地址。确保部署者在Owners列表中
将部署脚本的发起地址添加到SafeAccountConfig的owners数组中,这样在delegatecall调用enableModule时,msg.sender属于授权Owner,可通过权限校验。验证Calldata编码
使用ethers.js的Interface编码函数调用,避免手动拼接错误:const safeModuleSetup = await ethers.getContract("SafeModuleSetup"); const timelockModule = await ethers.getContract("TimelockModule"); const data = safeModuleSetup.interface.encodeFunctionData( "enableTimelockModule", [timelockModule.address] );指定足够Gas上限
在创建Safe时显式设置Gas限制:const safe = await safeFactory.createSafe(safeConfig, { gasLimit: 500000 });
内容的提问来源于stack exchange,提问作者jbrionne
相关产品推荐
相关产品推荐

