This repository provides an example of how delegatecall works in Solidity and demonstrates how it can be used to modify a storage slot in a contract.
Delegatecall is a low-level function in Solidity that allows a contract to delegate the execution of a function to another contract while preserving the storage and context of the calling contract. This powerful feature enables contract composition and code reuse, but it also introduces potential security risks if not used carefully.
When using delegatecall, the code of the target contract is executed within the context of the calling contract. This means that the target contract can access and modify the storage variables of the calling contract. It can also execute its own logic using the storage and state of the calling contract.
Delegatecall takes the form:
(bool success, bytes memory result) = address(targetContract).delegatecall(abi.encodeWithSignature("functionName(uint256)", _paramName));
Where:
targetContract
: The address of the contract to which the execution is delegated.functionName
: The name of the function to be called in the target contract (with the param type)._paramName
: The parameter to be passed to the function in the target contract.
Make sure to replace targetContract
, functionName
, and _paramName
with the appropriate values for your specific use case.
The delegatecall function will execute the specified function in the target contract while preserving the storage and context of the calling contract. It returns a boolean value success
indicating whether the delegatecall was successful, and result
contains any data returned by the target contract's function.
One potential vulnerability with delegatecall is the ability to modify a storage slot in the calling contract. If the target contract is designed to modify a specific storage slot and the delegatecall is used without proper checks, an attacker can craft malicious function call data to modify that storage slot in an unintended way.
To mitigate this vulnerability, it's essential to implement appropriate checks and validate the function call data before executing delegatecall. Always validate the caller's authority and ensure that the provided data is safe and valid.
Good.sol
: This contract demonstrates the usage of delegatecall to invoke a function in another contract and modify a storage slot.Helper.sol
: This contract is the target of the delegatecall and contains the logic to modify a specific storage slot.Attack.sol
: This contract is the attacker that will take the address of aGood
contract in the constructor. He will then call theattack
function which will further initially call thesetNum
function present insideGood.sol
.Secure.sol
: This contract is theGood.sol
edited with the vulnerability fixed.
The vulnerability lies in the assumption made about the storage slot layout between Good.sol
and Helper.sol
. In Good.sol
, slot 0 is expected to hold the address of the Helper
contract, but in Helper.sol
, the setNum
function modifies slot 0 directly. This inconsistency leads to overwriting unintended data.
Specifically, in Good.sol
, the setNum
function delegates the execution to Helper.setNum
, assuming that Helper.setNum
will only modify the num
variable. However, in Helper.sol
, the setNum
function directly modifies slot 0, which, in the context of Good.sol
, holds the address of the Helper
contract.
As a result, calling setNum
in Good.sol
using delegatecall
inadvertently modifies slot 0 in the storage, overwriting the address of the Helper
contract with a new value, potentially causing unexpected behavior and compromising the intended functionality of the contracts.
To run the example and test the delegatecall functionality:
- Install the required dependencies with
npm install
. - Run the tests using
npm test
. This will execute the test cases in thetests/
directory.
- Always validate and sanitize input data before using delegatecall.
- Be cautious when delegating execution to external contracts, as they may have unintended side effects or malicious code.
- Ensure that only authorized contracts or addresses can perform delegatecall to avoid unauthorized modifications.
- Use stateless library contracts which means that the contracts to which you delegate the call should only be used for execution of logic and should not maintain state. This way, it is not possible for functions in the library to modify the state of the calling contract.
This repository is for educational purposes only. The code provided should not be used in production environments without thorough security audits. Use at your own risk.