Overview
Both delegatecall
and call
are Solidity functions used to execute code from one contract within another contract. However, there are important differences between the two that are worth understanding.
call Function
The call
function is used to invoke a function of another contract and return a boolean value indicating whether or not the call was successful. It has the following syntax:
(bool success, bytes memory returnData) = addressOfContract.call(functionSignature);
Here, addressOfContract
is the address of the contract you want to call and functionSignature
is the function you want to execute, encoded as bytes. The function can have parameters and return values, but they must be encoded as well.
delegatecall Function
The delegatecall
function is similar to call
, but with some important differences. When you use delegatecall
, the code of the contract you are calling is executed in the context of the calling contract, rather than its own context. This means that any storage variables or state changes made by the called contract will be made in the calling contract.
The syntax of delegatecall
is very similar to call
:
(bool success, bytes memory returnData) = addressOfContract.delegatecall(functionSignature);
Example
Here's an example to illustrate the difference between the two functions:
pragma solidity ^0.8.0;
contract Caller {
uint public value;
function callSetValue(address target, uint _value) external returns(bool success, bytes memory returnData) {
bytes memory payload = abi.encodeWithSignature("setValue(uint256)", _value);
(success, returnData) = target.call(payload);
}
function delegateSetValue(address target, uint _value) external returns(bool success, bytes memory returnData) {
bytes memory payload = abi.encodeWithSignature("setValue(uint256)", _value);
(success, returnData) = target.delegatecall(payload);
}
}
contract Callee {
uint public value;
function setValue(uint _value) external {
value = _value;
}
}
In this example, we have two contracts: Caller
and Callee
. The Caller
contract has two functions: callSetValue
and delegateSetValue
. Both functions take an address and a uint value as arguments. The callSetValue
function uses call
to invoke the setValue
function of the Callee
contract. The delegateSetValue
function uses delegatecall
to invoke the same function.
Now let's see what happens when we call these functions:
Caller caller = new Caller();
Callee callee = new Callee();
// Using call
(bool success1, bytes memory returnData1) = caller.callSetValue(address(callee), 42);
assert(success1 == true); // The call is successful
assert(callee.value == 42); // The value in the callee contract is set to 42
assert(caller.value == 0); // The value in the caller contract is still 0
// Using delegatecall
(bool success2, bytes memory returnData2) = caller.delegateSetValue(address(callee), 42);
assert(success2 == true); // The delegatecall is successful
assert(callee.value == 42); // The value in the callee contract is set to 42
assert(caller.value == 42); // The value in the caller contract is also set to 42
As you can see, when we use call
, the **setValue
**function is executed in the context of the Callee
contract, and the changes made to the value
variable are only visible within that contract. The value
variable in the Caller
contract remains unchanged.
On the other hand, when we use delegatecall
, the setValue
function is executed in the context of the Caller
contract, and the changes made to the value
variable are visible in both contracts.