DELEGATECALL
basically says that I'm a contract and I'm allowing (delegating) you to do whatever you want to my storage. DELEGATECALL
is a security risk for the sending contract which needs to trust that the receiving contract will treat the storage well.
DELEGATECALL
was a new opcode that was a bug fix for CALLCODE
which did not preserve msg.sender
and msg.value
. If Alice invokes Bob who does DELEGATECALL
to Charlie, the msg.sender
in the DELEGATECALL
is Alice (whereas if CALLCODE
was used the msg.sender
would be Bob).
Details
When D does CALL on E, the code runs in the context of E: the storage of E is used.
When D does CALLCODE on E, the code runs in the context of D. So imagine that the code of E is in D. Whenever the code writes to storage, it writes to the storage of account D, instead of E.
contract D {
uint public n;
address public sender;
function callSetN(address _e, uint _n) {
_e.call(bytes4(sha3("setN(uint256)")), _n); // E's storage is set, D is not modified
}
function callcodeSetN(address _e, uint _n) {
_e.callcode(bytes4(sha3("setN(uint256)")), _n); // D's storage is set, E is not modified
}
function delegatecallSetN(address _e, uint _n) {
_e.delegatecall(bytes4(sha3("setN(uint256)")), _n); // D's storage is set, E is not modified
}
}
contract E {
uint public n;
address public sender;
function setN(uint _n) {
n = _n;
sender = msg.sender;
// msg.sender is D if invoked by D's callcodeSetN. None of E's storage is updated
// msg.sender is C if invoked by C.foo(). None of E's storage is updated
// the value of "this" is D, when invoked by either D's callcodeSetN or C.foo()
}
}
contract C {
function foo(D _d, E _e, uint _n) {
_d.delegatecallSetN(_e, _n);
}
}
When D does CALLCODE on E, msg.sender
inside E is D as commented in the code above.
When an account C invokes D, and D does DELEGATECALL on E, msg.sender
inside E is C. That is, E has the same msg.sender
and msg.value
as D.
You can quickly test above in Solidity Browser.