Unterschied zwischen CALL, CALLCODE und DELEGATECALL

CALL und CALLCODE nehmen die gleiche Anzahl von Operanden (im Ausführungsstapel). Für das Ausnahme-Flag, das oben auf den Stapel geschoben wird: 0 bedeutet Ausnahme, 1 bedeutet erfolgreiche Ausführung. CALL ist einfach zu verstehen, aber ich konnte den feinen Unterschied zwischen CALL & CALLCODE nicht verdauen. Im gelben Papier steht, dass z

CALLCODE: Das bedeutet, dass der Empfänger tatsächlich derselbe Account ist wie bisher, nur dass der Code überschrieben wird.

Was bedeutet es, dass der Code überschrieben wird ? Bedeutet das, dass ich den Vertrag bitten kann, einen externen Code auszuführen? Es wäre hilfreich, wenn mir jemand ein Beispiel geben könnte, um zwischen den beiden zu unterscheiden.

EDIT: DELEGATECALL wurde in Homestead hinzugefügt, was ist der Unterschied?

Antworten (5)

DELEGATECALLsagt im Grunde, dass ich ein Vertrag bin und Ihnen erlaube (delegiere), mit meinem Speicher zu tun, was immer Sie wollen . DELEGATECALList ein Sicherheitsrisiko für den sendenden Vertrag, der darauf vertrauen muss, dass der empfangende Vertrag die Speicherung gut behandelt.

DELEGATECALLwar ein neuer Opcode, der eine Fehlerbehebung war, für CALLCODEdie nicht erhalten wurde msg.senderund msg.value. Wenn Alice Bob aufruft, der es DELEGATECALLmit Charlie tut, ist das msg.senderin DELEGATECALLAlice (während wenn CALLCODEverwendet msg.senderwürde, wäre das Bob).

Einzelheiten

Wenn D CALL auf E ausführt, läuft der Code im Kontext von E: die Speicherung von E wird verwendet.

Wenn D CALLCODE auf E ausführt, wird der Code im Kontext von D ausgeführt. Stellen Sie sich also vor, dass sich der Code von E in D befindet. Immer wenn der Code in den Speicher schreibt, schreibt er in den Speicher von Konto D und nicht in 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);
    }
}

Wenn D CALLCODE auf E ausführt, msg.senderist in E D , wie im obigen Code kommentiert.

Wenn ein Konto C D aufruft und D DELEGATECALL auf E ausführt, msg.senderbefindet sich in E C . Das heißt, E hat das gleiche msg.senderund msg.valuewie D.

Sie können oben im Solidity Browser schnell testen .

Danke für die Antwort, aber das ist semantisch ist seltsam. Zum Beispiel werde ich niemals mein Geld auf einen Vertrag einzahlen, der einen solchen Callcode-Betreiber hat. Was kann jemanden daran hindern, (send money out of the contract to some different address)den Callcode auszuführen?
Vielleicht lohnt es sich, eine andere Frage zu stellen, um andere Perspektiven zu erhalten, aber normalerweise würden beide Verträge D und E von derselben Person geschrieben, und Sie würden wahrscheinlich nur Geld auf einen Vertrag D einzahlen, dem Sie vertrauen. Sie haben Recht, jeder Vertrag D, der CALLCODE eines anderen Vertrages E ausführt, muss darauf achten, was E tut, und jeder, der D verwendet, sollte ebenfalls vorsichtig sein.
Können Sie DELEGATECALL auch erklären?
@PawełBylica DELEGATECALL hinzugefügt und hoffe, es ist immer noch klar.
wäre thisdas gleiche in den beiden Kontexten, ähnlich wie msg.senderund msg.value?
@TravisJacobs Ja. Der Antwort wurde eine Zeile hinzugefügt (und getestet).
@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.Und in diesem Fall, welches Ether-Gleichgewicht wird verwendet, das Gleichgewicht von E oder das Gleichgewicht von D?
@ user2284570 this.balancewäre das Guthaben von D. Siehe Codekommentar, der Wert von "this" ist D, wenn es entweder von callcodeSetN oder C.foo() von D aufgerufen wird .
@eth, auch wenn E einen Teil des Restbetrags sendet? Oder wird nur der Betrag zurückgegeben, wie CODESIZE von E CODESIZE von D zurückgeben würde (was bedeutet, dass E-Guthaben zum Senden verwendet würde)

Um den Unterschied zwischen dem Anruf, dem Anrufcode und dem Delegiertenanruf zu zeigen, können wir das Beispiel des folgenden Codes betrachten:

Verträge können auf drei Arten interagieren

  1. Anruf: Durch direkten Aufruf von einem Vertrag über eine Funktion, die nicht den Wert des Anrufers, sondern den Wert des Angerufenen festlegt. Und der Absender in diesem Fall ist nur der Anrufer

  2. CallCode : Beim Aufruf über CallCode ruft der Aufrufer die Funktion des Aufgerufenen auf und sendet seinen eigenen Wert (oder ändert seinen eigenen Wert mit den aufgerufenen Parametern), aber es werden keine Änderungen im Speicher des Aufgerufenen widergespiegelt. Auch hier ist Absender der Anrufer selbst.

  3. DelegateCall : Wenn ein dritter Vertrag einen Delegiertenaufruf an eine Funktion in callee im Namen des Aufrufers aufruft und Speicheränderungen am Wert des Aufrufers vorgenommen werden und nichts im Speicher des aufgerufenen widergespiegelt wird.

    Hier ist der Absender nicht mehr der Anrufer, sondern der dritte Vertragsanrufhelfer

Pragma Solidität ^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);
    }
}

Eine Aktualisierung des Beispiels von @eth für solidity v6:

  • Funktionsdefinitionen müssen seinpublic

  • keccak256anstelle vonsha3

  • Anrufargumente verwendenabi.encode()

  • address()Vertragsadresse zu bekommen

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

callcodewurde zugunsten von delegatecallVersion 0.8.4 verworfen

Ja, aber es wurde tatsächlich in Version 0.5.0 entfernt, das war die Version, in der viele Dinge aus Solidity entfernt wurden.
@abhi3700 Obwohl Callcode veraltet ist, hat es eine global verfügbare Funktion (Methode vom Typ 'Adresse'), es ist immer noch möglich, es in Inline-Assembly und Yul zu verwenden. Siehe folgenden Link zu den Solidity-Dokumenten: docs.soliditylang.org/en/v0.8.9/yul.html#evm-dialect

aktualisiere die Antwort von @atomh33ls

  • Verwenden Sie 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));  
         
    }
}
Hallo und Willkommen! Ich verstehe, dass Sie ein neuer Benutzer sind und keine Kommentare hinterlassen können, aber diese Antwort, wie Sie sie geschrieben haben, ist eher ein Kommentar. Ich würde empfehlen, es als vollständige Antwort umzuschreiben - verweisen Sie auf die Antwort von atomh33ls und sagen Sie, warum Sie es umschreiben (um die Verwendung von encodeanstelle von zu korrigieren encodePacked). Prost!
Für diese Art von Fällen gibt es ein spezielles abi encodeWithSignature.