@@ -4,9 +4,8 @@ use inkwell::values::BasicValue;
44
55use crate :: polkavm:: context:: Context ;
66
7- const STATIC_CALL_FLAG : u32 = 0b0001_0000 ;
8- const REENTRANT_CALL_FLAG : u32 = 0b0000_1000 ;
9- const SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD : u64 = 2300 ;
7+ const STATIC_CALL_FLAG : u64 = 0b0001_0000 ;
8+ const REENTRANT_CALL_FLAG : u64 = 0b0000_1000 ;
109
1110/// Translates a contract call.
1211pub fn call < ' ctx > (
@@ -38,33 +37,12 @@ pub fn call<'ctx>(
3837 let output_length_pointer = context. build_alloca_at_entry ( context. xlen_type ( ) , "output_length" ) ;
3938 context. build_store ( output_length_pointer, output_length) ?;
4039
41- let ( flags, deposit_limit_value) = if static_call {
42- let flags = REENTRANT_CALL_FLAG | STATIC_CALL_FLAG ;
43- (
44- context. xlen_type ( ) . const_int ( flags as u64 , false ) ,
45- context. word_type ( ) . const_zero ( ) ,
46- )
40+ let flags = if static_call {
41+ REENTRANT_CALL_FLAG | STATIC_CALL_FLAG
4742 } else {
48- call_reentrancy_heuristic ( context , gas , input_length , output_length ) ?
43+ REENTRANT_CALL_FLAG
4944 } ;
5045
51- let deposit_pointer = context. build_alloca_at_entry ( context. word_type ( ) , "deposit_pointer" ) ;
52- context. build_store ( deposit_pointer, deposit_limit_value) ?;
53-
54- let flags_and_callee = revive_runtime_api:: calling_convention:: pack_hi_lo_reg (
55- context. builder ( ) ,
56- context. llvm ( ) ,
57- flags,
58- address_pointer. to_int ( context) ,
59- "address_and_callee" ,
60- ) ?;
61- let deposit_and_value = revive_runtime_api:: calling_convention:: pack_hi_lo_reg (
62- context. builder ( ) ,
63- context. llvm ( ) ,
64- deposit_pointer. to_int ( context) ,
65- value_pointer. to_int ( context) ,
66- "deposit_and_value" ,
67- ) ?;
6846 let input_data = revive_runtime_api:: calling_convention:: pack_hi_lo_reg (
6947 context. builder ( ) ,
7048 context. llvm ( ) ,
@@ -85,10 +63,10 @@ pub fn call<'ctx>(
8563 . build_runtime_call (
8664 name,
8765 & [
88- flags_and_callee . into ( ) ,
89- context . register_type ( ) . const_all_ones ( ) . into ( ) ,
90- context . register_type ( ) . const_all_ones ( ) . into ( ) ,
91- deposit_and_value . into ( ) ,
66+ context . xlen_type ( ) . const_int ( flags , false ) . into ( ) ,
67+ address_pointer . to_int ( context ) . into ( ) ,
68+ value_pointer . to_int ( context ) . into ( ) ,
69+ clip_call_gas ( context , gas ) ? ,
9270 input_data. into ( ) ,
9371 output_data. into ( ) ,
9472 ] ,
@@ -111,7 +89,7 @@ pub fn call<'ctx>(
11189
11290pub fn delegate_call < ' ctx > (
11391 context : & mut Context < ' ctx > ,
114- _gas : inkwell:: values:: IntValue < ' ctx > ,
92+ gas : inkwell:: values:: IntValue < ' ctx > ,
11593 address : inkwell:: values:: IntValue < ' ctx > ,
11694 input_offset : inkwell:: values:: IntValue < ' ctx > ,
11795 input_length : inkwell:: values:: IntValue < ' ctx > ,
@@ -132,18 +110,6 @@ pub fn delegate_call<'ctx>(
132110 let output_length_pointer = context. build_alloca_at_entry ( context. xlen_type ( ) , "output_length" ) ;
133111 context. build_store ( output_length_pointer, output_length) ?;
134112
135- let deposit_pointer = context. build_alloca_at_entry ( context. word_type ( ) , "deposit_pointer" ) ;
136- context. build_store ( deposit_pointer, context. word_type ( ) . const_all_ones ( ) ) ?;
137-
138- let flags = context. xlen_type ( ) . const_int ( 0u64 , false ) ;
139-
140- let flags_and_callee = revive_runtime_api:: calling_convention:: pack_hi_lo_reg (
141- context. builder ( ) ,
142- context. llvm ( ) ,
143- flags,
144- address_pointer. to_int ( context) ,
145- "address_and_callee" ,
146- ) ?;
147113 let input_data = revive_runtime_api:: calling_convention:: pack_hi_lo_reg (
148114 context. builder ( ) ,
149115 context. llvm ( ) ,
@@ -164,10 +130,9 @@ pub fn delegate_call<'ctx>(
164130 . build_runtime_call (
165131 name,
166132 & [
167- flags_and_callee. into ( ) ,
168- context. register_type ( ) . const_all_ones ( ) . into ( ) ,
169- context. register_type ( ) . const_all_ones ( ) . into ( ) ,
170- deposit_pointer. to_int ( context) . into ( ) ,
133+ context. xlen_type ( ) . const_int ( 0u64 , false ) . into ( ) ,
134+ address_pointer. to_int ( context) . into ( ) ,
135+ clip_call_gas ( context, gas) ?,
171136 input_data. into ( ) ,
172137 output_data. into ( ) ,
173138 ] ,
@@ -201,82 +166,22 @@ pub fn linker_symbol<'ctx>(
201166 context. build_load_address ( context. get_global ( path) ?. into ( ) )
202167}
203168
204- /// The Solidity `address.transfer` and `address.send` call detection heuristic.
205- ///
206- /// # Why
207- /// This heuristic is an additional security feature to guard against re-entrancy attacks
208- /// in case contract authors violate Solidity best practices and use `address.transfer` or
209- /// `address.send`.
210- /// While contract authors are supposed to never use `address.transfer` or `address.send`,
211- /// for a small cost we can be extra defensive about it.
212- ///
213- /// # How
214- /// The gas stipend emitted by solc for `transfer` and `send` is not static, thus:
215- /// - Dynamically allow re-entrancy only for calls considered not transfer or send.
216- /// - Detected balance transfers will supply 0 deposit limit instead of `u256::MAX`.
217- ///
218- /// Calls are considered transfer or send if:
219- /// - (Input length | Output lenght) == 0;
220- /// - Gas <= 2300;
221- ///
222- /// # Returns
223- /// The call flags xlen `IntValue` and the deposit limit word `IntValue`.
224- fn call_reentrancy_heuristic < ' ctx > (
225- context : & mut Context < ' ctx > ,
169+ /// The runtime implements gas as `u64` so we clip the stipend to `u64::MAX`.
170+ fn clip_call_gas < ' ctx > (
171+ context : & Context < ' ctx > ,
226172 gas : inkwell:: values:: IntValue < ' ctx > ,
227- input_length : inkwell:: values:: IntValue < ' ctx > ,
228- output_length : inkwell:: values:: IntValue < ' ctx > ,
229- ) -> anyhow:: Result < (
230- inkwell:: values:: IntValue < ' ctx > ,
231- inkwell:: values:: IntValue < ' ctx > ,
232- ) > {
233- // Branch-free SSA implementation: First derive the heuristic boolean (int1) value.
234- let input_length_or_output_length =
235- context
236- . builder ( )
237- . build_or ( input_length, output_length, "input_length_or_output_length" ) ?;
238- let is_no_input_no_output = context. builder ( ) . build_int_compare (
239- inkwell:: IntPredicate :: EQ ,
240- context. xlen_type ( ) . const_zero ( ) ,
241- input_length_or_output_length,
242- "is_no_input_no_output" ,
243- ) ?;
244- let gas_stipend = context
245- . word_type ( )
246- . const_int ( SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD , false ) ;
247- let is_gas_stipend_for_transfer_or_send = context. builder ( ) . build_int_compare (
248- inkwell:: IntPredicate :: ULE ,
249- gas,
250- gas_stipend,
251- "is_gas_stipend_for_transfer_or_send" ,
252- ) ?;
253- let is_balance_transfer = context. builder ( ) . build_and (
254- is_no_input_no_output,
255- is_gas_stipend_for_transfer_or_send,
256- "is_balance_transfer" ,
257- ) ?;
258- let is_regular_call = context
259- . builder ( )
260- . build_not ( is_balance_transfer, "is_balance_transfer_inverted" ) ?;
261-
262- // Call flag: Left shift the heuristic boolean value.
263- let is_regular_call_xlen = context. builder ( ) . build_int_z_extend (
264- is_regular_call,
265- context. xlen_type ( ) ,
266- "is_balance_transfer_xlen" ,
267- ) ?;
268- let call_flags = context. builder ( ) . build_left_shift (
269- is_regular_call_xlen,
270- context. xlen_type ( ) . const_int ( 3 , false ) ,
271- "flags" ,
272- ) ?;
173+ ) -> anyhow:: Result < inkwell:: values:: BasicValueEnum < ' ctx > > {
174+ let builder = context. builder ( ) ;
273175
274- // Deposit limit value: Sign-extended the heuristic boolean value.
275- let deposit_limit_value = context. builder ( ) . build_int_s_extend (
276- is_regular_call,
277- context. word_type ( ) ,
278- "deposit_limit_value" ,
176+ let clipped = context. register_type ( ) . const_all_ones ( ) ;
177+ let is_overflow = builder. build_int_compare (
178+ inkwell:: IntPredicate :: UGT ,
179+ gas,
180+ builder. build_int_z_extend ( clipped, context. word_type ( ) , "gas_clipped" ) ?,
181+ "is_gas_overflow" ,
279182 ) ?;
183+ let truncated = builder. build_int_truncate ( gas, context. register_type ( ) , "gas_truncated" ) ?;
184+ let call_gas = builder. build_select ( is_overflow, clipped, truncated, "call_gas" ) ?;
280185
281- Ok ( ( call_flags , deposit_limit_value ) )
186+ Ok ( call_gas )
282187}
0 commit comments