11"""
2- Tests nested CALL/CALLCODE gas usage with positive value transfer.
2+ Tests nested CALL/CALLCODE/DELEGATECALL/STATICCALL gas usage with positive
3+ value transfer (where applicable).
34
45This test investigates an issue identified in EthereumJS, as reported in:
56https://github.com/ethereumjs/ethereumjs-monorepo/issues/3194.
67
7- The issue pertains to the incorrect gas calculation for CALL/CALLCODE
8- operations with a positive value transfer, due to the pre-addition of the
9- gas stipend (2300) to the currently available gas instead of adding it to
10- the new call frame. This bug was specific to the case where insufficient
11- gas was provided for the CALL/CALLCODE operation. Due to the pre-addition
12- of the stipend to the currently available gas, the case for insufficient
13- gas was not properly failing with an out-of-gas error.
8+ The issue pertains to the incorrect gas calculation for
9+ CALL/CALLCODE/DELEGATECALL/STATICCALL operations with a positive value
10+ transfer, due to the pre-addition of the gas stipend (2300) to the currently
11+ available gas instead of adding it to the new call frame. This bug was specific
12+ to the case where insufficient gas was provided for the CALL/CALLCODE
13+ operation. Due to the pre-addition of the stipend to the currently available
14+ gas, the case for insufficient gas was not properly failing with an out-of-gas
15+ error.
1416
1517Test setup:
1618
4446 StateTestFiller ,
4547 Transaction ,
4648)
49+ from execution_testing .forks .forks .forks import Berlin , Byzantium , Homestead
50+ from execution_testing .forks .helpers import Fork
4751
48- """
49- PUSH opcode cost is 3, GAS opcode cost is 2.
50- We need 6 PUSH's and one GAS to fill the stack for both CALL & CALLCODE,
51- in the callee contract.
52- """
53- CALLEE_INIT_STACK_GAS = 6 * 3 + 2
5452
55- """
56- CALL gas breakdowns: (https://www.evm.codes/#f1)
57- memory_exp_cost + code_exec_cost + address_access_cost +
58- positive_value_cost + empty_account_cost
59- = 0 + 0 + 2600 + 9000 + 25000 = 36600
60- """
61- CALL_GAS = 36600
62- CALL_SUFFICIENT_GAS = CALL_GAS + CALLEE_INIT_STACK_GAS
53+ @pytest .fixture
54+ def callee_init_stack_gas (callee_opcode : Op , fork : Fork ) -> int :
55+ """
56+ Calculate the initial stack gas for the callee opcode.
57+ """
58+ if fork < Byzantium :
59+ # all *CALL arguments handled with PUSHes
60+ return len (callee_opcode .kwargs ) * 3
61+ else :
62+ # gas argument handled with GAS which is cheaper
63+ return (len (callee_opcode .kwargs ) - 1 ) * 3 + 2
6364
64- """
65- CALLCODE gas breakdowns: (https://www.evm.codes/#f2)
66- memory_exp_cost + code_exec_cost + address_access_cost +
67- positive_value_cost = 0 + 0 + 2600 + 9000 = 11600
68- """
69- CALLCODE_GAS = 11600
70- CALLCODE_SUFFICIENT_GAS = CALLCODE_GAS + CALLEE_INIT_STACK_GAS
65+
66+ @pytest .fixture
67+ def sufficient_gas (
68+ callee_opcode : Op , callee_init_stack_gas : int , fork : Fork
69+ ) -> int :
70+ """
71+ Calculate the sufficient gas for the nested call opcode with positive
72+ value transfer.
73+ """
74+ # memory_exp_cost is zero for our case.
75+ cost = 0
76+
77+ if fork >= Berlin :
78+ cost += 2600 # call and address_access_cost
79+ elif Byzantium <= fork < Berlin :
80+ cost += 700 # call
81+ elif fork == Homestead :
82+ cost += 40 # call
83+ cost += 1 # mandatory callee gas allowance
84+ else :
85+ raise Exception ("Only forks Homestead and >=Byzantium supported" )
86+
87+ is_value_call = callee_opcode in [Op .CALL , Op .CALLCODE ]
88+ if is_value_call :
89+ cost += 9000 # positive_value_cost
90+
91+ if callee_opcode == Op .CALL :
92+ cost += 25000 # empty_account_cost
93+
94+ cost += callee_init_stack_gas
95+
96+ return cost
7197
7298
7399@pytest .fixture
74- def callee_code (pre : Alloc , callee_opcode : Op ) -> Bytecode :
100+ def callee_code (pre : Alloc , callee_opcode : Op , fork : Fork ) -> Bytecode :
75101 """
76102 Code called by the caller contract:
77103 PUSH1 0x00 * 4
78- PUSH1 0x01 <- for positive value transfer
104+ PUSH1 0x01 <- for positive value transfer, if applies
79105 PUSH2 Contract.nonexistent
80- GAS <- value doesn't matter
81- CALL/CALLCODE.
106+ PUSH1 0x00 or GAS <- value doesn't matter:
107+ - PUSH1 0x01: pre Byzantium tests, as they require `gas_left` to be at
108+ least `gas`. In these cases we want non-zero `gas`, so that that
109+ condition check can also be triggered - see `gas_shortage` parameter
110+ values.
111+ - GAS: other forks, to not alter previous test behavior
112+ CALL/CALLCODE/DELEGATECALL/STATICCALL.
82113 """
83114 # The address needs to be empty and different for each execution of the
84115 # fixture, otherwise the calculations (empty_account_cost) are incorrect.
85- return callee_opcode (Op .GAS (), pre .empty_account (), 1 , 0 , 0 , 0 , 0 )
116+ is_value_call = callee_opcode in [Op .CALL , Op .CALLCODE ]
117+ extra_args = {"value" : 1 } if is_value_call else {}
118+
119+ return callee_opcode (
120+ unchecked = False ,
121+ gas = 1 if fork < Byzantium else Op .GAS ,
122+ address = pre .empty_account (),
123+ args_offset = 0 ,
124+ args_size = 0 ,
125+ ret_offset = 0 ,
126+ ret_size = 0 ,
127+ ** extra_args ,
128+ )
86129
87130
88131@pytest .fixture
@@ -98,7 +141,9 @@ def callee_address(pre: Alloc, callee_code: Bytecode) -> Address:
98141
99142
100143@pytest .fixture
101- def caller_code (caller_gas_limit : int , callee_address : Address ) -> Bytecode :
144+ def caller_code (
145+ sufficient_gas : int , gas_shortage : int , callee_address : Address
146+ ) -> Bytecode :
102147 """
103148 Code to CALL the callee contract:
104149 PUSH1 0x00 * 5
@@ -108,6 +153,8 @@ def caller_code(caller_gas_limit: int, callee_address: Address) -> Bytecode:
108153 PUSH1 0x00
109154 SSTORE.
110155 """
156+ caller_gas_limit = sufficient_gas - gas_shortage
157+
111158 return Op .SSTORE (
112159 0 , Op .CALL (caller_gas_limit , callee_address , 0 , 0 , 0 , 0 , 0 )
113160 )
@@ -128,36 +175,32 @@ def caller_address(pre: Alloc, caller_code: Bytecode) -> Address:
128175
129176
130177@pytest .fixture
131- def caller_tx (sender : EOA , caller_address : Address ) -> Transaction :
178+ def caller_tx (sender : EOA , caller_address : Address , fork : Fork ) -> Transaction :
132179 """Transaction that performs the call to the caller contract."""
133180 return Transaction (
134181 to = caller_address ,
135182 value = 1 ,
136183 gas_limit = 500_000 ,
137184 sender = sender ,
185+ protected = fork >= Byzantium ,
138186 )
139187
140188
141189@pytest .fixture
142190def post ( # noqa: D103
143- caller_address : Address , is_sufficient_gas : bool
191+ caller_address : Address , gas_shortage : int
144192) -> Dict [Address , Account ]:
145193 return {
146194 caller_address : Account (
147- storage = {0x00 : 0x01 if is_sufficient_gas else 0x00 }
195+ storage = {0x00 : 0x01 if gas_shortage == 0 else 0x00 }
148196 ),
149197 }
150198
151199
152200@pytest .mark .parametrize (
153- "callee_opcode, caller_gas_limit, is_sufficient_gas" ,
154- [
155- (Op .CALL , CALL_SUFFICIENT_GAS , True ),
156- (Op .CALL , CALL_SUFFICIENT_GAS - 1 , False ),
157- (Op .CALLCODE , CALLCODE_SUFFICIENT_GAS , True ),
158- (Op .CALLCODE , CALLCODE_SUFFICIENT_GAS - 1 , False ),
159- ],
201+ "callee_opcode" , [Op .CALL , Op .CALLCODE , Op .DELEGATECALL , Op .STATICCALL ]
160202)
203+ @pytest .mark .parametrize ("gas_shortage" , [0 , 1 ])
161204@pytest .mark .valid_from ("London" )
162205def test_value_transfer_gas_calculation (
163206 state_test : StateTestFiller ,
@@ -166,7 +209,48 @@ def test_value_transfer_gas_calculation(
166209 post : Dict [str , Account ],
167210) -> None :
168211 """
169- Tests the nested CALL/CALLCODE opcode gas consumption with a positive value
170- transfer.
212+ Tests the nested CALL/CALLCODE/DELEGATECALL/STATICCALL opcode gas
213+ consumption with a positive value transfer.
214+ """
215+ state_test (env = Environment (), pre = pre , post = post , tx = caller_tx )
216+
217+
218+ @pytest .mark .parametrize (
219+ "callee_opcode" , [Op .CALL , Op .CALLCODE , Op .DELEGATECALL , Op .STATICCALL ]
220+ )
221+ @pytest .mark .parametrize ("gas_shortage" , [0 , 1 ])
222+ @pytest .mark .valid_from ("Byzantium" )
223+ @pytest .mark .valid_until ("Berlin" )
224+ def test_value_transfer_gas_calculation_byzantium (
225+ state_test : StateTestFiller ,
226+ pre : Alloc ,
227+ caller_tx : Transaction ,
228+ post : Dict [str , Account ],
229+ ) -> None :
230+ """
231+ Tests the nested CALL/CALLCODE/DELEGATECALL/STATICCALL opcode gas
232+ consumption with a positive value transfer.
233+ """
234+ state_test (env = Environment (), pre = pre , post = post , tx = caller_tx )
235+
236+
237+ @pytest .mark .parametrize (
238+ "callee_opcode" , [Op .CALL , Op .CALLCODE , Op .DELEGATECALL ]
239+ )
240+ # pre-Byzantium rules have one more condition to fail on:
241+ # the check for `gas_left` to be at least `gas` allowance specified
242+ # in the CALL. We will be setting that allowance to `1` and either
243+ # making the call miss that amount or fail on the earlier gas check.
244+ @pytest .mark .parametrize ("gas_shortage" , [0 , 1 , 2 ])
245+ @pytest .mark .valid_at ("Homestead" )
246+ def test_value_transfer_gas_calculation_homestead (
247+ state_test : StateTestFiller ,
248+ pre : Alloc ,
249+ caller_tx : Transaction ,
250+ post : Dict [str , Account ],
251+ ) -> None :
252+ """
253+ Tests the nested CALL/CALLCODE/DELEGATECALL opcode gas
254+ consumption with a positive value transfer.
171255 """
172256 state_test (env = Environment (), pre = pre , post = post , tx = caller_tx )
0 commit comments