117

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?

6 Answers 6

141

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.

An important note: in order for the DELEGATECALL to function properly, the storage layouts of the caller and the callee contracts (D and E, respectively, in our example above) needs to be compatible. If it is not, the DELEGATECALL may lead to unexpected behavior and erros.

You can quickly test above in Solidity Browser.

9
  • 1
    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?
    – Loi.Luu
    Commented May 7, 2016 at 8:32
  • 2
    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.
    – eth
    Commented May 8, 2016 at 0:22
  • Can you also explain DELEGATECALL? Commented May 18, 2016 at 20:07
  • @PawełBylica Added DELEGATECALL and hope it's still clear.
    – eth
    Commented May 19, 2016 at 10:41
  • 1
    @TravisJacobs Yes. Added a line to the answer (and tested it).
    – eth
    Commented Sep 21, 2017 at 8:50
8

Showing the difference between the call, callcode and the delegatecall we can consider the example of the following code :

Contracts can interact in three ways

  1. Call : By directly calling from a contract through a function which will not set the caller's value but sets the callee's value. And sender in this will be the caller only

  2. CallCode : When called through CallCode caller calls the function of the callee and sents its own value (or alter its own value with the called parameters ) but no changes are reflected to the callee's storage. Here also sender is the Caller itself.

  3. DelegateCall : When a third contract calls a delegate call to some function in callee on behalf of the caller and storage changes are made in the caller's value and nothing is reflected in callee's storage .

    Here the sender is no more the caller but the third contract Call Helper

pragma solidity ^0.4.0;

contract Caller {
uint public value;
address public sender;

function callSetValue(address _callee, uint _value) {
    _callee.call(bytes4(sha3("setValue(uint256)")), _value); // Callee's storage is set as given , Caller's is not modified 
}

function callcodeSetValue(address _callee, uint _value) {
    _callee.callcode(bytes4(sha3("setValue(uint256)")), _value); // Caller's storage is set, Calee is not modified 
}

function delegatecallSetValue(address _callee, uint _value) {
    _callee.delegatecall(bytes4(sha3("setValue(uint256)")), _value); // Caller's storage is set, Callee is not modified 
}
}

contract Callee {
uint public value;
address public sender;

function setValue(uint _value) {
    value = _value;
    sender = msg.sender;
    // msg.sender is Caller if invoked by Caller's callcodeSetValue. None of Callee's storage is updated
    // msg.sender is OnlyCaller if invoked by onlyCaller.justCall(). None of Callee's storage is updated

    // the value of "this" is Caller, when invoked by either Caller's callcodeSetValue or CallHelper.justCall()
}
}

contract CallHelper {
    function justCall(Caller _caller, Callee _callee, uint _value) {
        _caller.delegatecallSetValue(_callee, _value);
    }
}
7

An update to @eth's example for solidity v6:

  • function definitions must be public

  • keccak256 in place of sha3

  • call arguments use abi.encode()

  • address() to get contract address

    pragma solidity ^0.6.0;
    contract D {
       uint public n;
       address public sender;
    
       function callSetN(address _e, uint _n) public {
         _e.call(abi.encode(bytes4(keccak256("setN(uint256)")), _n)); // E's storage is set, D is not modified 
       }
    
       /*
       callcode is depreciated
       function callcodeSetN(address _e, uint _n) public {
         _e.callcode(abi.encode(bytes4(keccak256("setN(uint256)")), _n)); // D's storage is set, E is not modified 
       }
       */
    
       function delegatecallSetN(address _e, uint _n) public {
         _e.delegatecall(abi.encode(bytes4(keccak256("setN(uint256)")), _n)); // D's storage is set, E is not modified 
       }
     }
    
     contract E {
       uint public n;
       address public sender;
    
       function setN(uint _n) public {
         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) public {
             _d.delegatecallSetN(address(_e), _n);
         }
     }
    
7

callcode has been deprecated in favour of delegatecall as per version 0.8.4

2
  • Yeah, but it was actually removed back in version 0.5.0, which was the version where many things got removed from Solidity. Commented Jul 21, 2021 at 7:52
  • 4
    @abhi3700 Although callcode has been deprecated has a globally available function (method from the ‘address’ type), it is still possible to use it in inline assembly and Yul. See following link to Solidity docs: docs.soliditylang.org/en/v0.8.9/yul.html#evm-dialect
    – CJ42
    Commented Oct 19, 2021 at 15:35
2

As said previously address.callcode()has been deprecated and cannot be used directly after solidity 0.8.

enter image description here

However I see that the callcode opcode is still available in the opcode list after merge fork !

enter image description here

Does that mean that one could still use assembly to use callcode ? Are there any uses to keeping it?

1
  • As other people already said, it is deprecated and delegatecall should be used now. However, older contracts might still use the opcode. In the entirety of November, exactly four callcode calls were made (so it is pretty much dead). One of them linked below: etherscan.io/tx/… Commented Dec 25, 2022 at 23:50
1

update @atomh33ls answer

  • use abi.encodePacked
pragma solidity ^0.6.0;


contract E {
    uint256 public n;        
    address public sender;


    function setN(uint256 _n) public {
        n = _n;      
        sender = msg.sender;     
    } 
}


contract D{
    uint256 public n;
    address public sender;


    function callSetN(address _e,uint256 _n) public {
         _e.call(abi.encodePacked(bytes4(keccak256("setN(uint256)")),_n));  
         
    }
}
2
  • Hello and welcome! I understand that you're a new user and can't leave comments, but this answer as you've written it is more of a comment. I would recommend rewriting it as a complete answer - reference atomh33ls answer, and say why you're rewriting it (to correct the use of encode instead of encodePacked). Cheers! Commented Oct 21, 2020 at 9:37
  • 1
    There's an specific abi for this type of cases encodeWithSignature.
    – Ismael
    Commented Oct 21, 2020 at 19:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.