A mental model for understanding delegatecall
Quiz:
The following chain of function calls is executed:
In the context of the
a) EOA
b) Contract
c) Contract
d) Contract
The
- Storage is updated in the calling contract, not the targeted contract
msg.sender is unchangedmsg.value is unchanged
This allows us to do fun things like implementing libraries or proxy patterns:
Where in these calls from users to the proxy contracts:
- The function invoked on the logic contract updates state in the proxy contract
- The implementation contract
msg.sender is the user's address - The implementation contract
msg.value is the ETH the user sent in the proxy contract call
Let's revisit the question from the beginning of this thread and write some code to prove that msg.sender is Contract B:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import "hardhat/console.sol";
contract B {
error DelegateCallFailed();
function bFunc(address c, address d) external returns (bytes memory) {
console.log("B msg.sender: ", msg.sender);
// delegatecall function cFunc() on contract C and
// pass it the address of contract D
(bool success, bytes memory data) =
c.delegatecall(abi.encodeWithSignature("cFunc(address)", d));
if (!success) revert DelegateCallFailed();
return data;
}
}
contract C {
function cFunc(address d) external view {
console.log("C msg.sender: ", msg.sender);
// Call function dFunc on contract D
D(d).dFunc();
}
}
contract D {
function dFunc() external view {
console.log("D msg.sender: ", msg.sender);
}
}
Once we've deployed these three contracts, we can use our
Where:
Let's update our function call diagram with these msg.senders for clarity:
Now I'd like to introduce a mental model for thinking about
Whenever a function on a target contract is invoked via
The calling contract is delegating functionality from another contract to within its own context.
Let's apply this mental model to the example function call chain from above.
In the three contracts we wrote earlier, we can prove this mental model works by moving function
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import "hardhat/console.sol";
contract B {
error DelegateCallFailed();
function bFunc(address d) external view {
console.log("B msg.sender: ", msg.sender);
// Call function cFunc that is now inside this contract
cFunc(d);
}
function cFunc(address d) public view {
console.log("C msg.sender: ", msg.sender);
// Call function dFunc on contract D
D(d).dFunc();
}
}
contract D {
function dFunc() external view {
console.log("D msg.sender: ", msg.sender);
}
}
Since this code is functionally the same as our original code with the
So functionally this is what's meant when
This mental model can also be used to understand why
If
I hope this thread helped you understand