Разница между CALL, CALLCODE и DELEGATECALL

CALL и CALLCODE принимают одинаковое количество операндов (в стеке выполнения). Для флага исключения, помещаемого на вершину стека: 0 означает исключение, 1 означает успешное выполнение. CALL легко понять, но я не мог понять тонкую разницу между CALL и CALLCODE. В желтой бумаге указано, что для

CALLCODE: Это означает, что получатель фактически является той же учетной записью, что и в настоящее время, просто код перезаписывается.

Что означает перезаписанный код ? Означает ли это, что я могу попросить контракт выполнить какой-то внешний код? Было бы полезно, если бы кто-нибудь мог привести мне пример, чтобы различать их.

РЕДАКТИРОВАТЬ: DELEGATECALL был добавлен в Homestead, в чем разница?

Ответы (5)

DELEGATECALLв основном говорит, что у меня контракт, и я разрешаю (делегирую) вам делать все, что вы хотите, с моим хранилищем . DELEGATECALLпредставляет собой угрозу безопасности для контракта-отправителя, который должен быть уверен, что контракт-получатель будет хорошо обращаться с хранилищем.

DELEGATECALLбыл новый код операции, который был исправлением ошибки, для CALLCODEкоторого не сохранялись msg.senderи файлы msg.value. Если Алиса вызывает Боба, который делает это DELEGATECALLс Чарли, то msg.senderэто DELEGATECALLАлиса (тогда как, если бы CALLCODEиспользовалось, это msg.senderбыл бы Боб).

Подробности

Когда D выполняет CALL для E, код выполняется в контексте E: используется память E.

Когда D выполняет CALLCODE для E, код выполняется в контексте D. Итак, представьте, что код E находится в D. Всякий раз, когда код записывает в хранилище, он записывает в хранилище учетной записи D, а не 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);
    }
}

Когда D выполняет CALLCODE для E, msg.senderвнутри E находится D , как указано в приведенном выше коде.

Когда учетная запись C вызывает D, а D выполняет DELEGATECALL для E, msg.senderвнутри E находится C. То есть E имеет то же самое, что msg.senderи msg.valueD.

Вы можете быстро протестировать выше в Solidity Browser .

Спасибо за ответ, но это семантически странно. Например, я никогда не вложу свои деньги в какой-либо контракт, в котором есть такой оператор callcode. Что может помешать кому-то выполнить (send money out of the contract to some different address)callcode?
Возможно, стоит опубликовать как еще один вопрос, чтобы получить другие точки зрения, но обычно оба контракта D и E будут написаны одним и тем же человеком, и вы, вероятно, вложите деньги только в контракт D, которому вы доверяете. Вы правы, любой контракт D, который выполняет CALLCODE другого контракта E, должен быть осторожен с тем, что делает E, и любой, кто использует D, также должен быть осторожен.
Можете ли вы также объяснить DELEGATECALL?
@PawełBylica добавил DELEGATECALL и надеюсь, что это все еще ясно.
будет thisодинаковым в двух контекстах, подобно msg.senderи msg.value?
@TravisJacobs Да. Добавил строку в ответ (и проверил).
@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.И в таком случае, какой баланс эфира используется, баланс E или баланс D?
@user2284570 this.balanceбудет балансом D. См. комментарий к коду, значение "this" равно D при вызове с помощью callcodeSetN D или C.foo() .
@eth, в том числе когда E отправляет часть баланса? Или просто возвращаемая сумма, например CODESIZE из E, вернет CODESIZE из D (что означает, что баланс E будет использоваться для отправки)

Показывая разницу между вызовом, кодом вызова и вызовом делегата, мы можем рассмотреть пример следующего кода:

Контракты могут взаимодействовать тремя способами

  1. Вызов: путем прямого вызова из контракта через функцию, которая не устанавливает значение вызывающего объекта, а устанавливает значение вызываемого объекта. И отправителем в этом будет только вызывающий абонент

  2. CallCode : при вызове через CallCode вызывающий вызывает функцию вызываемого объекта и отправляет свое собственное значение (или изменяет свое значение с помощью вызываемых параметров), но никакие изменения не отражаются в хранилище вызываемого объекта. Здесь также отправителем является сам вызывающий абонент.

  3. DelegateCall : когда третий контракт вызывает вызов делегата к некоторой функции в вызываемом объекте от имени вызывающего объекта, и изменения в хранилище вносятся в значение вызывающего объекта, и ничего не отражается в хранилище вызываемого объекта.

    Здесь отправитель больше не вызывающий абонент, а третий контракт Call Helper.

прочность прагмы ^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);
    }
}

Обновление примера @eth для Solidity v6:

  • определения функций должны бытьpublic

  • keccak256на местеsha3

  • использование аргументов вызоваabi.encode()

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

callcodeустарел в пользу delegatecallверсии 0.8.4

Да, но на самом деле он был удален еще в версии 0.5.0, в которой многие вещи были удалены из Solidity.
@abhi3700 abhi3700 Хотя callcode устарел, имеет глобально доступную функцию (метод из типа «адрес»), его все еще можно использовать во встроенной сборке и Yul. См. следующую ссылку на документы Solidity: docs.soliditylang.org/en/v0.8.9/yul.html#evm-dialect

обновить ответ @atomh33ls

  • используйте 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));  
         
    }
}
Привет и добро пожаловать! Я понимаю, что вы новый пользователь и не можете оставлять комментарии, но этот ответ, как вы его написали, больше похож на комментарий. Я бы порекомендовал переписать его как полный ответ - сослаться на ответ atomh33ls и сказать, почему вы его переписываете (чтобы исправить использование encodeвместо encodePacked). Ваше здоровье!
Для таких случаев есть особое аби encodeWithSignature.