Difference between CALL, CALLCODE and DELEGATECALL

  • CALL and CALLCODE take the same number of operands (in the execution stack). For the exception flag being pushed on top of the stack: 0 means exception, 1 means successful execution. CALL is easy to understand, but I could not digest the subtle difference between CALL & CALLCODE. It is stated in the yellow paper that for

    CALLCODE: This means that the recipient is in fact the same account as at present, simply that the code is overwritten.

    What does it mean by the code is overwritten? Does that mean I can ask the contract to execute some external code? It would be helpful if anyone can provide me an example to differentiate between the two.

    EDIT: DELEGATECALL was added in Homestead what is the difference?

  • eth

    eth Correct answer

    5 years ago

    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.

    Thanks for the answer, but this is semantic is strange though. For example, I will never deposit my money to any contract which has such callcode operator. What can prevent someone from executing `(send money out of the contract to some different address)` in the callcode?

    Maybe worth posting as another question to get other perspectives, but usually both contracts D and E would be written by same person, and you would probably only deposit money to a contract D that you trusted. You're right, any contract D that does CALLCODE of another contract E must be careful of what E does, and anyone using D should also be cautious.

    Can you also explain DELEGATECALL?

    @PawełBylica Added DELEGATECALL and hope it's still clear.

    would `this` be the same in the two contexts, similar to `msg.sender` and `msg.value`?

    @TravisJacobs Yes. Added a line to the answer (and tested it).

    @eth `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.` And in that case, which Ether balance is used, the balance of E or the Balance of D ?

    @user2284570 `this.balance` would be D's balance. See code comment *the value of "this" is D, when invoked by either D's callcodeSetN or C.foo()*.

    @eth including when E is sending some part of the balance ? Or is just the amount being returned like CODESIZE from E would return CODESIZE of D (which means E balance would be used for sending)

License under CC-BY-SA with attribution


Content dated before 7/24/2021 11:53 AM