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?
DELEGATECALL
básicamente dice que soy un contrato y te permito (delegar) que hagas lo que quieras con mi almacenamiento . DELEGATECALL
es un riesgo de seguridad para el contrato de envío que necesita confiar en que el contrato de recepción tratará bien el almacenamiento.
DELEGATECALL
era un nuevo código de operación que era una corrección de errores para CALLCODE
los que no conservaba msg.sender
y msg.value
. Si Alice invoca a Bob que lo hace DELEGATECALL
a Charlie, el msg.sender
en el DELEGATECALL
es Alice (mientras que si CALLCODE
se usara el msg.sender
sería Bob).
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.sender
dentro de E está D como se comenta en el código anterior.
Cuando una cuenta C invoca D y D DELEGATECALL en E, msg.sender
dentro de E está C . Es decir, E tiene el mismo msg.sender
y msg.value
que D.
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
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
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.
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
keccak256
en 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);
}
}
callcode
ha quedado obsoleto a favor de delegatecall
según la versión 0.8.4
actualizar @ atomh33ls respuesta
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));
}
}
encode
en lugar de encodePacked
). ¡Salud!encodeWithSignature
.
Loi.Luu
(send money out of the contract to some different address)
en el código de llamada?ética
Paweł Bylica
ética
travis jacobs
this
lo mismo en los dos contextos, similar amsg.sender
ymsg.value
?ética
usuario2284570
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?ética
this.balance
serí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.usuario2284570