Diferencia entre CALL, CALLCODE y DELEGATECALL

CALL y CALLCODE toman el mismo número de operandos (en la pila de ejecución). Para el indicador de excepción que se coloca en la parte superior de la pila: 0 significa excepción, 1 significa ejecución exitosa. CALL es fácil de entender, pero no pude digerir la sutil diferencia entre CALL y CALLCODE. Se afirma en el papel amarillo que para

CALLCODE: Esto significa que el destinatario es de hecho la misma cuenta que en la actualidad, simplemente que el código se sobrescribe.

¿Qué significa que el código se sobrescribe ? ¿Eso significa que puedo pedirle al contrato que ejecute algún código externo? Sería útil si alguien me puede dar un ejemplo para diferenciar entre los dos.

EDITAR: DELEGATECALL se agregó en Homestead, ¿cuál es la diferencia?

Respuestas (5)

DELEGATECALLbásicamente dice que soy un contrato y te permito (delegar) que hagas lo que quieras con mi almacenamiento . DELEGATECALLes un riesgo de seguridad para el contrato de envío que necesita confiar en que el contrato de recepción tratará bien el almacenamiento.

DELEGATECALLera un nuevo código de operación que era una corrección de errores para CALLCODElos que no conservaba msg.sendery msg.value. Si Alice invoca a Bob que lo hace DELEGATECALLa Charlie, el msg.senderen el DELEGATECALLes Alice (mientras que si CALLCODEse usara el msg.sendersería Bob).

Detalles

Cuando D hace CALL en E, el código se ejecuta en el contexto de E: se usa el almacenamiento de E.

Cuando D hace CALLCODE en E, el código se ejecuta en el contexto de D. Imagine que el código de E está en D. Cada vez que el código escribe en el almacenamiento, escribe en el almacenamiento de la cuenta D, en lugar de 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);
    }
}

Cuando D hace CALLCODE en E, msg.senderdentro de E está D como se comenta en el código anterior.

Cuando una cuenta C invoca D y D DELEGATECALL en E, msg.senderdentro de E está C . Es decir, E tiene el mismo msg.sendery msg.valueque D.

Puede probar rápidamente arriba en Solidity Browser .

Gracias por la respuesta, pero esto es semántico, aunque es extraño. Por ejemplo, nunca depositaré mi dinero en ningún contrato que tenga dicho operador de código de llamada. ¿Qué puede evitar que alguien ejecute (send money out of the contract to some different address)en el código de llamada?
Tal vez valga la pena publicarlo como otra pregunta para obtener otras perspectivas, pero por lo general ambos contratos D y E serían escritos por la misma persona, y probablemente solo depositarías dinero en un contrato D en el que confías. Tiene razón, cualquier contrato D que haga CALLCODE de otro contrato E debe tener cuidado con lo que hace E, y cualquiera que use D también debe tener cuidado.
¿También puedes explicar DELEGATECALL?
@PawełBylica agregó DELEGATECALL y espero que aún esté claro.
sería thislo mismo en los dos contextos, similar a msg.sendery msg.value?
@TravisJacobs Sí. Agregué una línea a la respuesta (y la probé).
@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.Y en ese caso, ¿qué balance de éter se usa, el balance de E o el balance de D?
@ user2284570 this.balancesería el saldo de D. Consulte el comentario del código , el valor de "esto" es D, cuando lo invoca CallcodeSetN o C.foo() de D.
@eth incluso cuando E está enviando una parte del saldo? ¿O es solo la cantidad que se devuelve como CODESIZE de E devolvería CODESIZE de D (lo que significa que el saldo E se usaría para enviar)

Mostrando la diferencia entre la llamada, el código de llamada y la llamada delegada, podemos considerar el ejemplo del siguiente código:

Los contratos pueden interactuar de tres maneras

  1. Llamada: llamando directamente desde un contrato a través de una función que no establecerá el valor de la persona que llama pero establece el valor de la persona que recibe la llamada. Y el remitente en esto será solo la persona que llama

  2. CallCode: cuando se llama a través de CallCode, la persona que llama llama a la función del destinatario y envía su propio valor (o modifica su propio valor con los parámetros llamados), pero no se reflejan cambios en el almacenamiento del destinatario. Aquí también remitente es el propio llamador.

  3. DelegateCall: cuando un tercer contrato llama a una llamada delegada a alguna función en el destinatario de la llamada en nombre de la persona que llama y se realizan cambios de almacenamiento en el valor de la persona que llama y nada se refleja en el almacenamiento de la persona que llama.

    Aquí el remitente ya no es la persona que llama sino el tercer contrato Call Helper

solidez de pragma ^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);
    }
}

Una actualización del ejemplo de @eth para solidity v6:

  • Las definiciones de funciones deben serpublic

  • keccak256en lugar desha3

  • uso de argumentos de llamadaabi.encode()

  • address()para obtener la dirección del contrato

    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);
         }
     }
    

callcodeha quedado obsoleto a favor de delegatecallsegún la versión 0.8.4

Sí, pero en realidad se eliminó en la versión 0.5.0, que fue la versión en la que se eliminaron muchas cosas de Solidity.
@ abhi3700 Aunque el código de llamada ha quedado obsoleto tiene una función disponible globalmente (método del tipo 'dirección'), todavía es posible usarlo en ensamblaje en línea y Yul. Consulte el siguiente enlace a los documentos de Solidity: docs.soliditylang.org/en/v0.8.9/yul.html#evm-dialect

actualizar @ atomh33ls respuesta

  • usar 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));  
         
    }
}
¡Hola y bienvenido! Entiendo que eres un usuario nuevo y no puedes dejar comentarios, pero esta respuesta, tal como la has escrito, es más un comentario. Recomendaría reescribirlo como una respuesta completa: haga referencia a la respuesta de atomh33ls y diga por qué lo está reescribiendo (para corregir el uso de encodeen lugar de encodePacked). ¡Salud!
Existe un abi específico para este tipo de casos encodeWithSignature.