DelegateCall in Solidity — With Some Code Examples
Johnny Time
Founder @ Ginger Security | Blockchain Security Engineer and Web3 Security Educator. Learn more at: johnnytime.xyz
DelegateCall Solidity
elegateCall is a unique feature in Solidity, which allows contract A to execute code in contract B with contract A storage, and the preserving the msg (msg.sender, msg.value, etc..).
Today, we will discuss how DelegateCall works and provide some code samples.
Understanding DelegateCall
DelegateCall is a low-level Solidity opcode that allows a contract to execute code from another contract, but it using the state and the storage of the calling contract.
The syntax for DelegateCall is as follows:
(bool success, bytes memory returnData) = address.delegatecall(bytes memory data);
The address parameter is the address of the contract to execute, and the data parameter is the encoded function call to execute.
DelegateCall vs. Call
The primary distinction between call and delegateCall is how they handle the execution context of the function. When you use call, the called function executes within the context of the calling contract. This means that the called function has access to the calling contract's storage and code.
On the other hand, when you use delegateCall, the called function executes in the context of the calling contract's caller. This means that the called function has access to the caller's storage and code, not the calling contract's storage and code.
In simple terms, delegatecCall enables the called contract to access the storage and code of the calling contract's caller, whereas call only permits the called contract to use the storage and code of the calling contract.
The difference between call and delegateCall is crucial when upgrading smart contracts. By using delegateCall, you can create a separate contract that contains the upgraded logic, and then call that contract from your main contract using delegateCall. This way, your main contract retains its storage, and the upgraded logic is executed in the context of the calling contract's caller. This allows for seamless upgrades without losing any data.
DelegateCall Security
As you continue to learn about delegateCall, it's important to gain hands-on experience and training
This course offers high-quality video content and practical hands-on exercises to help you gain valuable knowledge and skills. You will learn how to identify potential security risks and vulnerabilities in smart contracts, as well as best practices for preventing and mitigating those risks.
By completing this course, you will become a more knowledgeable and skilled auditor, increasing your chances of landing a job in the growing field of smart contract auditing.
But the benefits don't stop there. Upon completion of the course, you'll receive a certificate to showcase your newly acquired expertise.
This certificate can be a powerful tool when seeking employment opportunities, demonstrating to potential employers that you have the necessary skills and knowledge to be a successful smart contract auditor.
Whether you're looking to advance your career or simply improve your understanding of smart contract security, this course is the perfect solution. Take your first step toward becoming a top-notch smart contract auditor:
领英推荐
DelegateCall Use Cases
Here are some of the most common use cases for the delegateCall opcode:
DelegateCall Examples
Let's look at some code samples to better understand how delegateCall works:
Example 1: How delegateCall works
In the following example, we have two smart contracts, Contract A and Contract B
contract A {
uint public a;
function setA(uint _a) public {
a = _a;
}
}
contract B {
address public aAddress;
uint public b;
constructor(address _aAddress) {
aAddress = _aAddress;
}
function setA(uint _a) public {
(bool success, bytes memory result) = aAddress.delegatecall(
abi.encodeWithSignature("setA(uint256)", _a)
);
require(success, "delegatecall failed");
}
function setB(uint _b) public {
b = _b;
}
}
In this example, contract B has a state variable aAddress that stores the address of contract A, which has a state variable a.
The setA function in contract B uses delegatecall to call the setA function in contract A with the context of contract B.
This allows contract A to access the storage variables and state of contract B.
Example 2: Gas-efficient Upgradeable Contracts
One common use case for delegatecall is to implement proxy contracts that can be upgraded (upgradeable contracts) in a gas-efficient way
The basic idea is to separate the contract state from the contract logic so that the contract logic can be upgraded without affecting the state.
Here's an example of a proxy upgradable contract that uses delegatecall:
pragma solidity ^0.8.0;
contract Proxy {
address private _implementation;
function setImplementation(address implementation) external {
_implementation = implementation;
}
fallback() external payable {
address impl = _implementation;
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
}
In this example, we define a contract called Proxy that contains a private _implementation variable. This variable stores the address of the contract that contains the contract logic. We also define a setImplementation function that allows the contract owner to update the _implementation variable.
The fallback function is where the magic happens. It uses delegatecall to execute the function call in the context of the contract that contains the contract logic. This means that the contract state is still stored in the Proxy contract, but the contract logic is executed in the context of the _implementation contract.
Conclusion
Site Reliability Engineer | DevOps | Python Developer | Cyber Crime Researcher
1 年Hi Johnny Time. Nice article. I have used Delegate calls while implementing upgradable smart contracts which follow the EIP-1822 standard. But is there a way to make call to a 3rd party contract from my contract by maintaining the actual caller's address as the msg.sender and simulataneously not taking the burden of storage? I'm having a problem while dynamically creating Chainlink VRF subscriptions from my contract for randomization. Turns out it's recognizing my contract's address as the caller, and making a delegate call wouldn't solve this. Any possible workaround?