Skip to main content

WTF Solidity: 23. Delegatecall

Time:
Best Score:
Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies.

Twitter: @0xAA_Science | @WTFAcademy_

Community: DiscordWechatWebsite wtf.academy

Codes and tutorials are open source on GitHub: github.com/AmazingAng/WTFSolidity


delegatecall

delegatecall is similar to call, is a low level function in Solidity. delegate meas entrust/represent, so what does delegatecallentrust?

When user A call contract C via contract B, the executed functions are from contract C, the execution context (the environment including state and variable) is in contract C: msg.sender is contract B's address, and if state variables are changed due to function call, the affected state variables are in contract C.

execution context of call

And when user A delegatecall contract C via contract B, the executed functions are from contract C, the execution context is in contract B: msg.sender is user A's address, and if state variables are changed due to function call, the affected state variables are in contract B.

execution context of delegatecall

You can understand it like this: a rich businessman entrusts his asset (state variables) to a VC (functions of target contract) for management. The executed functions are from the VC, but the state variables get changed is from the businessman.

The syntax of delegatecall is simimar to call:

targetContractAddress.delegatecall(binary code);

the binary code is generated by abi.encodeWithSignature:

abi.encodeWithSignature("function signature", parameters separated by comma)

function signature is "functionName(parameters separated by comma)". For example, abi.encodeWithSignature("f(uint256,address)", _x, _addr)

Unlike call, delegatecall can specify the value of gas when calling smart contract, but the value of ETH can't be specified.

Attention: using delegatecall could incur risk, make sure the storage layout of state variables of current contract and target cotnract is same, and target contract is safe, otherwise could cause loss of funds.

delegatecall use cases?

Currently there are 2 major use cases for delegatecall:

  1. Proxy Contract: separating the storage part and logic part of smart contract: proxy contract is used to store all related variables, and also store the address of logic contract; all functions are stored in the logic contract, and called via delegatecall. When upgrading, you only need to redirect proxy contract to a new logic contract.
  2. EIP-2535 Diamonds: Diamond is a standard that supports building modular smart contract systems that can scale in production. Diamond is a proxy contract with multiple implementation contracts. For more information, check: Introduction to EIP-2535 Diamonds.

delegatecall example

Call mechanism: you (A) call contract C via contract B.

Target Contract C

First we create a target contract C with 2 public variables: num and sender which are uint256 and address respectively; and a function which sets num based on _num, and set sender as msg.sender.

// Target contract C
contract C {
uint public num;
address public sender;

function setVars(uint _num) public payable {
num = _num;
sender = msg.sender;
}
}

Call Initizalization Contract B

First, contract B must have the same state variable layout as target contract C, 2 variabels and the order is num and sender.

contract B {
uint public num;
address public sender;

Next, we use call and delegatecall respectively to call setVars from contract C, so we can understand the difference better.

Function callSetVars calls setVars via call. callSetVars has 2 parameters, _addr and _num, which correspond to contract C's address and the parameter of setVars.

    // Calling setVars() of contract C with call, the state variables of contract C will be changed
function callSetVars(address _addr, uint _num) external payable{
// call setVars()
(bool success, bytes memory data) = _addr.call(
abi.encodeWithSignature("setVars(uint256)", _num)
);
}

While function delegatecallSetVars calls setVars via delegatecall. Similar to callSetVars, delegatecallSetVars has 2 parameters, _addr and _num, which correspond to contract C's address and the parameter of setVars.

    // Calling setVars() of contract C with delegatecall, the state variables of contract B will be changed
function delegatecallSetVars(address _addr, uint _num) external payable{
// delegatecall setVars()
(bool success, bytes memory data) = _addr.delegatecall(
abi.encodeWithSignature("setVars(uint256)", _num)
);
}
}

Verify on Remix

  1. First we deploy contract B and contract C

deploy.png

  1. After deployment, check the initial value of state variables in contract C, also the initial value of state variables in contract B.

initialstate.png

  1. Next, call callSetVars in contract B with arguments of contract C's address and 10

call.png

  1. After execution, the state variables in contract C are changed: num is changed to 10, sender is changed to contract B's address

resultcall.png

  1. Next, we call delegatecallsetVars in contract B with arguments of contract C's address and 100

delegatecall.png

  1. Because of delegatecall, the execution context is contract B. Afte execution, the state variables of contract B are changed: num is changed to 100, sender is changed to your wallet's address. The state variables of contract C are unchanged.

resultdelegatecall.png

Summary

In this lecture we introduce another low level function in Solidity, delegatecall. Similar to call, delegatecall can be used to call another contract; the difference of delegatecall and call is execution context, the execution context is C if B call C; but the execution context is B if B delegatecall C. The major use cases for delegatecall is proxy contract and EIP-2535 Diamons.