From 1076b6a06fac679612a914640bd7eab01f35be97 Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Wed, 7 Dec 2022 22:44:41 +0800 Subject: [PATCH 1/6] Fix to not invoke `transfer` for call related opcodes (CALLCODE, DELEGATECALL and STATICCALL) expect CALL. --- src/zkevm_specs/evm/execution/callop.py | 32 +++++++++++++++---------- tests/evm/test_callop.py | 20 +++++++++++----- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/zkevm_specs/evm/execution/callop.py b/src/zkevm_specs/evm/execution/callop.py index 55318afd1..7425b2ca9 100644 --- a/src/zkevm_specs/evm/execution/callop.py +++ b/src/zkevm_specs/evm/execution/callop.py @@ -108,10 +108,14 @@ def callop(instruction: Instruction): has_value = 1 - instruction.is_zero(value) instruction.constrain_zero(has_value * is_static) - # Verify transfer - _, (_, callee_balance_prev) = instruction.transfer( - caller_address, callee_address, value, callee_reversion_info - ) + if is_call == 1: + # Verify transfer only for CALL opcode. + _, (_, callee_balance) = instruction.transfer( + caller_address, callee_address, value, callee_reversion_info + ) + else: + # Get callee balance for CALLCODE, DELEGATECALL and STATICCALL opcodes. + callee_balance = instruction.account_read(callee_address, AccountFieldTag.Balance) # Verify gas cost callee_nonce = instruction.account_read(callee_address, AccountFieldTag.Nonce) @@ -119,10 +123,12 @@ def callop(instruction: Instruction): is_empty_code_hash = instruction.is_equal( callee_code_hash, instruction.rlc_encode(EMPTY_CODE_HASH, 32) ) + # TODO: + # Suppose to fix to use non-existing proofs for account existence after it + # has been used for one opcode in zkevm-circuit. + # https://github.com/privacy-scaling-explorations/zkevm-circuits/pull/907 is_account_empty = ( - instruction.is_zero(callee_nonce) - * instruction.is_zero(callee_balance_prev) - * is_empty_code_hash + instruction.is_zero(callee_nonce) * instruction.is_zero(callee_balance) * is_empty_code_hash ) gas_cost = ( instruction.select( @@ -161,10 +167,11 @@ def callop(instruction: Instruction): expected_value, ) - # Both CALL and CALLCODE opcodes have an extra stack pop `value`, and - # opcode DELEGATECALL has two extra call context lookups - parent caller - # address and value. - rw_counter_delta = 23 + is_call + is_callcode + is_delegatecall * 2 + # For CALL opcode, it has an extra stack pop `value` and two account write for `transfer` call (+3). + # For CALLCODE opcode, it has an extra stack pop `value` and one account read for callee balance (+2). + # For DELEGATECALL opcode, has two extra call context lookups for current caller address and value (+2). + # For STATICCALL opcode, it has one account read for callee balance (+1). + rw_counter_delta = 21 + is_call * 3 + is_callcode * 2 + is_delegatecall * 3 + is_staticcall stack_pointer_delta = 5 + is_call + is_callcode instruction.constrain_step_state_transition( @@ -181,7 +188,8 @@ def callop(instruction: Instruction): code_hash=Transition.same(), ) else: - rw_counter_delta = 43 + is_call + is_callcode + is_delegatecall * 2 + # Similar as above comment. + rw_counter_delta = 41 + is_call * 3 + is_callcode * 2 + is_delegatecall * 3 + is_staticcall stack_pointer_delta = 5 + is_call + is_callcode # Save caller's call state diff --git a/tests/evm/test_callop.py b/tests/evm/test_callop.py index db5783cb1..11f88aa53 100644 --- a/tests/evm/test_callop.py +++ b/tests/evm/test_callop.py @@ -184,6 +184,7 @@ def test_callop( is_call = 1 if opcode == Opcode.CALL else 0 is_callcode = 1 if opcode == Opcode.CALLCODE else 0 is_delegatecall = 1 if opcode == Opcode.DELEGATECALL else 0 + is_staticcall = 1 if opcode == Opcode.STATICCALL else 0 # Set `is_static == 1` for both DELEGATECALL and STATICCALL opcodes, or when # `stack.value == 0` for both CALL and CALLCODE opcodes. @@ -266,10 +267,11 @@ def test_callop( ) ) - # Both CALL and CALLCODE opcodes have an extra stack pop `value`, and opcode - # DELEGATECALL has two extra call context lookups - parent caller address - # and value. - call_id = 23 + is_call + is_callcode + is_delegatecall * 2 + # For CALL opcode, it has an extra stack pop `value` and two account write for `transfer` call (+3). + # For CALLCODE opcode, it has an extra stack pop `value` and one account read for callee balance (+2). + # For DELEGATECALL opcode, has two extra call context lookups for current caller address and value (+2). + # For STATICCALL opcode, it has one account read for callee balance (+1). + call_id = 21 + is_call * 3 + is_callcode * 2 + is_delegatecall * 3 + is_staticcall rw_counter = call_id next_program_counter = 232 if is_call + is_callcode == 1 else 199 stack_pointer = 1018 - is_call - is_callcode @@ -329,9 +331,15 @@ def test_callop( callee_balance = RLC(callee.balance + value, randomness) # fmt: off + if is_call == 1: + rw_dictionary \ + .account_write(caller.address, AccountFieldTag.Balance, caller_balance, caller_balance_prev, rw_counter_of_reversion=None if callee_is_persistent else callee_rw_counter_end_of_reversion) \ + .account_write(callee.address, AccountFieldTag.Balance, callee_balance, callee_balance_prev, rw_counter_of_reversion=None if callee_is_persistent else callee_rw_counter_end_of_reversion - 1) + else: + rw_dictionary \ + .account_read(callee.address, AccountFieldTag.Balance, RLC(callee.balance, randomness)) + rw_dictionary \ - .account_write(caller.address, AccountFieldTag.Balance, caller_balance, caller_balance_prev, rw_counter_of_reversion=None if callee_is_persistent else callee_rw_counter_end_of_reversion) \ - .account_write(callee.address, AccountFieldTag.Balance, callee_balance, callee_balance_prev, rw_counter_of_reversion=None if callee_is_persistent else callee_rw_counter_end_of_reversion - 1) \ .account_read(callee.address, AccountFieldTag.Nonce, RLC(callee.nonce, randomness)) \ .account_read(code_address, AccountFieldTag.CodeHash, callee_bytecode_hash) From 88253cffadea25bd7c2d2c661baf6e7e82c907dc Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Wed, 7 Dec 2022 23:06:51 +0800 Subject: [PATCH 2/6] Update Markdown of call opcodes. --- .../F1CALL_F2CALLCODE_F4DELEGATECALL_FASTATICCALL.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/specs/opcode/F1CALL_F2CALLCODE_F4DELEGATECALL_FASTATICCALL.md b/specs/opcode/F1CALL_F2CALLCODE_F4DELEGATECALL_FASTATICCALL.md index d0fc6a7b8..9c10c2778 100644 --- a/specs/opcode/F1CALL_F2CALLCODE_F4DELEGATECALL_FASTATICCALL.md +++ b/specs/opcode/F1CALL_F2CALLCODE_F4DELEGATECALL_FASTATICCALL.md @@ -25,7 +25,8 @@ It creates a new sub context as setting caller address to parent caller's and ca - For opcode `STATICCALL`: It does not allow any state modifying instructions (is_static == 1) or sending ether to callee in the sub context. -And both `DELEGATECALL` and `STATICCALL` opcodes only pop 6 words from stack `gas`, `callee_address`, `call_data_offset`, `call_data_length`, `return_data_offset` and `return_data_length` (except the third popped word `value` for both `CALL` and `CALLCODE` opcodes). +Both `DELEGATECALL` and `STATICCALL` opcodes only pop 6 words from stack `gas`, `callee_address`, `call_data_offset`, `call_data_length`, `return_data_offset` and `return_data_length` (except the third popped word `value` for both `CALL` and `CALLCODE` opcodes). +There should be no `transfer` invocation for `CALLCODE`, `DELEGATECALL`and `STATICCALL` (except `CALL`). Before switching call context to the new one, it does several things: @@ -34,7 +35,7 @@ Before switching call context to the new one, it does several things: 3. Calculate `gas_cost` and check `gas_left` is enough 4. Calculate `callee_gas_left` for new context by rule in EIP150 5. Check `depth` is less than `1024` -6. Check `value` could be transfer (zero for both `DELEGATECALL` and `STATICCALL` opcodes) +6. Check `value` could be transferred (only for `CALL` and `CALLCODE` opcodes) The memory size is calculated as follows: @@ -84,7 +85,7 @@ callee_gas_left := min(gas_available - floor(gas_available / 64), gas) After switching call context, it does: -1. Transfer `value` (zero for both `DELEGATECALL` and `STATICCALL` opcodes) +1. Transfer `value` (only for `CALL` opcode) 2. Execution 1. If `callee_address` is a precompiled, it runs the pre-defined handler 2. Otherwise, it takes callee's code for execution From a679bd7c2387395701b24d2c105dcfb021c4db25 Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Thu, 8 Dec 2022 09:04:21 +0800 Subject: [PATCH 3/6] Fix comments. --- src/zkevm_specs/evm/execution/callop.py | 2 +- tests/evm/test_callop.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zkevm_specs/evm/execution/callop.py b/src/zkevm_specs/evm/execution/callop.py index 7425b2ca9..50aaf07d1 100644 --- a/src/zkevm_specs/evm/execution/callop.py +++ b/src/zkevm_specs/evm/execution/callop.py @@ -169,7 +169,7 @@ def callop(instruction: Instruction): # For CALL opcode, it has an extra stack pop `value` and two account write for `transfer` call (+3). # For CALLCODE opcode, it has an extra stack pop `value` and one account read for callee balance (+2). - # For DELEGATECALL opcode, has two extra call context lookups for current caller address and value (+2). + # For DELEGATECALL opcode, has two extra call context lookups for current caller address and value, and one account read for callee balance (+3). # For STATICCALL opcode, it has one account read for callee balance (+1). rw_counter_delta = 21 + is_call * 3 + is_callcode * 2 + is_delegatecall * 3 + is_staticcall stack_pointer_delta = 5 + is_call + is_callcode diff --git a/tests/evm/test_callop.py b/tests/evm/test_callop.py index 11f88aa53..7d3519f68 100644 --- a/tests/evm/test_callop.py +++ b/tests/evm/test_callop.py @@ -269,7 +269,7 @@ def test_callop( # For CALL opcode, it has an extra stack pop `value` and two account write for `transfer` call (+3). # For CALLCODE opcode, it has an extra stack pop `value` and one account read for callee balance (+2). - # For DELEGATECALL opcode, has two extra call context lookups for current caller address and value (+2). + # For DELEGATECALL opcode, has two extra call context lookups for current caller address and value, and one account read for callee balance (+3). # For STATICCALL opcode, it has one account read for callee balance (+1). call_id = 21 + is_call * 3 + is_callcode * 2 + is_delegatecall * 3 + is_staticcall rw_counter = call_id From ced99ca194b8cd2bd9eb24a35a2722b13a7e2210 Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Sun, 18 Dec 2022 10:26:10 +0800 Subject: [PATCH 4/6] Fix to use non-existing proof for callee account existence, and add a constraint yo verify caller balance should be greater than or equal to stack `value` (only for CALL and CALLCODE). --- src/zkevm_specs/evm/execution/callop.py | 64 +++++++++++++++---------- tests/evm/test_callop.py | 34 +++++++++---- 2 files changed, 64 insertions(+), 34 deletions(-) diff --git a/src/zkevm_specs/evm/execution/callop.py b/src/zkevm_specs/evm/execution/callop.py index 50aaf07d1..b5b224949 100644 --- a/src/zkevm_specs/evm/execution/callop.py +++ b/src/zkevm_specs/evm/execution/callop.py @@ -109,32 +109,48 @@ def callop(instruction: Instruction): instruction.constrain_zero(has_value * is_static) if is_call == 1: - # Verify transfer only for CALL opcode. - _, (_, callee_balance) = instruction.transfer( + # For CALL opcode, verify transfer, and get caller balance before + # transfer to constrain it should be greater or equal to stack `value`. + (_, caller_balance), _ = instruction.transfer( caller_address, callee_address, value, callee_reversion_info ) - else: - # Get callee balance for CALLCODE, DELEGATECALL and STATICCALL opcodes. - callee_balance = instruction.account_read(callee_address, AccountFieldTag.Balance) + elif is_callcode == 1: + # For CALLCODE opcode, get caller balance to constrain it should be + # greater or equal to stack `value`. + caller_balance = instruction.account_read(caller_address, AccountFieldTag.Balance) - # Verify gas cost - callee_nonce = instruction.account_read(callee_address, AccountFieldTag.Nonce) - callee_code_hash = instruction.account_read(code_address, AccountFieldTag.CodeHash) - is_empty_code_hash = instruction.is_equal( - callee_code_hash, instruction.rlc_encode(EMPTY_CODE_HASH, 32) - ) - # TODO: - # Suppose to fix to use non-existing proofs for account existence after it - # has been used for one opcode in zkevm-circuit. - # https://github.com/privacy-scaling-explorations/zkevm-circuits/pull/907 - is_account_empty = ( - instruction.is_zero(callee_nonce) * instruction.is_zero(callee_balance) * is_empty_code_hash - ) + # For both CALL and CALLCODE opcodes, verify caller balance is greater or + # equal to stack `value`. + if is_call + is_callcode == 1: + value_lt_caller_balance, value_eq_caller_balance = instruction.compare_word( + value, caller_balance + ) + instruction.constrain_zero(1 - value_lt_caller_balance - value_eq_caller_balance) + + # Load callee account `exists` value from auxilary witness data. + callee_exists = instruction.curr.aux_data + + if callee_exists == 1: + # Get callee code hash. + callee_code_hash = instruction.account_read(code_address, AccountFieldTag.CodeHash) + is_empty_code_hash = instruction.is_equal( + callee_code_hash, instruction.rlc_encode(EMPTY_CODE_HASH, 32) + ) + else: # callee_exists == 0 + instruction.account_read(code_address, AccountFieldTag.NonExisting) + is_empty_code_hash = FQ(1) + + # Verify gas cost. gas_cost = ( instruction.select( is_warm_access, FQ(GAS_COST_WARM_ACCESS), FQ(GAS_COST_ACCOUNT_COLD_ACCESS) ) - + has_value * (GAS_COST_CALL_WITH_VALUE + is_account_empty * GAS_COST_NEW_ACCOUNT) + + has_value + * ( + GAS_COST_CALL_WITH_VALUE + # Only CALL opcode could invoke transfer to make empty account into non-empty. + + is_call * (1 - callee_exists) * GAS_COST_NEW_ACCOUNT + ) + memory_expansion_gas_cost ) @@ -168,10 +184,10 @@ def callop(instruction: Instruction): ) # For CALL opcode, it has an extra stack pop `value` and two account write for `transfer` call (+3). - # For CALLCODE opcode, it has an extra stack pop `value` and one account read for callee balance (+2). - # For DELEGATECALL opcode, has two extra call context lookups for current caller address and value, and one account read for callee balance (+3). - # For STATICCALL opcode, it has one account read for callee balance (+1). - rw_counter_delta = 21 + is_call * 3 + is_callcode * 2 + is_delegatecall * 3 + is_staticcall + # For CALLCODE opcode, it has an extra stack pop `value` and one account read for caller balance (+2). + # For DELEGATECALL opcode, it has two extra call context lookups for current caller address and value (+2). + # No extra lookups for STATICCALL opcode. + rw_counter_delta = 20 + is_call * 3 + is_callcode * 2 + is_delegatecall * 2 stack_pointer_delta = 5 + is_call + is_callcode instruction.constrain_step_state_transition( @@ -189,7 +205,7 @@ def callop(instruction: Instruction): ) else: # Similar as above comment. - rw_counter_delta = 41 + is_call * 3 + is_callcode * 2 + is_delegatecall * 3 + is_staticcall + rw_counter_delta = 40 + is_call * 3 + is_callcode * 2 + is_delegatecall * 2 stack_pointer_delta = 5 + is_call + is_callcode # Save caller's call state diff --git a/tests/evm/test_callop.py b/tests/evm/test_callop.py index 7d3519f68..c56fc02ce 100644 --- a/tests/evm/test_callop.py +++ b/tests/evm/test_callop.py @@ -74,6 +74,7 @@ def memory_size(offset: int, length: int) -> int: return 0 return (offset + length + 31) // 32 + is_call = 1 if opcode == Opcode.CALL else 0 # Both CALL and CALLCODE opcodes have argument `value` on stack, but no for # DELEGATECALL or STATICCALL. has_value = stack.value != 0 if opcode in [Opcode.CALL, Opcode.CALLCODE] else False @@ -87,7 +88,12 @@ def memory_size(offset: int, length: int) -> int: ) // 512 + 3 * (next_memory_size - caller_ctx.memory_size) gas_cost = ( (GAS_COST_WARM_ACCESS if is_warm_access else GAS_COST_ACCOUNT_COLD_ACCESS) - + has_value * (GAS_COST_CALL_WITH_VALUE + callee.is_empty() * GAS_COST_NEW_ACCOUNT) + + has_value + * ( + GAS_COST_CALL_WITH_VALUE + # Only CALL opcode could invoke transfer to make empty account into non-empty. + + is_call * callee.is_empty() * GAS_COST_NEW_ACCOUNT + ) + memory_expansion_gas_cost ) gas_available = caller_ctx.gas_left - gas_cost @@ -186,6 +192,8 @@ def test_callop( is_delegatecall = 1 if opcode == Opcode.DELEGATECALL else 0 is_staticcall = 1 if opcode == Opcode.STATICCALL else 0 + callee_exists = 0 if callee.is_empty() else 1 + # Set `is_static == 1` for both DELEGATECALL and STATICCALL opcodes, or when # `stack.value == 0` for both CALL and CALLCODE opcodes. value = stack.value if is_call + is_callcode == 1 else 0 @@ -268,10 +276,10 @@ def test_callop( ) # For CALL opcode, it has an extra stack pop `value` and two account write for `transfer` call (+3). - # For CALLCODE opcode, it has an extra stack pop `value` and one account read for callee balance (+2). - # For DELEGATECALL opcode, has two extra call context lookups for current caller address and value, and one account read for callee balance (+3). - # For STATICCALL opcode, it has one account read for callee balance (+1). - call_id = 21 + is_call * 3 + is_callcode * 2 + is_delegatecall * 3 + is_staticcall + # For CALLCODE opcode, it has an extra stack pop `value` and one account read for caller balance (+2). + # For DELEGATECALL opcode, it has two extra call context lookups for current caller address and value (+2). + # No extra lookups for STATICCALL opcode. + call_id = 20 + is_call * 3 + is_callcode * 2 + is_delegatecall * 2 rw_counter = call_id next_program_counter = 232 if is_call + is_callcode == 1 else 199 stack_pointer = 1018 - is_call - is_callcode @@ -332,16 +340,21 @@ def test_callop( # fmt: off if is_call == 1: + # For `transfer` invocation. rw_dictionary \ .account_write(caller.address, AccountFieldTag.Balance, caller_balance, caller_balance_prev, rw_counter_of_reversion=None if callee_is_persistent else callee_rw_counter_end_of_reversion) \ .account_write(callee.address, AccountFieldTag.Balance, callee_balance, callee_balance_prev, rw_counter_of_reversion=None if callee_is_persistent else callee_rw_counter_end_of_reversion - 1) - else: + elif is_callcode == 1: + # Get caller balance to constrain it should be greater or equal to stack `value`. rw_dictionary \ - .account_read(callee.address, AccountFieldTag.Balance, RLC(callee.balance, randomness)) + .account_read(caller.address, AccountFieldTag.Balance, RLC(caller.balance, randomness)) - rw_dictionary \ - .account_read(callee.address, AccountFieldTag.Nonce, RLC(callee.nonce, randomness)) \ - .account_read(code_address, AccountFieldTag.CodeHash, callee_bytecode_hash) + if callee_exists == 1: + rw_dictionary \ + .account_read(code_address, AccountFieldTag.CodeHash, callee_bytecode_hash) + else: + rw_dictionary \ + .account_read(code_address, AccountFieldTag.NonExisting, RLC(1, randomness)) if is_empty_code_hash: rw_dictionary \ @@ -403,6 +416,7 @@ def test_callop( gas_left=caller_ctx.gas_left, memory_size=caller_ctx.memory_size, reversible_write_counter=caller_ctx.reversible_write_counter, + aux_data=callee_exists, ), ( StepState( From 13c254d7d64f41ea2bd93d3755e1c1e7b782ccd3 Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Sun, 18 Dec 2022 10:30:40 +0800 Subject: [PATCH 5/6] Update comments. --- src/zkevm_specs/evm/execution/callop.py | 9 +++++---- tests/evm/test_callop.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/zkevm_specs/evm/execution/callop.py b/src/zkevm_specs/evm/execution/callop.py index b5b224949..149435b16 100644 --- a/src/zkevm_specs/evm/execution/callop.py +++ b/src/zkevm_specs/evm/execution/callop.py @@ -110,17 +110,18 @@ def callop(instruction: Instruction): if is_call == 1: # For CALL opcode, verify transfer, and get caller balance before - # transfer to constrain it should be greater or equal to stack `value`. + # transfer to constrain it should be greater than or equal to stack + # `value`. (_, caller_balance), _ = instruction.transfer( caller_address, callee_address, value, callee_reversion_info ) elif is_callcode == 1: # For CALLCODE opcode, get caller balance to constrain it should be - # greater or equal to stack `value`. + # greater than or equal to stack `value`. caller_balance = instruction.account_read(caller_address, AccountFieldTag.Balance) - # For both CALL and CALLCODE opcodes, verify caller balance is greater or - # equal to stack `value`. + # For both CALL and CALLCODE opcodes, verify caller balance is greater than + # or equal to stack `value`. if is_call + is_callcode == 1: value_lt_caller_balance, value_eq_caller_balance = instruction.compare_word( value, caller_balance diff --git a/tests/evm/test_callop.py b/tests/evm/test_callop.py index c56fc02ce..c22a0eeff 100644 --- a/tests/evm/test_callop.py +++ b/tests/evm/test_callop.py @@ -345,7 +345,7 @@ def test_callop( .account_write(caller.address, AccountFieldTag.Balance, caller_balance, caller_balance_prev, rw_counter_of_reversion=None if callee_is_persistent else callee_rw_counter_end_of_reversion) \ .account_write(callee.address, AccountFieldTag.Balance, callee_balance, callee_balance_prev, rw_counter_of_reversion=None if callee_is_persistent else callee_rw_counter_end_of_reversion - 1) elif is_callcode == 1: - # Get caller balance to constrain it should be greater or equal to stack `value`. + # Get caller balance to constrain it should be greater than or equal to stack `value`. rw_dictionary \ .account_read(caller.address, AccountFieldTag.Balance, RLC(caller.balance, randomness)) From 61a74a1dbc52f3feb9be951ed47421bfb6b2837e Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 19 Dec 2022 17:04:36 +0800 Subject: [PATCH 6/6] Update specs/opcode/F1CALL_F2CALLCODE_F4DELEGATECALL_FASTATICCALL.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Carlos Pérez <37264926+CPerezz@users.noreply.github.com> --- specs/opcode/F1CALL_F2CALLCODE_F4DELEGATECALL_FASTATICCALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/opcode/F1CALL_F2CALLCODE_F4DELEGATECALL_FASTATICCALL.md b/specs/opcode/F1CALL_F2CALLCODE_F4DELEGATECALL_FASTATICCALL.md index 9c10c2778..4d77fc44a 100644 --- a/specs/opcode/F1CALL_F2CALLCODE_F4DELEGATECALL_FASTATICCALL.md +++ b/specs/opcode/F1CALL_F2CALLCODE_F4DELEGATECALL_FASTATICCALL.md @@ -26,7 +26,7 @@ It creates a new sub context as setting caller address to parent caller's and ca It does not allow any state modifying instructions (is_static == 1) or sending ether to callee in the sub context. Both `DELEGATECALL` and `STATICCALL` opcodes only pop 6 words from stack `gas`, `callee_address`, `call_data_offset`, `call_data_length`, `return_data_offset` and `return_data_length` (except the third popped word `value` for both `CALL` and `CALLCODE` opcodes). -There should be no `transfer` invocation for `CALLCODE`, `DELEGATECALL`and `STATICCALL` (except `CALL`). +There should be no `transfer` invocation for `CALLCODE`, `DELEGATECALL`and `STATICCALL` (only for CALL). Before switching call context to the new one, it does several things: