Solidity智能合约withDraw函数无法向msg.sender转账ETH求助
问题背景
这是一个简易银行智能合约,支持用户在合约内开立账户,合约本身作为账户载体:设置了1ETH的最低开户余额门槛,用户开户时必须转入不低于1ETH的资金,同时设置后续取款校验用的密钥。编写withDraw取款函数时,完成用户余额校验、密钥匹配校验后,标注的代码行无法将对应金额的ETH转回给存款用户,原合约代码如下:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; contract ActualBank{ uint minAccountBalance=1 ether; address payable owner; mapping(address => uint256) balance ; mapping(address => uint256) secretKey; constructor(){ owner=payable(msg.sender); } function openAccount(uint256 _secretKey) payable public returns(uint256) { require(msg.value >= minAccountBalance,"There must a minimum balance of 1 ether"); balance[msg.sender]+=msg.value; secretKey[msg.sender]=_secretKey; return balance[msg.sender]; } function withDraw(uint256 _secretKey) payable public returns(uint256) { require(msg.value <= balance[msg.sender],"With drawal value not correct"); require(secretKey[msg.sender] == _secretKey, "Secret key didn't matched"); balance[msg.sender]-=msg.value; address payable receiver= payable(msg.sender); receiver.transfer(msg.value); // issue seems to be on this line return balance[msg.sender]; } function getAccountBalance() public view returns(uint256){ return balance[msg.sender]; } }
问题根因
转账失败不是receiver.transfer()这行本身的语法问题,是函数前置逻辑有3个核心错误:
- 取款函数错误添加了
payable修饰,且错把调用函数时用户转入的msg.value当成取款金额。取款是用户从合约提走自己的存款,不需要调用时往合约转钱,正常调用场景下用户不会附带转ETH,msg.value默认值为0,最终执行的转账是transfer(0),自然不会有ETH到账。 - 函数缺失取款金额入参:用户调用取款函数时没有办法指定自己要取多少钱,逻辑上完全不成立。
- 转账方法兼容性差:
transfer()方法固定只给接收地址转发2300gas,如果取款用户是合约地址,且地址的receive/fallback函数逻辑复杂超过gas限制,会直接导致转账失败回滚交易。
修复方案
- 移除
withDraw函数的payable修饰,新增uint256 _amount入参,用来接收用户指定的取款金额 - 把余额校验、余额扣减、转账逻辑里的
msg.value全部替换为用户传入的_amount - 替换
transfer()为call()方法做ETH转账,做好转账结果校验,兼容所有接收地址类型
修复后的完整合约代码:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; contract ActualBank{ uint minAccountBalance=1 ether; address payable owner; mapping(address => uint256) balance ; mapping(address => uint256) secretKey; constructor(){ owner=payable(msg.sender); } function openAccount(uint256 _secretKey) payable public returns(uint256) { require(msg.value >= minAccountBalance,"There must a minimum balance of 1 ether"); balance[msg.sender]+=msg.value; secretKey[msg.sender]=_secretKey; return balance[msg.sender]; } function withDraw(uint256 _secretKey, uint256 _amount) public returns(uint256) { require(_amount <= balance[msg.sender],"Withdrawal amount exceeds account balance"); require(secretKey[msg.sender] == _secretKey, "Secret key didn't match"); // 先扣减余额,防止重入攻击 balance[msg.sender]-=_amount; address payable receiver= payable(msg.sender); // 用call替代transfer做转账 (bool success, ) = receiver.call{value: _amount}(""); require(success, "Transfer failed"); return balance[msg.sender]; } function getAccountBalance() public view returns(uint256){ return balance[msg.sender]; } }
补充说明:修复时把余额扣减逻辑放在转账操作前,是为了规避重入攻击风险,属于转账类合约的标准安全实践。
内容的提问来源于stack exchange,提问作者Shamoon97
相关产品推荐
相关产品推荐

