20

In other words, is using transfer() safe?

function transfer(address contractB) public
{
    contractB.transfer(1000);
    //balances[msg.sender] -= 1000;
}

How about using call.gas?

function transfer2(address receiver) public
{
    receiver.call.gas(20000).value(1000)();
}
eth
  • 85,679
  • 53
  • 285
  • 406
Robert Ggg
  • 469
  • 5
  • 13

2 Answers2

23

transfer() and send() should be avoided (because they take a hard dependency on gas costs by forwarding a fixed amount of gas: 2300).

Gas specific code (call{gas: ..., value: ...}("")) should also be avoided.

call{value: ...}("") should be used, for example: contractB.call{value: 1000}("")

It is also critical that you make sure to guard against reentrancy by making all state changes, before call{value: ...}("").

https://diligence.consensys.net/blog/2019/09/stop-using-soliditys-transfer-now explains more about the best practice change.

Any gas specific code should be avoided because gas costs can and will change.

For example, the gas cost for SLOAD was raised from 50 to 200 in 2016, and again in EIP 1884, with some impacts described in: https://chainsecurity.com/istanbul-hardfork-eips-increasing-gas-costs-and-more/


UNGAS is an idea to remove the GAS opcode and smart contracts would not have any notion of gas. (Gas would still be in the protocol, but not in the EVM.)

eth
  • 85,679
  • 53
  • 285
  • 406
  • 1
    Rather than "prevent", it's better to say "guard against". You can't prevent reentrancy by making state changes before ether transfer. In your example, for example, you can re-enter multiple times, draining the balance of msg.sender. The example from the consensus blog sends the entire balance and has a line that sets the balance to 0 before the send, which guards appropriately in that context against reentrancy. – Chan-Ho Suh Jan 15 '20 at 19:36
  • 1
    @Chan-HoSuh Thanks for your comments and edits have been made. (The example in this answer was basically copy-pasted from the question without much thought, so it has now been removed because it was a little nonsensical.) – eth Jan 18 '20 at 07:56
  • Care to explain why transfer() and send() should be avoided? – axwell Sep 07 '21 at 16:30
  • 1
    @axwell I added a bit more explanation to answer but the linked blog post has more space for it. – eth Sep 12 '21 at 05:27
  • 1
    Why is empty string ("") passed in call{value: ...}("")? – Toni Sučić Nov 28 '21 at 16:01
  • @ToniSučić .call() needs 1 string argument, the function being called. The empty string is the way to call the fallback function. I found these examples that might help https://solidity-by-example.org/call/ and https://solidity-by-example.org/fallback/ – eth Nov 29 '21 at 10:31
  • 1
    @ToniSučić The parameter of call has type bytes memory, and should contain an ABI-encoded call to a function, complete with ABI-encoded concrete parameters. Passing a string into a bytes parameter treats the whole string as a byte array. Both bytes and string start with a uint256 length field, and are then followed by the bytes or characters. In this case "" is being used as an empty byte array (equivalent to a zero-length string). In ABI encoding, an empty array is interpreted to mean "call the fallback function" since it has no function name and no parameters. – Luke Hutchison Jun 01 '22 at 08:47
7

the above answer is perfect, just notice the change in syntax in the latest version of Solidity.

someAddress.call{value: 1 ether}('');

Bitnician
  • 71
  • 1
  • 3