Add a new opcode, DELEGATECALL
at 0xf4
, which is similar in idea to CALLCODE
, except that it propagates the sender and value from the parent scope to the child scope, i.e. the call created has the same sender and value as the original call.
DELEGATECALL
: 0xf4
, takes 6 operands:
- gas
: the amount of gas the code may use in order to execute;
- to
: the destination address whose code is to be executed;
- in_offset
: the offset into memory of the input;
- in_size
: the size of the input in bytes;
- out_offset
: the offset into memory of the output;
- out_size
: the size of the scratch pad for the output.
gas
is the total amount the callee receives.CALLCODE
, account creation never happens, so the upfront gas cost is always schedule.callGas
+ gas
.CALLER
and VALUE
behave exactly in the callee's environment as they do in the caller's environment.Propagating the sender and value from the parent scope to the child scope makes it much easier for a contract to store another address as a mutable source of code and ''pass through'' calls to it, as the child code would execute in essentially the same environment (except for reduced gas and increased callstack depth) as the parent.
Use case 1: split code to get around 3m gas barrier
~calldatacopy(0, 0, ~calldatasize())
if ~calldataload(0) < 2**253:
~delegate_call(msg.gas - 10000, $ADDR1, 0, ~calldatasize(), ~calldatasize(), 10000)
~return(~calldatasize(), 10000)
elif ~calldataload(0) < 2**253 * 2:
~delegate_call(msg.gas - 10000, $ADDR2, 0, ~calldatasize(), ~calldatasize(), 10000)
~return(~calldatasize(), 10000)
...
Use case 2: mutable address for storing the code of a contract:
if ~calldataload(0) / 2**224 == 0x12345678 and self.owner == msg.sender:
self.delegate = ~calldataload(4)
else:
~delegate_call(msg.gas - 10000, self.delegate, 0, ~calldatasize(), ~calldatasize(), 10000)
~return(~calldatasize(), 10000)
The child functions called by these methods can now freely reference msg.sender
and msg.value
.