Solidity跨合约调用保持原msg.sender:能否以发起者地址调用外部合约的transfer方法?
让ERC20 Transfer以原始调用者地址执行的解决方案
嘿,这个需求完全可行,但得调整一下你的代币转账逻辑——你现在用的transfer方法是让当前合约作为发起方转出自己的ERC20代币,但这显然不是你要的效果,因为你希望是调用buyGameBundle的用户(原始调用者)直接把钱转到收费账户。
为什么当前代码不可行?
你当前的erc20.transfer(_feesAccount, currentItem.cost)调用,本质是让你的合约把自己钱包里的ERC20转到_feesAccount。但你的合约本身并没有用户的代币,除非用户先把代币转到合约里,这显然不符合“用原始调用者地址执行”的需求,而且大概率会因为合约余额不足导致调用失败。
正确的实现方式
要让用户直接转出代币,必须通过授权+转账的流程:
- 用户先给你的合约授权,允许合约从他的地址转出指定数量的ERC20代币;
- 你的合约调用
transferFrom方法,从用户地址把代币转到收费账户。
修改后的合约代码:
function buyGameBundle(string calldata id) external nonReentrant { structGameBundles memory currentItem = _gameBundles[id]; require(currentItem.exists == true, "bundle does not exists"); require(currentItem.totalSupply > 0, "there are no more bundles left"); if (currentItem.cost > 0) { // 替换transfer为transferFrom,指定从原始调用者地址转出 bool transferSuccess = erc20.transferFrom(_msgSender(), _feesAccount, currentItem.cost); require(transferSuccess, "Token transfer failed"); // 注:部分老版本ERC20不返回bool值,此时可以去掉返回值检查,或者用try/catch处理异常 } currentItem.totalSupply = currentItem.totalSupply.sub(1); _gameBundles[id] = currentItem; emit BuyGameBundle(_msgSender(), id, currentItem.cost); }
关键细节提醒
- 用户端操作:用户在调用
buyGameBundle之前,必须先调用ERC20代币的approve(你的合约地址, currentItem.cost)方法,给你的合约授权足够的额度。如果用户可能多次购买,也可以授权无限额度(但要提醒用户注意授权风险)。 - ERC20兼容性:不同ERC20的实现可能有差异,比如有些不会返回
bool表示转账结果。如果你的目标代币是这类,建议用Solidity的try/catch语法来捕获转账异常,避免合约因调用失败而回滚。 - 安全保障:你已经添加了
nonReentrant修饰符,这能有效防止重入攻击,继续保持这个安全措施就好。
内容的提问来源于stack exchange,提问作者Amit Wagner
相关产品推荐
相关产品推荐

