diff --git a/crates/pvm-contract-macros/src/codegen/abi_import.rs b/crates/pvm-contract-macros/src/codegen/abi_import.rs index 297189d7..94e27a9e 100644 --- a/crates/pvm-contract-macros/src/codegen/abi_import.rs +++ b/crates/pvm-contract-macros/src/codegen/abi_import.rs @@ -285,9 +285,10 @@ fn generate_abi_reference_method(func: &AbiFunction) -> Result = func .inputs .iter() - .map(|p| { + .enumerate() + .map(|(i, p)| { let name = if p.name.is_empty() { - format!("arg{}", 0) + format!("arg{}", i) } else { to_snake_case(&p.name) }; @@ -312,13 +313,12 @@ fn generate_abi_reference_method(func: &AbiFunction) -> Result Result::call_evm( + let output = pvm_contract::call::call_evm_collect( pvm_contract::CallFlags::ALLOW_REENTRY, - self.addr.as_fixed_bytes(), u64::MAX, &[0u8; 32], &calldata, #output_arg, - ); - match result { - Ok(()) => { - let written = output_ref.len(); - let output = &output_buf[..written]; - Ok(#decode_return) - } - Err(e) => Err(pvm_contract::call::CallError::from(e)), - } + self.addr.as_fixed_bytes(), u64::MAX, &[0u8; 32], &calldata, + )?; + #success_body } }) } @@ -347,7 +338,7 @@ fn generate_abi_reference_method(func: &AbiFunction) -> Result Result<(TokenStream, TokenStream, TokenStream, bool), String> { +) -> Result<(TokenStream, TokenStream, bool), String> { // Check if we should generate a named return struct if has_named_components(&func.outputs) { let comps = func.outputs[0].components.as_ref().unwrap(); @@ -363,33 +354,26 @@ fn generate_return_handling( .map(|c: &AbiParam| format_ident!("{}", to_snake_case(&c.name))) .collect(); - let output_size: usize = comp_types.iter().map(|t: &SolType| t.head_size()).sum(); - - let mut offset = 0usize; - let field_decodes: Vec = comp_types + let tuple_decode = generate_decode(&SolType::Tuple(comp_types.clone()), quote!(output), 0, true); + let tuple_bindings: Vec = field_names .iter() - .map(|t: &SolType| { - let d = generate_decode(t, quote!(output), offset, true); - offset += t.head_size(); - d + .enumerate() + .map(|(i, name)| { + let idx = syn::Index::from(i); + quote! { #name: __tuple.#idx } }) .collect(); - let field_assignments: Vec = field_names - .iter() - .zip(field_decodes.iter()) - .map(|(name, decode): (&Ident, &TokenStream)| quote! { #name: #decode }) - .collect(); - - let ret_ty: TokenStream = quote! { #struct_name }; - let output_setup: TokenStream = quote! { let mut output_buf = [0u8; #output_size]; }; - let decode_return: TokenStream = quote! { - #struct_name { - #(#field_assignments),* - } - }; - - Ok((ret_ty, output_setup, decode_return, true)) + Ok(( + quote! { #struct_name }, + quote! {{ + let __tuple = #tuple_decode; + #struct_name { + #(#tuple_bindings),* + } + }}, + true, + )) } else { Ok(generate_resolved_return_parts(output_types)) } @@ -588,4 +572,78 @@ mod tests { ]; assert!(!has_named_components(&outputs)); } + + #[test] + fn test_generate_reference_method_supports_nested_dynamic_inputs() { + let func = AbiFunction { + name: "submitArena".into(), + inputs: vec![ + AbiParam { + name: "player".into(), + type_str: "tuple".into(), + components: Some(vec![ + AbiParam { + name: "name".into(), + type_str: "string".into(), + components: None, + }, + AbiParam { + name: "rating".into(), + type_str: "uint64".into(), + components: None, + }, + ]), + }, + AbiParam { + name: "ghosts".into(), + type_str: "tuple[]".into(), + components: Some(vec![ + AbiParam { + name: "alias".into(), + type_str: "string".into(), + components: None, + }, + AbiParam { + name: "wins".into(), + type_str: "uint64".into(), + components: None, + }, + ]), + }, + ], + outputs: vec![AbiParam { + name: "".into(), + type_str: "string".into(), + components: None, + }], + }; + + let generated = generate_abi_reference_method(&func).unwrap().to_string(); + assert!(generated.contains("call_evm_collect")); + assert!(generated.contains("submit_arena")); + } + + #[test] + fn test_generate_reference_method_uses_unique_names_for_unnamed_inputs() { + let func = AbiFunction { + name: "pair".into(), + inputs: vec![ + AbiParam { + name: "".into(), + type_str: "uint64".into(), + components: None, + }, + AbiParam { + name: "".into(), + type_str: "address".into(), + components: None, + }, + ], + outputs: vec![], + }; + + let generated = generate_abi_reference_method(&func).unwrap().to_string(); + assert!(generated.contains("arg0 : u64")); + assert!(generated.contains("arg1 : pvm_contract :: Address")); + } } diff --git a/crates/pvm-contract-macros/src/codegen/contract.rs b/crates/pvm-contract-macros/src/codegen/contract.rs index a5b4d9a0..2b977916 100644 --- a/crates/pvm-contract-macros/src/codegen/contract.rs +++ b/crates/pvm-contract-macros/src/codegen/contract.rs @@ -492,12 +492,12 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result = raw_types.iter().map(|ty| { - quote! { <#ty as pvm_contract::SolAbi>::HEAD_SIZE } + quote! { <#ty as pvm_contract::SolAbi>::SLOT_SIZE } }).collect(); let decode_stmts: Vec = param_names.iter().zip(raw_types.iter()).map(|(name, ty)| { quote! { let #name = <#ty as pvm_contract::SolAbi>::abi_decode(input, __offset); - __offset += <#ty as pvm_contract::SolAbi>::HEAD_SIZE; + __offset += <#ty as pvm_contract::SolAbi>::SLOT_SIZE; } }).collect(); let call_args: Vec<_> = param_names.iter().map(|n| quote!(#n)).collect(); @@ -977,33 +977,23 @@ fn generate_reference_method(method: &MethodInfo) -> TokenStream { }; // --- Varying pieces 4-6: return type, output buffer, decode --- - let (ret_ty, output_setup, decode_return, has_output) = if fully_resolved { + let (ret_ty, decode_return, has_output) = if fully_resolved { generate_resolved_return_parts(&sig.outputs) } else { match &method.return_type { - None => ( - quote! { () }, - quote! { let mut output_buf = alloc::vec![0u8; 0usize]; }, - quote! { () }, - false, - ), + None => (quote! { () }, quote! { () }, false), Some(ty) => ( quote! { #ty }, - quote! { - let __out_size: usize = <#ty as pvm_contract::SolAbi>::HEAD_SIZE; - let mut output_buf = alloc::vec![0u8; __out_size]; - }, quote! { <#ty as pvm_contract::SolAbi>::abi_decode(output, 0) }, true, ), } }; - // --- Varying piece 7: output_arg --- - let output_arg = if has_output { - quote! { Some(&mut output_ref) } + let success_body = if has_output { + quote! { Ok(#decode_return) } } else { - quote! { None } + quote! { Ok(()) } }; // --- Single function body template --- @@ -1012,20 +1002,11 @@ fn generate_reference_method(method: &MethodInfo) -> TokenStream { extern crate alloc; #selector_setup #encode_block - #output_setup - let mut output_ref: &mut [u8] = &mut output_buf[..]; - let result = ::call_evm( + let output = pvm_contract::call::call_evm_collect( pvm_contract::CallFlags::ALLOW_REENTRY, - self.addr.as_fixed_bytes(), u64::MAX, &[0u8; 32], &calldata, #output_arg, - ); - match result { - Ok(()) => { - let written = output_ref.len(); - let output = &output_buf[..written]; - Ok(#decode_return) - } - Err(e) => Err(pvm_contract::call::CallError::from(e)), - } + self.addr.as_fixed_bytes(), u64::MAX, &[0u8; 32], &calldata, + )?; + #success_body } } } diff --git a/crates/pvm-contract-macros/src/codegen/decode.rs b/crates/pvm-contract-macros/src/codegen/decode.rs index 6047041f..02fda922 100644 --- a/crates/pvm-contract-macros/src/codegen/decode.rs +++ b/crates/pvm-contract-macros/src/codegen/decode.rs @@ -3,106 +3,120 @@ use quote::quote; use crate::signature::SolType; -pub fn generate_decode( +fn add_offset(base: &TokenStream, offset: usize) -> TokenStream { + if offset == 0 { + base.clone() + } else { + quote! { (#base) + #offset } + } +} + +fn generate_decode_expr( ty: &SolType, data_expr: TokenStream, - offset: usize, + offset_expr: TokenStream, use_alloc: bool, ) -> TokenStream { - let offset_lit = offset; - match ty { SolType::Address => { quote! {{ let mut addr = [0u8; 20]; - addr.copy_from_slice(&#data_expr[#offset_lit + 12..#offset_lit + 32]); + addr.copy_from_slice(&#data_expr[#offset_expr + 12..#offset_expr + 32]); pvm_contract::Address::from(addr) }} } SolType::Bool => { quote! { - #data_expr[#offset_lit + 31] != 0 + #data_expr[#offset_expr + 31] != 0 } } SolType::Uint(8) => { quote! { - #data_expr[#offset_lit + 31] + #data_expr[#offset_expr + 31] } } SolType::Uint(16) => { quote! { - u16::from_be_bytes([#data_expr[#offset_lit + 30], #data_expr[#offset_lit + 31]]) + u16::from_be_bytes([#data_expr[#offset_expr + 30], #data_expr[#offset_expr + 31]]) } } SolType::Uint(32) => { quote! { - u32::from_be_bytes(#data_expr[#offset_lit + 28..#offset_lit + 32].try_into().unwrap()) + u32::from_be_bytes(#data_expr[#offset_expr + 28..#offset_expr + 32].try_into().unwrap()) } } SolType::Uint(64) => { quote! { - u64::from_be_bytes(#data_expr[#offset_lit + 24..#offset_lit + 32].try_into().unwrap()) + u64::from_be_bytes(#data_expr[#offset_expr + 24..#offset_expr + 32].try_into().unwrap()) } } SolType::Uint(128) => { quote! { - u128::from_be_bytes(#data_expr[#offset_lit + 16..#offset_lit + 32].try_into().unwrap()) + u128::from_be_bytes(#data_expr[#offset_expr + 16..#offset_expr + 32].try_into().unwrap()) } } SolType::Uint(_) => { quote! { - pvm_contract::U256::from_be_slice(&#data_expr[#offset_lit..#offset_lit + 32]) + pvm_contract::U256::from_be_slice(&#data_expr[#offset_expr..#offset_expr + 32]) } } SolType::Int(8) => { quote! { - #data_expr[#offset_lit + 31] as i8 + #data_expr[#offset_expr + 31] as i8 } } SolType::Int(16) => { quote! { - i16::from_be_bytes([#data_expr[#offset_lit + 30], #data_expr[#offset_lit + 31]]) + i16::from_be_bytes([#data_expr[#offset_expr + 30], #data_expr[#offset_expr + 31]]) } } SolType::Int(32) => { quote! { - i32::from_be_bytes(#data_expr[#offset_lit + 28..#offset_lit + 32].try_into().unwrap()) + i32::from_be_bytes(#data_expr[#offset_expr + 28..#offset_expr + 32].try_into().unwrap()) } } SolType::Int(64) => { quote! { - i64::from_be_bytes(#data_expr[#offset_lit + 24..#offset_lit + 32].try_into().unwrap()) + i64::from_be_bytes(#data_expr[#offset_expr + 24..#offset_expr + 32].try_into().unwrap()) } } SolType::Int(128) => { quote! { - i128::from_be_bytes(#data_expr[#offset_lit + 16..#offset_lit + 32].try_into().unwrap()) + i128::from_be_bytes(#data_expr[#offset_expr + 16..#offset_expr + 32].try_into().unwrap()) } } SolType::Int(_) => { quote! { - pvm_contract::I256::from_be_slice(&#data_expr[#offset_lit..#offset_lit + 32]) + pvm_contract::I256::from_be_slice(&#data_expr[#offset_expr..#offset_expr + 32]) } } SolType::Bytes(size) => { let size_lit = *size; quote! {{ let mut bytes = [0u8; #size_lit]; - bytes.copy_from_slice(&#data_expr[#offset_lit..#offset_lit + #size_lit]); + bytes.copy_from_slice(&#data_expr[#offset_expr..#offset_expr + #size_lit]); bytes }} } SolType::DynBytes => { if use_alloc { quote! {{ - let dyn_offset = pvm_contract::U256::from_be_slice(&#data_expr[#offset_lit..#offset_lit + 32]).as_limbs()[0] as usize; - let length = pvm_contract::U256::from_be_slice(&#data_expr[dyn_offset..dyn_offset + 32]).as_limbs()[0] as usize; + let dyn_offset = + pvm_contract::U256::from_be_slice(&#data_expr[#offset_expr..#offset_expr + 32]) + .as_limbs()[0] as usize; + let length = + pvm_contract::U256::from_be_slice(&#data_expr[dyn_offset..dyn_offset + 32]) + .as_limbs()[0] as usize; #data_expr[dyn_offset + 32..dyn_offset + 32 + length].to_vec() }} } else { quote! {{ - let dyn_offset = pvm_contract::U256::from_be_slice(&#data_expr[#offset_lit..#offset_lit + 32]).as_limbs()[0] as usize; - let length = pvm_contract::U256::from_be_slice(&#data_expr[dyn_offset..dyn_offset + 32]).as_limbs()[0] as usize; + let dyn_offset = + pvm_contract::U256::from_be_slice(&#data_expr[#offset_expr..#offset_expr + 32]) + .as_limbs()[0] as usize; + let length = + pvm_contract::U256::from_be_slice(&#data_expr[dyn_offset..dyn_offset + 32]) + .as_limbs()[0] as usize; &#data_expr[dyn_offset + 32..dyn_offset + 32 + length] }} } @@ -110,15 +124,23 @@ pub fn generate_decode( SolType::String => { if use_alloc { quote! {{ - let dyn_offset = pvm_contract::U256::from_be_slice(&#data_expr[#offset_lit..#offset_lit + 32]).as_limbs()[0] as usize; - let length = pvm_contract::U256::from_be_slice(&#data_expr[dyn_offset..dyn_offset + 32]).as_limbs()[0] as usize; + let dyn_offset = + pvm_contract::U256::from_be_slice(&#data_expr[#offset_expr..#offset_expr + 32]) + .as_limbs()[0] as usize; + let length = + pvm_contract::U256::from_be_slice(&#data_expr[dyn_offset..dyn_offset + 32]) + .as_limbs()[0] as usize; let bytes = &#data_expr[dyn_offset + 32..dyn_offset + 32 + length]; alloc::string::String::from_utf8_lossy(bytes).into_owned() }} } else { quote! {{ - let dyn_offset = pvm_contract::U256::from_be_slice(&#data_expr[#offset_lit..#offset_lit + 32]).as_limbs()[0] as usize; - let length = pvm_contract::U256::from_be_slice(&#data_expr[dyn_offset..dyn_offset + 32]).as_limbs()[0] as usize; + let dyn_offset = + pvm_contract::U256::from_be_slice(&#data_expr[#offset_expr..#offset_expr + 32]) + .as_limbs()[0] as usize; + let length = + pvm_contract::U256::from_be_slice(&#data_expr[dyn_offset..dyn_offset + 32]) + .as_limbs()[0] as usize; let bytes = &#data_expr[dyn_offset + 32..dyn_offset + 32 + length]; core::str::from_utf8(bytes).unwrap_or("") }} @@ -126,16 +148,23 @@ pub fn generate_decode( } SolType::Array(inner) => { if use_alloc { - let inner_decode = - generate_decode_array_element(inner, quote!(elem_data), use_alloc); let elem_size = inner.head_size(); + let inner_decode = generate_decode_expr( + inner, + quote!(array_data), + quote!(i * #elem_size), + use_alloc, + ); quote! {{ - let dyn_offset = pvm_contract::U256::from_be_slice(&#data_expr[#offset_lit..#offset_lit + 32]).as_limbs()[0] as usize; - let length = pvm_contract::U256::from_be_slice(&#data_expr[dyn_offset..dyn_offset + 32]).as_limbs()[0] as usize; + let dyn_offset = + pvm_contract::U256::from_be_slice(&#data_expr[#offset_expr..#offset_expr + 32]) + .as_limbs()[0] as usize; + let length = + pvm_contract::U256::from_be_slice(&#data_expr[dyn_offset..dyn_offset + 32]) + .as_limbs()[0] as usize; let array_data = &#data_expr[dyn_offset + 32..]; let mut result = alloc::vec::Vec::with_capacity(length); for i in 0..length { - let elem_data = &array_data[i * #elem_size..]; result.push(#inner_decode); } result @@ -145,41 +174,84 @@ pub fn generate_decode( } } SolType::FixedArray(inner, size) => { - let _size_lit = *size; let elem_size = inner.head_size(); - let elem_decodes: Vec<_> = (0..*size) - .map(|i| { - let elem_offset = offset + i * elem_size; - generate_decode(inner, data_expr.clone(), elem_offset, use_alloc) - }) - .collect(); - quote! { - [#(#elem_decodes),*] + let elem_decodes: Vec<_> = if inner.is_dynamic() { + (0..*size) + .map(|i| { + let elem_offset = i * elem_size; + generate_decode_expr( + inner, + quote!(array_data), + quote!(#elem_offset), + use_alloc, + ) + }) + .collect() + } else { + (0..*size) + .map(|i| { + let elem_offset = add_offset(&offset_expr, i * elem_size); + generate_decode_expr(inner, data_expr.clone(), elem_offset, use_alloc) + }) + .collect() + }; + + if inner.is_dynamic() { + quote! {{ + let dyn_offset = + pvm_contract::U256::from_be_slice(&#data_expr[#offset_expr..#offset_expr + 32]) + .as_limbs()[0] as usize; + let array_data = &#data_expr[dyn_offset..]; + [#(#elem_decodes),*] + }} + } else { + quote! { + [#(#elem_decodes),*] + } } } SolType::Tuple(types) => { - let mut current_offset = offset; - let elem_decodes: Vec<_> = types - .iter() - .map(|t| { - let decode = generate_decode(t, data_expr.clone(), current_offset, use_alloc); - current_offset += t.head_size(); - decode - }) - .collect(); - quote! { - (#(#elem_decodes),*) + let build_tuple = |base_data: TokenStream, base_offset: TokenStream| { + let mut current_offset = 0usize; + let elem_decodes: Vec<_> = types + .iter() + .map(|t| { + let decode = generate_decode_expr( + t, + base_data.clone(), + add_offset(&base_offset, current_offset), + use_alloc, + ); + current_offset += t.head_size(); + decode + }) + .collect(); + quote! { (#(#elem_decodes),*) } + }; + + if ty.is_dynamic() { + let tuple_decode = build_tuple(quote!(tuple_data), quote!(0usize)); + quote! {{ + let dyn_offset = + pvm_contract::U256::from_be_slice(&#data_expr[#offset_expr..#offset_expr + 32]) + .as_limbs()[0] as usize; + let tuple_data = &#data_expr[dyn_offset..]; + #tuple_decode + }} + } else { + build_tuple(data_expr, offset_expr) } } } } -fn generate_decode_array_element( +pub fn generate_decode( ty: &SolType, data_expr: TokenStream, + offset: usize, use_alloc: bool, ) -> TokenStream { - generate_decode(ty, data_expr, 0, use_alloc) + generate_decode_expr(ty, data_expr, quote!(#offset), use_alloc) } pub fn generate_decode_params(types: &[SolType], use_alloc: bool) -> Vec { @@ -197,3 +269,34 @@ pub fn generate_decode_params(types: &[SolType], use_alloc: bool) -> Vec usize { types.iter().map(|t| t.head_size()).sum() } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn decode_dynamic_array_uses_array_base_for_element_offsets() { + let tokens = generate_decode( + &SolType::Array(Box::new(SolType::String)), + quote!(output), + 0, + true, + ) + .to_string(); + assert!(tokens.contains("array_data")); + assert!(!tokens.contains("elem_data")); + } + + #[test] + fn decode_dynamic_tuple_resolves_tuple_offset() { + let tokens = generate_decode( + &SolType::Tuple(vec![SolType::Uint(64), SolType::String]), + quote!(output), + 0, + true, + ) + .to_string(); + assert!(tokens.contains("tuple_data")); + assert!(tokens.contains("dyn_offset")); + } +} diff --git a/crates/pvm-contract-macros/src/codegen/derive_sol_abi.rs b/crates/pvm-contract-macros/src/codegen/derive_sol_abi.rs index 6ef6d0ea..7e5b66ef 100644 --- a/crates/pvm-contract-macros/src/codegen/derive_sol_abi.rs +++ b/crates/pvm-contract-macros/src/codegen/derive_sol_abi.rs @@ -226,7 +226,14 @@ fn expand_named_struct( } fn abi_decode(data: &[u8], offset: usize) -> Self { - let sd = &data[offset..]; + let sd = if ::IS_DYNAMIC { + let dyn_offset = + pvm_contract::U256::from_be_slice(&data[offset..offset + 32]).as_limbs()[0] + as usize; + &data[dyn_offset..] + } else { + &data[offset..] + }; let mut hp = 0usize; Self { #( #field_idents4: { diff --git a/crates/pvm-contract-macros/src/codegen/dispatch.rs b/crates/pvm-contract-macros/src/codegen/dispatch.rs index afefcb4f..351094bc 100644 --- a/crates/pvm-contract-macros/src/codegen/dispatch.rs +++ b/crates/pvm-contract-macros/src/codegen/dispatch.rs @@ -143,16 +143,16 @@ pub fn generate_trait_dispatch_arm( quote! { <#ty as pvm_contract::SolAbi>::SOL_NAME } }).collect(); - // Build min size computation using SolAbi::HEAD_SIZE + // Build min size computation using the tuple slot size for each parameter. let head_size_exprs: Vec = param_types.iter().map(|ty| { - quote! { <#ty as pvm_contract::SolAbi>::HEAD_SIZE } + quote! { <#ty as pvm_contract::SolAbi>::SLOT_SIZE } }).collect(); // Build decode statements using SolAbi::abi_decode let decode_statements: Vec = param_names.iter().zip(param_types.iter()).map(|(name, ty)| { quote! { let #name = <#ty as pvm_contract::SolAbi>::abi_decode(input, __offset); - __offset += <#ty as pvm_contract::SolAbi>::HEAD_SIZE; + __offset += <#ty as pvm_contract::SolAbi>::SLOT_SIZE; } }).collect(); @@ -217,32 +217,18 @@ fn generate_trait_result_handling( } } // Has return type, returns Result - (Some(ret_ty), true) => { + (Some(_ret_ty), true) => { quote! { return #mod_name::#fn_name(#(#call_args),*).map(|result| { - let mut __ret_buf = alloc::vec::Vec::new(); - if <#ret_ty as pvm_contract::SolAbi>::IS_DYNAMIC { - let mut __w = [0u8; 32]; - __w[24..32].copy_from_slice(&(32u64).to_be_bytes()); - __ret_buf.extend_from_slice(&__w); - } - <#ret_ty as pvm_contract::SolAbi>::abi_encode(&result, &mut __ret_buf); - Some(__ret_buf) + Some(pvm_contract::abi::encode_return_value(&result)) }).map_err(|e| e.as_ref().to_vec()); } } // Has return type, no Result - (Some(ret_ty), false) => { + (Some(_ret_ty), false) => { quote! { let result = #mod_name::#fn_name(#(#call_args),*); - let mut __ret_buf = alloc::vec::Vec::new(); - if <#ret_ty as pvm_contract::SolAbi>::IS_DYNAMIC { - let mut __w = [0u8; 32]; - __w[24..32].copy_from_slice(&(32u64).to_be_bytes()); - __ret_buf.extend_from_slice(&__w); - } - <#ret_ty as pvm_contract::SolAbi>::abi_encode(&result, &mut __ret_buf); - return Ok(Some(__ret_buf)); + return Ok(Some(pvm_contract::abi::encode_return_value(&result))); } } } diff --git a/crates/pvm-contract-macros/src/codegen/encode.rs b/crates/pvm-contract-macros/src/codegen/encode.rs index 9d6b0a1e..1e767dc7 100644 --- a/crates/pvm-contract-macros/src/codegen/encode.rs +++ b/crates/pvm-contract-macros/src/codegen/encode.rs @@ -3,6 +3,88 @@ use quote::quote; use crate::signature::SolType; +fn generate_encode_sequence( + value_exprs: &[TokenStream], + types: &[SolType], + use_alloc: bool, +) -> TokenStream { + let has_dynamic = types.iter().any(|t| t.is_dynamic()); + let head_size = types.iter().map(|t| t.head_size()).sum::(); + + if !has_dynamic { + let encodes: Vec<_> = value_exprs + .iter() + .zip(types.iter()) + .map(|(value_expr, ty)| generate_encode(ty, value_expr.clone(), use_alloc)) + .collect(); + let slot_sizes: Vec<_> = types.iter().map(|ty| ty.head_size()).collect(); + + if use_alloc { + quote! {{ + let mut out = alloc::vec::Vec::with_capacity(#head_size); + #(out.extend_from_slice(&#encodes);)* + out + }} + } else { + quote! {{ + let mut out = [0u8; #head_size]; + let mut offset = 0usize; + #( + let encoded = #encodes; + out[offset..offset + #slot_sizes].copy_from_slice(&encoded); + offset += #slot_sizes; + )* + out + }} + } + } else { + if !use_alloc { + panic!("Dynamic ABI encoding requires alloc"); + } + + let mut writes = Vec::new(); + let mut offset = 0usize; + for (value_expr, ty) in value_exprs.iter().zip(types.iter()) { + let slot_size = ty.head_size(); + let end = offset + slot_size; + let encoded = generate_encode(ty, value_expr.clone(), true); + + if ty.is_dynamic() { + writes.push(quote! { + { + let __dyn_offset = (#head_size + __tail.len()) as u64; + let mut __off = [0u8; 32]; + __off[24..32].copy_from_slice(&__dyn_offset.to_be_bytes()); + __head[#offset..#offset + 32].copy_from_slice(&__off); + + let __enc = #encoded; + __tail.extend_from_slice(&__enc); + } + }); + } else { + writes.push(quote! { + { + let __enc = #encoded; + __head[#offset..#end].copy_from_slice(&__enc); + } + }); + } + + offset = end; + } + + quote! {{ + let mut __head = alloc::vec![0u8; #head_size]; + let mut __tail = alloc::vec::Vec::new(); + #(#writes)* + let mut out = alloc::vec::Vec::with_capacity(#head_size + __tail.len()); + out.extend_from_slice(&__head); + out.extend_from_slice(&__tail); + out + }} + } +} + pub fn generate_encode(ty: &SolType, value_expr: TokenStream, use_alloc: bool) -> TokenStream { match ty { SolType::Address => { @@ -113,216 +195,168 @@ pub fn generate_encode(ty: &SolType, value_expr: TokenStream, use_alloc: bool) - SolType::String => { if use_alloc { quote! {{ - // Solidity string encoding: offset (32) + length (32) + data (padded to 32) - let s: &str = #value_expr.as_str(); - let len = s.len(); - let padded_len = (len + 31) / 32 * 32; - let mut out = alloc::vec::Vec::with_capacity(64 + padded_len); - - // Encode offset - out.extend_from_slice(&[0u8; 31]); - out.push(32); - let mut len_bytes = [0u8; 32]; - len_bytes[24..32].copy_from_slice(&(len as u64).to_be_bytes()); - // Encode length - out.extend_from_slice(&len_bytes); - // Encode data + padding - out.extend_from_slice(s.as_bytes()); - out.resize(64 + padded_len, 0); + let __string: &str = #value_expr.as_str(); + let __len = __string.len(); + let __padded_len = (__len + 31) / 32 * 32; + let mut out = alloc::vec::Vec::with_capacity(32 + __padded_len); + + let mut __len_bytes = [0u8; 32]; + __len_bytes[24..32].copy_from_slice(&(__len as u64).to_be_bytes()); + out.extend_from_slice(&__len_bytes); + out.extend_from_slice(__string.as_bytes()); + out.resize(32 + __padded_len, 0); out }} } else { panic!("String encoding requires alloc"); } } - SolType::DynBytes | SolType::Array(_) => { - panic!("`DynBytes` & `Array` types require special handling in tuple encoding"); - } - SolType::FixedArray(inner, size) => { - let size_lit = *size; - let inner_encodes: Vec<_> = (0..*size) - .map(|i| { - let idx = i; - generate_encode(inner, quote!(#value_expr[#idx]), use_alloc) - }) - .collect(); + SolType::DynBytes => { if use_alloc { quote! {{ - let mut out = alloc::vec::Vec::with_capacity(#size_lit * 32); - #(out.extend_from_slice(&#inner_encodes);)* + let __bytes: &[u8] = &#value_expr; + let __len = __bytes.len(); + let __padded_len = (__len + 31) / 32 * 32; + let mut out = alloc::vec::Vec::with_capacity(32 + __padded_len); + + let mut __len_bytes = [0u8; 32]; + __len_bytes[24..32].copy_from_slice(&(__len as u64).to_be_bytes()); + out.extend_from_slice(&__len_bytes); + out.extend_from_slice(__bytes); + out.resize(32 + __padded_len, 0); out }} } else { - quote! {{ - let mut out = [0u8; #size_lit * 32]; - let mut offset = 0; - #( - out[offset..offset + 32].copy_from_slice(&#inner_encodes); - offset += 32; - )* - out - }} + panic!("Dynamic bytes encoding requires alloc"); } } - SolType::Tuple(types) => { - if types.iter().all(|t| !t.is_dynamic()) { - let encodes: Vec<_> = types - .iter() - .enumerate() - .map(|(i, t)| { - let idx = syn::Index::from(i); - generate_encode(t, quote!(#value_expr.#idx), use_alloc) - }) - .collect(); - let total_size = types.iter().map(|t| t.head_size()).sum::(); - if use_alloc { - quote! {{ - let mut out = alloc::vec::Vec::with_capacity(#total_size); - #(out.extend_from_slice(&#encodes);)* - out - }} - } else { - quote! {{ - let mut out = [0u8; #total_size]; - let mut offset = 0; - #( - let encoded = #encodes; - out[offset..offset + 32].copy_from_slice(&encoded); - offset += 32; - )* - out - }} - } - } else { - panic!("Dynamic tuple encoding not yet implemented"); + SolType::Array(inner) => { + if !use_alloc { + panic!("Dynamic arrays require alloc"); } - } - } -} -/// Encode a sequence of named parameters into ABI calldata. -/// Generates code that extends a pre-existing `calldata: Vec` variable. -/// Handles both all-static sequences (simple concatenation) and mixed -/// static/dynamic sequences (proper head/tail encoding). -pub fn generate_encode_params( - names: &[syn::Ident], - types: &[SolType], -) -> TokenStream { - let has_dynamic = types.iter().any(|t| t.is_dynamic()); - - if !has_dynamic { - let encodes: Vec = names - .iter() - .zip(types.iter()) - .map(|(name, ty)| { - let enc = generate_encode(ty, quote!(#name), true); - quote! { calldata.extend_from_slice(&#enc); } - }) - .collect(); - quote! { #(#encodes)* } - } else { - let n_params = types.len(); - let head_size = n_params * 32; + let inner_slot_size = inner.head_size(); + if inner.is_dynamic() { + let inner_encode = generate_encode(inner, quote!(__item), true); + quote! {{ + let __array = &#value_expr; + let __len = __array.len(); + let __array_head_size = __len * #inner_slot_size; + let mut out = alloc::vec::Vec::new(); - let mut writes = Vec::new(); - for (i, (name, ty)) in names.iter().zip(types.iter()).enumerate() { - let offset = i * 32; - let end = offset + 32; + let mut __len_bytes = [0u8; 32]; + __len_bytes[24..32].copy_from_slice(&(__len as u64).to_be_bytes()); + out.extend_from_slice(&__len_bytes); - if ty.is_dynamic() { - match ty { - SolType::String => { - writes.push(quote! { - { - let __dyn_offset = (#head_size + __tail.len()) as u64; - let mut __off = [0u8; 32]; - __off[24..32].copy_from_slice(&__dyn_offset.to_be_bytes()); - __head[#offset..#end].copy_from_slice(&__off); + let mut __head = alloc::vec![0u8; __array_head_size]; + let mut __tail = alloc::vec::Vec::new(); + for (i, __item) in __array.iter().enumerate() { + let __offset = i * #inner_slot_size; + let __dyn_offset = (__array_head_size + __tail.len()) as u64; + let mut __off = [0u8; 32]; + __off[24..32].copy_from_slice(&__dyn_offset.to_be_bytes()); + __head[__offset..__offset + 32].copy_from_slice(&__off); - let __s: &str = #name.as_str(); - let __len = __s.len(); - let __padded_len = (__len + 31) / 32 * 32; - let mut __len_bytes = [0u8; 32]; - __len_bytes[24..32].copy_from_slice(&(__len as u64).to_be_bytes()); - __tail.extend_from_slice(&__len_bytes); - __tail.extend_from_slice(__s.as_bytes()); - __tail.resize(__tail.len() + __padded_len - __len, 0); - } - }); + let __enc = #inner_encode; + __tail.extend_from_slice(&__enc); } - SolType::DynBytes => { - writes.push(quote! { - { - let __dyn_offset = (#head_size + __tail.len()) as u64; - let mut __off = [0u8; 32]; - __off[24..32].copy_from_slice(&__dyn_offset.to_be_bytes()); - __head[#offset..#end].copy_from_slice(&__off); - let __data: &[u8] = &#name; - let __len = __data.len(); - let __padded_len = (__len + 31) / 32 * 32; - let mut __len_bytes = [0u8; 32]; - __len_bytes[24..32].copy_from_slice(&(__len as u64).to_be_bytes()); - __tail.extend_from_slice(&__len_bytes); - __tail.extend_from_slice(__data); - __tail.resize(__tail.len() + __padded_len - __len, 0); - } - }); - } - _ => panic!("Unsupported dynamic type in cross-contract call encoding"), - } + out.extend_from_slice(&__head); + out.extend_from_slice(&__tail); + out + }} } else { - let enc = generate_encode(ty, quote!(#name), true); - writes.push(quote! { - __head[#offset..#end].copy_from_slice(&#enc); - }); - } - } + let inner_encode = generate_encode(inner, quote!(__item), true); + quote! {{ + let __array = &#value_expr; + let __len = __array.len(); + let mut out = alloc::vec::Vec::new(); - quote! { - { - let mut __head = alloc::vec![0u8; #head_size]; - let mut __tail = alloc::vec::Vec::new(); - #(#writes)* - calldata.extend_from_slice(&__head); - calldata.extend_from_slice(&__tail); + let mut __len_bytes = [0u8; 32]; + __len_bytes[24..32].copy_from_slice(&(__len as u64).to_be_bytes()); + out.extend_from_slice(&__len_bytes); + + for __item in __array.iter() { + let __enc = #inner_encode; + out.extend_from_slice(&__enc); + } + out + }} } } + SolType::FixedArray(inner, size) => { + let element_types = vec![(**inner).clone(); *size]; + let element_exprs: Vec<_> = (0..*size) + .map(|i| { + let idx = syn::Index::from(i); + quote!(#value_expr[#idx]) + }) + .collect(); + generate_encode_sequence(&element_exprs, &element_types, use_alloc) + } + SolType::Tuple(types) => { + let element_exprs: Vec<_> = types + .iter() + .enumerate() + .map(|(i, _)| { + let idx = syn::Index::from(i); + quote!(#value_expr.#idx) + }) + .collect(); + generate_encode_sequence(&element_exprs, types, use_alloc) + } + } +} + +/// Encode a sequence of named parameters into ABI calldata. +/// Generates code that extends a pre-existing `calldata: Vec` variable. +pub fn generate_encode_params(names: &[syn::Ident], types: &[SolType]) -> TokenStream { + let value_exprs: Vec<_> = names.iter().map(|name| quote!(#name)).collect(); + let encoded = generate_encode_sequence(&value_exprs, types, true); + quote! { + calldata.extend_from_slice(&#encoded); } } /// Encode parameters using the SolAbi trait (runtime dynamic detection). /// Used when types aren't fully resolved at macro expansion time. /// Generates code that extends a pre-existing `calldata: Vec` variable. -pub fn generate_encode_params_trait( - names: &[syn::Ident], - types: &[syn::Type], -) -> TokenStream { - let n_params = types.len(); - let head_size = n_params * 32; +pub fn generate_encode_params_trait(names: &[syn::Ident], types: &[syn::Type]) -> TokenStream { + let head_size_exprs: Vec<_> = types + .iter() + .map(|ty| quote! { <#ty as pvm_contract::SolAbi>::SLOT_SIZE }) + .collect(); - let mut writes = Vec::new(); - for (i, (name, ty)) in names.iter().zip(types.iter()).enumerate() { - let offset = i * 32; - let end = offset + 32; - writes.push(quote! { - if <#ty as pvm_contract::SolAbi>::IS_DYNAMIC { - let __dyn_offset = (#head_size + __tail.len()) as u64; - let mut __off = [0u8; 32]; - __off[24..32].copy_from_slice(&__dyn_offset.to_be_bytes()); - __head[#offset..#end].copy_from_slice(&__off); - <#ty as pvm_contract::SolAbi>::abi_encode(&#name, &mut __tail); - } else { - let mut __enc = alloc::vec::Vec::new(); - <#ty as pvm_contract::SolAbi>::abi_encode(&#name, &mut __enc); - __head[#offset..#end].copy_from_slice(&__enc); + let writes: Vec<_> = names + .iter() + .zip(types.iter()) + .map(|(name, ty)| { + quote! { + { + let __slot_size: usize = <#ty as pvm_contract::SolAbi>::SLOT_SIZE; + if <#ty as pvm_contract::SolAbi>::IS_DYNAMIC { + let __dyn_offset = (__head_size + __tail.len()) as u64; + let mut __off = [0u8; 32]; + __off[24..32].copy_from_slice(&__dyn_offset.to_be_bytes()); + __head[__offset..__offset + 32].copy_from_slice(&__off); + <#ty as pvm_contract::SolAbi>::abi_encode(&#name, &mut __tail); + } else { + let mut __enc = alloc::vec::Vec::new(); + <#ty as pvm_contract::SolAbi>::abi_encode(&#name, &mut __enc); + __head[__offset..__offset + __slot_size].copy_from_slice(&__enc); + } + __offset += __slot_size; + } } - }); - } + }) + .collect(); + quote! { { - let mut __head = alloc::vec![0u8; #head_size]; + let __head_size: usize = 0usize #(+ #head_size_exprs)*; + let mut __head = alloc::vec![0u8; __head_size]; let mut __tail = alloc::vec::Vec::new(); + let mut __offset = 0usize; #(#writes)* calldata.extend_from_slice(&__head); calldata.extend_from_slice(&__tail); @@ -335,42 +369,53 @@ pub fn generate_encode_return(types: &[SolType], use_alloc: bool) -> TokenStream return quote! { &[] }; } - if types.len() == 1 { - let encode = generate_encode(&types[0], quote!(result), use_alloc); - if use_alloc { - return quote! { #encode.to_vec() }; - } else { - return quote! { &#encode }; - } + let value_exprs: Vec<_> = if types.len() == 1 { + vec![quote!(result)] + } else { + types + .iter() + .enumerate() + .map(|(i, _)| { + let idx = syn::Index::from(i); + quote!(result.#idx) + }) + .collect() + }; + + let encoded = generate_encode_sequence(&value_exprs, types, use_alloc); + if use_alloc { + quote! { #encoded } + } else { + quote! { &#encoded } } +} - let encodes: Vec<_> = types - .iter() - .enumerate() - .map(|(i, ty)| { - let idx = syn::Index::from(i); - generate_encode(ty, quote!(result.#idx), use_alloc) - }) - .collect(); +#[cfg(test)] +mod tests { + use super::*; + use quote::format_ident; - let total_size: usize = types.iter().map(|t| t.head_size()).sum(); + #[test] + fn encode_return_uses_head_tail_for_mixed_outputs() { + let tokens = + generate_encode_return(&[SolType::Uint(64), SolType::String], true).to_string(); + assert!(tokens.contains("__head")); + assert!(tokens.contains("__tail")); + } - if use_alloc { - quote! {{ - let mut out = alloc::vec::Vec::with_capacity(#total_size); - #(out.extend_from_slice(&#encodes);)* - out - }} - } else { - quote! {{ - let mut out = [0u8; #total_size]; - let mut offset = 0; - #( - let encoded = #encodes; - out[offset..offset + 32].copy_from_slice(&encoded); - offset += 32; - )* - &out - }} + #[test] + fn encode_params_supports_tuple_and_dynamic_array_inputs() { + let params = vec![format_ident!("player"), format_ident!("ghosts")]; + let types = vec![ + SolType::Tuple(vec![SolType::String, SolType::Uint(64)]), + SolType::Array(Box::new(SolType::Tuple(vec![ + SolType::String, + SolType::Uint(64), + ]))), + ]; + + let tokens = generate_encode_params(¶ms, &types).to_string(); + assert!(tokens.contains("__array")); + assert!(tokens.contains("__tail")); } } diff --git a/crates/pvm-contract-macros/src/codegen/mod.rs b/crates/pvm-contract-macros/src/codegen/mod.rs index 06a43d98..749c79e7 100644 --- a/crates/pvm-contract-macros/src/codegen/mod.rs +++ b/crates/pvm-contract-macros/src/codegen/mod.rs @@ -96,24 +96,13 @@ pub(super) fn generate_cdm_reference(cdm_name: &str) -> TokenStream { // String data calldata[4 + 64..4 + 64 + name_len].copy_from_slice(cdm_name.as_bytes()); - // Output buffer: registry returns Option
as tuple(bool isSome, address value) - // ABI-encoded: 32 bytes for isSome + 32 bytes for address = 64 bytes - let mut output_buf = [0u8; 64]; - let mut output_ref: &mut [u8] = &mut output_buf[..]; - - let result = ::call_evm( + let result = pvm_contract::call::call_evm_collect( pvm_contract::CallFlags::ALLOW_REENTRY, - &__CDM_REGISTRY_ADDR, - u64::MAX, - &[0u8; 32], - &calldata, - Some(&mut output_ref), + &__CDM_REGISTRY_ADDR, u64::MAX, &[0u8; 32], &calldata, ); match result { - Ok(()) => { - let written = output_ref.len(); - let output = &output_buf[..written]; + Ok(output) => { // First word (0..32) is isSome bool, second word (32..64) is the address // Address is 20 bytes right-aligned in the second 32-byte word let is_some = output[31] != 0; @@ -136,28 +125,16 @@ pub(super) fn generate_cdm_reference(cdm_name: &str) -> TokenStream { /// decode expression, and whether there is output — for cross-contract call methods. pub(super) fn generate_resolved_return_parts( outputs: &[SolType], -) -> (TokenStream, TokenStream, TokenStream, bool) { +) -> (TokenStream, TokenStream, bool) { match outputs { - [] => ( - quote! { () }, - quote! { let mut output_buf = [0u8; 0]; }, - quote! { () }, - false, - ), + [] => (quote! { () }, quote! { () }, false), [one] => { let rt = one.rust_type(true); - let output_size = one.head_size(); let decode = generate_decode(one, quote!(output), 0, true); - ( - quote! { #rt }, - quote! { let mut output_buf = [0u8; #output_size]; }, - decode, - true, - ) + (quote! { #rt }, decode, true) } many => { let tys: Vec = many.iter().map(|t| t.rust_type(true)).collect(); - let output_size: usize = many.iter().map(|t| t.head_size()).sum(); let mut offset = 0usize; let decs: Vec = many .iter() @@ -167,12 +144,7 @@ pub(super) fn generate_resolved_return_parts( d }) .collect(); - ( - quote! { (#(#tys),*) }, - quote! { let mut output_buf = [0u8; #output_size]; }, - quote! { (#(#decs),*) }, - true, - ) + (quote! { (#(#tys),*) }, quote! { (#(#decs),*) }, true) } } } diff --git a/crates/pvm_contract/src/abi.rs b/crates/pvm_contract/src/abi.rs index 143e7b05..ffe4c1eb 100644 --- a/crates/pvm_contract/src/abi.rs +++ b/crates/pvm_contract/src/abi.rs @@ -21,6 +21,12 @@ pub trait SolAbi: Sized { /// Size of the head portion in the ABI encoding (always 32 for non-tuple types). const HEAD_SIZE: usize = 32; + /// Size of this value's slot when it appears in an enclosing tuple. + /// + /// Static types are inlined into the tuple head, while dynamic types occupy + /// a single 32-byte offset word. + const SLOT_SIZE: usize = if Self::IS_DYNAMIC { 32 } else { Self::HEAD_SIZE }; + /// Whether this is a dynamic type (string, bytes, dynamic arrays). const IS_DYNAMIC: bool = false; @@ -31,6 +37,22 @@ pub trait SolAbi: Sized { fn abi_decode(data: &[u8], offset: usize) -> Self; } +/// ABI-encode a single function return value. +/// +/// Solidity return data is encoded as the ABI tuple of all outputs. For a +/// single dynamic output, that means emitting a top-level offset word before +/// the value body. +pub fn encode_return_value(value: &T) -> Vec { + let mut buf = Vec::new(); + if T::IS_DYNAMIC { + let mut offset = [0u8; 32]; + offset[24..32].copy_from_slice(&(T::SLOT_SIZE as u64).to_be_bytes()); + buf.extend_from_slice(&offset); + } + value.abi_encode(&mut buf); + buf +} + // -- bool -- impl SolAbi for bool { @@ -388,6 +410,17 @@ impl SolAbi for Option { } fn abi_decode(data: &[u8], offset: usize) -> Self { + if T::IS_DYNAMIC { + let dyn_offset = + crate::U256::from_be_slice(&data[offset..offset + 32]).as_limbs()[0] as usize; + let body = &data[dyn_offset..]; + let is_some = bool::abi_decode(body, 0); + if !is_some { + return None; + } + return Some(T::abi_decode(body, 32)); + } + let is_some = bool::abi_decode(data, offset); if !is_some { return None; @@ -418,3 +451,61 @@ pub fn compute_selector(name: &str, param_type_names: &[&str]) -> [u8; 4] { hasher.finalize(&mut output); [output[0], output[1], output[2], output[3]] } + +#[cfg(test)] +mod tests { + use super::{SolAbi, encode_return_value}; + use crate as pvm_contract; + + #[derive(pvm_contract_macros::SolAbi, Debug, PartialEq, Eq)] + struct StaticPair { + left: u64, + right: u64, + } + + #[derive(pvm_contract_macros::SolAbi, Debug, PartialEq, Eq)] + struct DynamicPair { + id: u64, + label: alloc::string::String, + } + + #[test] + fn encode_single_dynamic_return_prefixes_top_level_offset() { + let encoded = encode_return_value(&alloc::string::String::from("hi")); + let mut expected_offset = [0u8; 32]; + expected_offset[24..32].copy_from_slice(&32u64.to_be_bytes()); + assert_eq!(&encoded[..32], &expected_offset); + assert_eq!( + alloc::string::String::abi_decode(&encoded, 0), + alloc::string::String::from("hi") + ); + } + + #[test] + fn dynamic_struct_decode_uses_top_level_offset() { + let value = DynamicPair { + id: 7, + label: alloc::string::String::from("arena"), + }; + let encoded = encode_return_value(&value); + assert_eq!(DynamicPair::abi_decode(&encoded, 0), value); + } + + #[test] + fn dynamic_option_decode_uses_top_level_offset() { + let value = Some(alloc::string::String::from("ghost")); + let encoded = encode_return_value(&value); + assert_eq!( + Option::::abi_decode(&encoded, 0), + value + ); + } + + #[test] + fn static_types_keep_in_place_encoding() { + let value = StaticPair { left: 1, right: 2 }; + let encoded = encode_return_value(&value); + assert_eq!(encoded.len(), StaticPair::HEAD_SIZE); + assert_eq!(StaticPair::abi_decode(&encoded, 0), value); + } +} diff --git a/crates/pvm_contract/src/call.rs b/crates/pvm_contract/src/call.rs index da52193c..575ffc8a 100644 --- a/crates/pvm_contract/src/call.rs +++ b/crates/pvm_contract/src/call.rs @@ -1,3 +1,11 @@ +extern crate alloc; + +use alloc::vec::Vec; + +use crate::CallFlags; +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +use crate::{api, HostFn}; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CallError { Reverted, @@ -20,3 +28,43 @@ impl From for CallError { } } } + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +pub fn last_return_data() -> Vec { + let len = ::return_data_size() as usize; + let mut output = alloc::vec![0u8; len]; + if len > 0 { + let mut output_ref: &mut [u8] = &mut output[..]; + ::return_data_copy(&mut output_ref, 0); + } + output +} + +#[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))] +pub fn last_return_data() -> Vec { + panic!("call return data is only available inside contracts"); +} + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +pub fn call_evm_collect( + flags: CallFlags, + callee: &[u8; 20], + gas: u64, + value: &[u8; 32], + input_data: &[u8], +) -> CallResult> { + ::call_evm(flags, callee, gas, value, input_data, None) + .map_err(CallError::from)?; + Ok(last_return_data()) +} + +#[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))] +pub fn call_evm_collect( + _flags: CallFlags, + _callee: &[u8; 20], + _gas: u64, + _value: &[u8; 32], + _input_data: &[u8], +) -> CallResult> { + panic!("cross-contract calls are only available inside contracts"); +}