ERC20代理合约中如何获取调用SuperFunction的PartnerCoin合约地址
问题根因说明
你当前的取值异常是可升级代理的delegatecall上下文特性导致的:
CoinA实现合约运行在代理的delegatecall上下文内,原生msg.sender固定返回代理自身地址0x123;而你使用的OpenZeppelin _msgSender()默认会透传整个调用链路的最外层发起者,所以拿到了交易发起人Bob的地址0x987,中间发起调用的PartnerCoin地址没有被默认透传。
可选解决方案
方案1:调整调用逻辑复用_msgSender()(最优解)
你只需要要求PartnerCoin用普通call而非delegatecall调用CoinA代理的SuperFunction即可:正常call模式下,OpenZeppelin可升级合约的_msgSender()会返回直接调用代理的地址,也就是PartnerCoin的地址0x456,直接和你预先存储的白名单地址对比即可完成校验。
代码示例:
// CoinA实现合约预先写入白名单地址 address public constant ALLOWED_PARTNER = 0x456; function SuperFunction(uint256 transferAmount, address from, address to) external returns (bool) { // 直接校验调用方身份 require(_msgSender() == ALLOWED_PARTNER, "CoinA: caller is not allowed partner"); // 原有业务逻辑 return true; }
如果需要同时拿到用户Bob的地址,让PartnerCoin调用SuperFunction时把用户地址作为入参传递即可,不要依赖_msgSender()透传用户身份。
方案2:瞬态存储存储调用方地址(兼容现有调用逻辑)
如果你使用的链支持EIP-1153瞬态存储(上海升级后以太坊主网、绝大多数L2均已支持),可以在CoinA代理的fallback函数中写入调用代理的地址到瞬态存储,再在实现合约中读取该值,即可拿到PartnerCoin地址,不需要修改PartnerCoin的调用逻辑。
代理侧代码补充:
bytes32 constant CALLER_KEY = keccak256("coina.proxy.caller"); fallback() external payable { assembly { tstore(CALLER_KEY, caller()) } // 原有代理delegatecall逻辑 }
实现合约侧读取逻辑:
function getPartnerCaller() internal view returns (address) { bytes32 key = keccak256("coina.proxy.caller"); address caller; assembly { caller := tload(key) } return caller; }
方案3:入参加参校验(改造成本最低)
如果前两种方案落地有困难,可以直接要求PartnerCoin调用SuperFunction时主动传入自身合约地址作为入参,你在CoinA中校验该地址是否在白名单内,同时补充合约身份校验防止伪造:
address public constant ALLOWED_PARTNER = 0x456; function SuperFunction(address partnerCoin, uint256 otherParams) external returns (bool) { require(partnerCoin == ALLOWED_PARTNER, "CoinA: invalid partner address"); // 额外校验传入地址为合约地址,避免EOA伪造 require(partnerCoin.code.length > 0, "CoinA: partner is not a contract"); // 原有业务逻辑 return true; }
内容的提问来源于stack exchange,提问作者RatherPeachy

