Ganache环境中调用Solidity合约payable buy()函数时发送方ETH余额未减少的问题排查与修复
问题分析与修复:调用buy()获取代币但ETH余额未减少
我来帮你拆解这个问题的核心原因,然后给出具体的修复步骤:
核心原因
1. 调用时的金额单位错误
你在Truffle控制台执行的MyCoinDEX.buy({value: 1})里,value:1的单位是wei(ETH的最小单位),1wei约等于0.000000000000000001ETH——这个金额太小了,Ganache的余额显示通常只保留到小数点后4-6位,所以视觉上看起来账户ETH余额没有变化,但实际上你的账户已经扣除了1wei的ETH。
2. 合约兑换逻辑的单位不匹配
你的合约注释写着“1 token == 1 ether”,但代码里直接把msg.value(ETH的wei数)作为代币数量来转账。如果你的MyCoinSupply代币小数位数是18(和ETH一致),这意味着你发送1wei ETH只能拿到1wei的代币(也就是0.000000000000000001个完整代币),这和你预期的“1ETH换1代币”的兑换比例完全不匹配。
修复方案
第一步:正确调用合约方法
在Truffle控制台里,必须使用web3.utils.toWei()把你要发送的ETH金额转换成wei单位,同时指定调用的测试账户。比如要发送1ETH兑换代币,执行以下命令:
// 先获取部署好的DEX合约实例 const dexInstance = await MyCoinDEX.deployed(); // 调用buy方法,发送1ETH,从第一个测试账户发起交易 await dexInstance.buy({ value: web3.utils.toWei("1", "ether"), from: accounts[0] });
执行完后再去Ganache查看账户余额,你会发现ETH余额减少了1ETH加上对应的Gas费用,同时代币余额会增加1个完整代币(根据你的代币小数位调整)。
第二步:修复合约的兑换逻辑
修改MyCoinDEX合约,确保兑换比例和你注释里的“1 token == 1 ether”一致,同时增加必要的安全检查:
pragma solidity ^0.8.0; import "./IERC20.sol"; import "./MyCoinSupply.sol"; contract MyCoinDEX { IERC20 public token; uint8 public tokenDecimals; // 存储代币的小数位数 event Bought(uint256 amount); event Sold(uint256 amount); constructor() { token = new MyCoinSupply(); tokenDecimals = token.decimals(); // 初始化代币小数位数 } // 保留你的调试辅助函数 function getSenderAddress() public view returns (address) { return msg.sender; } function getAddress() public view returns (address) { return address(this); } function getTokenAddress() public view returns (address) { return address(token); } function buy() payable public { uint256 ethWei = msg.value; require(ethWei > 0, "You need to send some ether"); // 计算兑换的代币数量:1 ETH = 1 Token,适配代币小数位 uint256 tokenAmount = (ethWei * 10 ** tokenDecimals) / 1 ether; uint256 dexTokenBalance = token.balanceOf(address(this)); require(tokenAmount <= dexTokenBalance, "Not enough tokens in the reserve"); token.transfer(msg.sender, tokenAmount); emit Bought(tokenAmount); } function sell(uint256 tokenAmount) public { require(tokenAmount > 0, "You need to sell at least some tokens"); uint256 allowance = token.allowance(msg.sender, address(this)); require(allowance >= tokenAmount, "Check the token allowance"); // 计算返还的ETH数量:1 Token = 1 ETH uint256 ethWei = (tokenAmount * 1 ether) / 10 ** tokenDecimals; require(address(this).balance >= ethWei, "Not enough ETH in the reserve"); token.transferFrom(msg.sender, address(this), tokenAmount); payable(msg.sender).transfer(ethWei); emit Sold(tokenAmount); } }
这里做了几个关键优化:
- 自动获取代币的小数位数,保证兑换逻辑适配不同小数位的代币
- 使用Solidity内置的
1 ether常量(等于1e18wei)简化计算,避免手动写死数值出错 - 在
sell函数里增加了合约ETH余额的检查,防止合约ETH不足时转账失败
内容的提问来源于stack exchange,提问作者Join_Where
相关产品推荐
相关产品推荐

