Send ETH (transfer, send, call)
How to send Ether
You can send Ether to other contracts by
transfer
(2300 gas, throws error)send
(2300 gas, returns bool)call
(forward all gas or set gas, returns bool)
How to receive Ether
A contract receiving Ether must have at least one of the functions below
receive()
external payablefallback()
external payablereceive()
is called ifmsg.data
is empty, otherwisefallback()
is called.
Which method should you use?
call
in combination with re-entrancy guard is the recommended method to use after December 2019.
Guard against re-entrancy
Making all state changes before calling other contracts.
Using re-entrancy guard modifier.
Example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract ReceiveEther {
/*
Which function is called, fallback() or receive()?
send Ether
|
msg.data is empty?
/ \
yes no
/ \
receive() exists? fallback()
/ \
yes no
/ \
receive() fallback()
*/
// Function to receive Ether. msg.data must be empty
receive() external payable {}
// Fallback function is called when msg.data is not empty
fallback() external payable {}
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
contract SendEther {
function sendViaTransfer(address payable _to) public payable {
// This function is no longer recommended for sending Ether.
_to.transfer(msg.value);
}
function sendViaSend(address payable _to) public payable {
// Send returns a boolean value indicating success or failure.
// This function is not recommended for sending Ether.
bool sent = _to.send(msg.value);
require(sent, "Failed to send Ether");
}
function sendViaCall(address payable _to) public payable {
// Call returns a boolean value indicating success or failure.
// This is the current recommended method to use.
(bool sent, bytes memory data) = _to.call{value: msg.value}("");
require(sent, "Failed to send Ether");
}
}
Example - Call
call
is a low level function to interact with other contractsThis is the recommended method to use when you're just sending Ether via calling the
fallback
functionHowever it is not the recommend way to call existing functions
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract Receiver {
event Received(address caller, uint amount, string message);
fallback() external payable {
emit Received(msg.sender, msg.value, "Fallback was called");
}
function foo(string memory _message, uint _x) public payable returns (uint) {
emit Received(msg.sender, msg.value, _message);
return _x + 1;
}
}
contract Caller {
event Response(bool success, bytes data);
// Let's imagine that contract B does not have the source code for
// contract A, but we do know the address of A and the function to call.
function testCallFoo(address payable _addr) public payable {
// You can send ether and specify a custom gas amount
(bool success, bytes memory data) = _addr.call{value: msg.value, gas: 5000}(
abi.encodeWithSignature("foo(string,uint256)", "call foo", 123)
);
emit Response(success, data);
}
// Calling a function that does not exist triggers the fallback function.
function testCallDoesNotExist(address _addr) public {
(bool success, bytes memory data) = _addr.call(
abi.encodeWithSignature("doesNotExist()")
);
emit Response(success, data);
}
}
Call - Specify Function
function myFunction(uint _x, address _addr) public returns(uint, uint) {
// do something
return (a, b);
}
// function signature string should not have any spaces
// 10 is the first parameter _x
// msg.sender is the second parameter _addr
(bool success, bytes memory result) = addr.call(abi.encodeWithSignature("myFunction(uint,address)", 10, msg.sender));
Delegatecall
delegatecall
is a low level function similar tocall
delegatecall
syntax is exactly the same ascall
syntax except it cannot accept thevalue
option but onlygas
When contract A executes
delegatecall
to contract B, B's code is executedwith contract A's storage,
msg.sender
andmsg.value
A popular and very useful use case for
delegatecall
is upgradable contractsUpgradable contracts use a proxy contract which forwards all the function calls to the implementation contract using
delegatecall
The address of the proxy contract remains constant while new implementations can be deployed multiple times
The address of the new implementation gets updated in the proxy contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
// NOTE: Deploy this contract first
contract B {
// NOTE: storage layout must be the same as contract A
uint public num;
address public sender;
uint public value;
function setVars(uint _num) public payable {
num = _num;
sender = msg.sender;
value = msg.value;
}
}
contract A {
uint public num;
address public sender;
uint public value;
function setVars(address _contract, uint _num) public payable {
// A's storage is set, B is not modified.
(bool success, bytes memory data) = _contract.delegatecall(
abi.encodeWithSignature("setVars(uint256)", _num)
);
}
}
Last updated