原文地址:https://medium.com/@kidinamoto/how-to-send-ether-correctly-a60208ad76d9
Compare send, call & transfer
Let’s see the following code
pragma solidity ^0.4.13;
contract someContract {
mapping(address => uint) balances;
function deposit() payable {
balances[msg.sender] += msg.value;
}
//VERY very bad below
function withdrawVeryBad1(uint amount) {
if(balances[msg.sender] >= amount){
msg.sender.send(amount); balances[msg.sender] -= amount;
}
}
function withdrawVeryVeryBad2(uint amount){
if(balances[msg.sender] >= amount) {
msg.sender.call.gas(2500000).value(amount)();
balances[msg.sender] -= amount;
}
}
function withdrawVeryVeryBad3(uint amount) {
if(balances[msg.sender] >= amount) { if(msg.sender.call.gas(2500000).value(amount)()) { balances[msg.sender] -= amount;
}
}
}
function withdrawBad1(uint amount) {
if(balances[msg.sender] >= amount) {
if(msg.sender.send(amount)) {
balances[msg.sender] -= amount;
}
}
}
function withdrawOkayish(uint amount) {
if(balances[msg.sender] >= amount) {
balances[msg.sender] -= amount; if(!msg.sender.send(amount)) { throw; }
}
}
function withdrawBad2(uint amount) {
if(balances[msg.sender] >= amount) {
balances[msg.sender] -= amount; if(!msg.sender.call.gas(2500000).value(amount)()) { throw; }
}
}
//OKAY FUNCTIONS
function withdrawOK(uint amount) {
if(balances[msg.sender] >= amount) {
balances[msg.sender] -= amount;
msg.sender.transfer(amount);
}
}
//New Exception handling
function withdrawGood(uint amount) {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
msg.sender.transfer(amount);
}
}
In the first
function withdrawVeryBad1(uint amount) {
if(balances[msg.sender] >= amount){
msg.sender.send(amount);
balances[msg.sender] -= amount;
}
}
The problem is that we do not check if the return value of send is false or not. If the msg.sender is a contract, it will runs out of gas(> 2300), then it will return False. But we will still reduce the balance because there is no exception.
function withdrawVeryVeryBad2(uint amount){
if(balances[msg.sender] >= amount) {
msg.sender.call.gas(2500000).value(amount)();
balances[msg.sender] -= amount;
}
}
In this example, we can run call again and again until the contract has no ether left. the stack size is 1024 so we can do it 1024 times. it’s very bad to reduce the balance after send .
function withdrawVeryVeryBad3(uint amount) {
if(balances[msg.sender] >= amount) { if(msg.sender.call.gas(2500000).value(amount)()) { balances[msg.sender] -= amount;
}
}
}
veryverybad3 is also not good because it only use if statement.
function withdrawBad1(uint amount) {
if(balances[msg.sender] >= amount) {
if(msg.sender.send(amount)) {
balances[msg.sender] -= amount;
}
}
}
if msg.sender is a contract, send will run out of gas. So the function can only work at some rare cases.again, we shouldn’t reduce the balance after.
function withdrawOkayish(uint amount) {
if(balances[msg.sender] >= amount) {
balances[msg.sender] -= amount; if(!msg.sender.send(amount)) { throw; }
}
}
withdrawOkayish is much better, but still not working with re-entrence. the problem is still we are sending 2300 gas.
msg.sender.call.gas(2500000).value(amount)()
does the same as
msg.sender.transfer(amount);
but transfer propagate exceptions.
we can have a require statement instead of if.
require(balances[msg.sender] >= amount);
One clap, two clap, three clap, forty?
By clapping more or less, you can signal to us which stories really stand out.