In Part 1, we explored different address
members and their use cases. While the previous article explored scenarios involving sending Ether, it is helpful to know that the functionalities extend beyond that.
For instance, the call
function is a low-level function which has the capability to invoke any function on any target contract by specifying the function signature and arguments in the "data" parameter. (Security Note: call never reverts. It is our responsibility to handle unsuccessful transaction errors.)
Now, let's delve into the final two members of the address object: staticcall
and delegatecall
. These members are also interchangeably referred as functions - particularly when describing their role in code execution through invocation.
The underlying concept of staticcall
and delegatecall
is relatively straightforward, as they are essentially specialized variants of the call
function.
1. delegatecall
This function allows state modifying instructions while preserving the original contract's context (storage, sender address, and value).
At this point, you might be wondering about the terminology “delegatecall”. How exactly are the function calls “delegated”? Or rather, what is the mode of “delegation” here?
Let's utilise the code below to answer these questions!
pragma solidity ^0.8.24;
contract Caller {
uint256 public value;
function setVars(uint256 _value) public payable {
value = _value;
}
}
contract DelegateCaller {
uint256 public value;
function setVars(address _callerContract, uint256 _value) public payable {
// Caller delegated its setVars() function to DelegateCaller Contract
(bool success, ) = _callerContract.delegatecall(
abi.encodeWithSignature("setVars(uint256)", _value)
);
if (!success) revert("Failed");
}
}
When setVars()
is called in DelegateCaller contract, the delegatecall
invokes the setVars()
function in the Caller contract but in the DelegateCaller execution environment.
Thus, Caller contract has effectively delegated its function to DelegateCaller contract.
Let's explore further using the video below! (Watch what happens when setVars()
was called in DelegateCaller contract. Observe how the value
variables change in the context of Caller and DelegateCaller contract.)
Were you able to identify the contract that had its value
variable updated when delegatecall
was invoked?
This behavior might seem redundant at first when we can simply use call
function. However, delegatecall
enables use cases such as for proxy contracts - where only the implementation contract (i.e. Uniswap V2, V3) is upgraded to preserve the context of the proxy contract.
Having explored the complexities of delegatecall
, let’s proceed to explore staticcall
which is relatively simpler!
2. staticcall
In general, staticcall
does not allow any state modifying instructions or capability to send ether. It will revert if there are any state changes during the function invocation.
At a low-level, it disallows opcodes such as CREATE, SSTORE, SELFDESTRUCT, and few others. Personally, I view staticcall
as a safer variant of the call function to read state.
Figure 1: staticcall
example
In the example above, notice how the value
variable for StaticCaller contract did not change. It remains as “0” even when Caller contract has been updated to store “100” in its value
variable. Unlike delegatecall
which allows state modifying instruction, staticcall
only reads state of the target contract (Caller contract in this example).
As a treat, I have also included a function setVars()
which purposefully calls a state modifying function from the target contract to simulate transaction failure. Watch the video towards the end to learn what happens when success
returns false.
Explore this code further here: staticcall.sol
In ETH CC Brussels, Scroll introduced the experimental L1SLOAD precompile which uses staticcall
under the hood.
Hackers were able to explore novel methods of resolving ENS address, extending NFT ownership - all on L2. Despite preliminary stage of experimentation, this highlights that the potential opportunities for this function are virtually limitless.
Now that you have equipped yourself with an arsenal of knowledge on the address
object, you are now capable of address-ing (Hehe! Pun intended) more intermediate to advanced level builds. Can't wait to hear all about how you will be using this knowledge to level up your projects.
More Content
A guide on how to use Vyper, Ape & Web3.py to write, deploy & interact with smart contracts
Learn how to build chance-based contracts using verifiable randomness provided by Anyrand (powered by drand)
Get started with developing privacy applications by combining Circuits and Smart Contracts.
Keep users' data safe by generating proofs in the browser, on Javascript.
Dive into the world of Solidity in pursuit of leveling up! Starting with Address object.