tl;dr: delegatecall
runs in the context of the caller contract.
The difference between call
and delegatecall
in Solidity relates to the execution context:
target.call(funcData)
:- the function reads/modifies target contract's storage
msg.sender
is the caller contract
target.delegatecall(funcData)
- the function reads/modifies caller contract's storage
msg.sender
is the original sender == caller contract'smsg.sender
![[Attachments/call.webp]]
![[Attachments/delegatecall.webp]]
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.24;
import "forge-std/Test.sol";
contract Target {
address public owner;
uint256 public value;
function setOwnerAndValue(uint256 valueArg) public {
owner = msg.sender;
value = valueArg;
}
}
contract Caller {
address public owner;
uint256 public value;
function callSetOwnerAndValue(address target, uint256 valueArg) public {
(bool success, ) = target.call(abi.encodeWithSignature("setOwnerAndValue(uint256)", valueArg));
require(success, "call failed");
}
function delegatecallSetOwnerAndValue(address target, uint256 valueArg) public {
(bool success, ) = target.delegatecall(abi.encodeWithSignature("setOwnerAndValue(uint256)", valueArg));
require(success, "delegatecall failed");
}
}
contract MyTest is Test {
address sender = makeAddr("sender");
Target target;
Caller caller;
function setUp() public {
target = new Target();
caller = new Caller();
assertEq(target.owner(), address(0));
assertEq(target.value(), 0);
assertEq(caller.owner(), address(0));
assertEq(caller.value(), 0);
}
function test_callSetOwnerAndValue() public {
vm.prank(sender);
caller.callSetOwnerAndValue(address(target), 100);
// call modifies target contract's state, and target contract's msg.sender is caller contract
assertEq(target.owner(), address(caller));
assertEq(target.value(), 100);
// caller contract's state didn't change
assertEq(caller.owner(), address(0));
assertEq(caller.value(), 0);
}
function test_delegatecallSetOwnerAndValue() public {
vm.prank(sender);
caller.delegatecallSetOwnerAndValue(address(target), 200);
// target contract's state didn't change
assertEq(target.owner(), address(0));
assertEq(target.value(), 0);
// delegatecall runs in the context of caller contract, so msg.sender is sender
assertEq(caller.owner(), sender);
assertEq(caller.value(), 200);
}
}
ref:
https://medium.com/0xmantle/solidity-series-part-3-call-vs-delegatecall-8113b3c76855