From 6dc604ba5ffbd44eb6314f84ff0e0ba97bbac1c5 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 7 Apr 2026 16:51:07 -0400 Subject: [PATCH 1/2] Fix ABI handling for dynamic cross-contract calls --- .../src/codegen/abi_import.rs | 227 ++++++--- .../src/codegen/contract.rs | 237 +++++---- .../pvm-contract-macros/src/codegen/decode.rs | 211 ++++++-- .../src/codegen/derive_sol_abi.rs | 124 +++-- .../src/codegen/dispatch.rs | 65 ++- .../pvm-contract-macros/src/codegen/encode.rs | 461 ++++++++++-------- crates/pvm-contract-macros/src/codegen/mod.rs | 50 +- crates/pvm_contract/src/abi.rs | 95 ++++ crates/pvm_contract/src/call.rs | 48 ++ 9 files changed, 976 insertions(+), 542 deletions(-) diff --git a/crates/pvm-contract-macros/src/codegen/abi_import.rs b/crates/pvm-contract-macros/src/codegen/abi_import.rs index 297189d7..8d326efc 100644 --- a/crates/pvm-contract-macros/src/codegen/abi_import.rs +++ b/crates/pvm-contract-macros/src/codegen/abi_import.rs @@ -7,9 +7,9 @@ use crate::codegen::decode::generate_decode; use crate::codegen::encode::generate_encode_params; use crate::codegen::generate_cdm_reference; use crate::codegen::generate_resolved_return_parts; -use crate::signature::compute_selector; use crate::signature::FunctionSignature; use crate::signature::SolType; +use crate::signature::compute_selector; use crate::solidity::to_snake_case; // --------------------------------------------------------------------------- @@ -92,9 +92,7 @@ fn parse_abi_param(val: &serde_json::Value) -> Result { fn parse_abi_json(json_str: &str) -> Result, String> { let value: serde_json::Value = serde_json::from_str(json_str).map_err(|e| format!("Invalid ABI JSON: {}", e))?; - let arr = value - .as_array() - .ok_or("ABI JSON must be an array")?; + let arr = value.as_array().ok_or("ABI JSON must be an array")?; let mut functions = Vec::new(); for entry in arr { @@ -110,12 +108,20 @@ fn parse_abi_json(json_str: &str) -> Result, String> { let inputs = entry .get("inputs") .and_then(|v| v.as_array()) - .map(|arr| arr.iter().map(parse_abi_param).collect::, _>>()) + .map(|arr| { + arr.iter() + .map(parse_abi_param) + .collect::, _>>() + }) .unwrap_or(Ok(vec![]))?; let outputs = entry .get("outputs") .and_then(|v| v.as_array()) - .map(|arr| arr.iter().map(parse_abi_param).collect::, _>>()) + .map(|arr| { + arr.iter() + .map(parse_abi_param) + .collect::, _>>() + }) .unwrap_or(Ok(vec![]))?; functions.push(AbiFunction { name, @@ -253,7 +259,9 @@ fn has_named_components(outputs: &[AbiParam]) -> bool { _ => return false, }; // Check that at least one component has a non-empty, non-positional name - comps.iter().any(|c| !c.name.is_empty() && !c.name.starts_with("_field")) + comps + .iter() + .any(|c| !c.name.is_empty() && !c.name.starts_with("_field")) } // --------------------------------------------------------------------------- @@ -285,9 +293,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 +321,11 @@ 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 +345,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 +361,27 @@ 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)) } @@ -413,7 +405,10 @@ fn generate_return_struct(func: &AbiFunction) -> Result, Str .map(|c| format_ident!("{}", to_snake_case(&c.name))) .collect(); - let field_types: Vec = comp_types.iter().map(|t: &SolType| t.rust_type(true)).collect(); + let field_types: Vec = comp_types + .iter() + .map(|t: &SolType| t.rust_type(true)) + .collect(); let result: TokenStream = quote! { pub struct #struct_name { @@ -439,9 +434,8 @@ pub fn expand_abi_import(args: AbiImportArgs) -> syn::Result { ) })?; - let functions = parse_abi_json(&json_str).map_err(|e| { - syn::Error::new(proc_macro2::Span::call_site(), e) - })?; + let functions = parse_abi_json(&json_str) + .map_err(|e| syn::Error::new(proc_macro2::Span::call_site(), e))?; // Generate return structs for functions with named tuple outputs let mut return_structs = Vec::new(); @@ -458,9 +452,8 @@ pub fn expand_abi_import(args: AbiImportArgs) -> syn::Result { // Generate methods let mut methods = Vec::new(); for func in &functions { - let method = generate_abi_reference_method(func).map_err(|e| { - syn::Error::new(proc_macro2::Span::call_site(), e) - })?; + let method = generate_abi_reference_method(func) + .map_err(|e| syn::Error::new(proc_macro2::Span::call_site(), e))?; methods.push(method); } @@ -527,9 +520,21 @@ mod tests { #[test] fn test_parse_tuple_type() { let comps = vec![ - AbiParam { name: "id".into(), type_str: "bytes32".into(), components: None }, - AbiParam { name: "status".into(), type_str: "uint8".into(), components: None }, - AbiParam { name: "proposer".into(), type_str: "address".into(), components: None }, + AbiParam { + name: "id".into(), + type_str: "bytes32".into(), + components: None, + }, + AbiParam { + name: "status".into(), + type_str: "uint8".into(), + components: None, + }, + AbiParam { + name: "proposer".into(), + type_str: "address".into(), + components: None, + }, ]; let result = parse_abi_type("tuple", Some(&comps)).unwrap(); assert_eq!( @@ -567,8 +572,16 @@ mod tests { name: "".into(), type_str: "tuple".into(), components: Some(vec![ - AbiParam { name: "id".into(), type_str: "bytes32".into(), components: None }, - AbiParam { name: "status".into(), type_str: "uint8".into(), components: None }, + AbiParam { + name: "id".into(), + type_str: "bytes32".into(), + components: None, + }, + AbiParam { + name: "status".into(), + type_str: "uint8".into(), + components: None, + }, ]), }]; assert!(has_named_components(&outputs)); @@ -583,9 +596,91 @@ mod tests { // Multiple outputs (not a named struct case) let outputs = vec![ - AbiParam { name: "".into(), type_str: "uint64".into(), components: None }, - AbiParam { name: "".into(), type_str: "address".into(), components: None }, + AbiParam { + name: "".into(), + type_str: "uint64".into(), + components: None, + }, + AbiParam { + name: "".into(), + type_str: "address".into(), + components: None, + }, ]; 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..f10a12b4 100644 --- a/crates/pvm-contract-macros/src/codegen/contract.rs +++ b/crates/pvm-contract-macros/src/codegen/contract.rs @@ -1,13 +1,13 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::{parse::Parse, parse::ParseStream, Attribute, Ident, ItemMod, LitInt, LitStr, Token}; +use syn::{Attribute, Ident, ItemMod, LitInt, LitStr, Token, parse::Parse, parse::ParseStream}; use super::decode::{calculate_min_input_size, generate_decode_params}; -use super::dispatch::{generate_dispatch_arm, generate_trait_dispatch_arm, MethodInfo}; +use super::dispatch::{MethodInfo, generate_dispatch_arm, generate_trait_dispatch_arm}; use super::encode::{generate_encode_params, generate_encode_params_trait}; use super::{generate_cdm_reference, generate_resolved_return_parts, unwrap_option_inner}; -use crate::signature::{compute_selector, FunctionSignature, SolType}; -use crate::solidity::{parse_solidity_interface, to_snake_case, SolInterface}; +use crate::signature::{FunctionSignature, SolType, compute_selector}; +use crate::solidity::{SolInterface, parse_solidity_interface, to_snake_case}; pub struct ContractArgs { pub no_alloc: bool, @@ -243,29 +243,46 @@ fn parse_contract( for item in &content.1 { if let syn::Item::Fn(func) = item { if has_pvm_attr(&func.attrs, "constructor") { - let param_names: Vec = func.sig.inputs.iter().filter_map(|arg| { - if let syn::FnArg::Typed(pat_type) = arg { - if let syn::Pat::Ident(pat_ident) = &*pat_type.pat { - return Some(pat_ident.ident.clone()); + let param_names: Vec = func + .sig + .inputs + .iter() + .filter_map(|arg| { + if let syn::FnArg::Typed(pat_type) = arg { + if let syn::Pat::Ident(pat_ident) = &*pat_type.pat { + return Some(pat_ident.ident.clone()); + } } - } - None - }).collect(); - let raw_param_types: Vec = func.sig.inputs.iter().filter_map(|arg| { - if let syn::FnArg::Typed(pat_type) = arg { - Some((*pat_type.ty).clone()) - } else { - None - } - }).collect(); - let param_types: Vec = func.sig.inputs.iter().filter_map(|arg| { - if let syn::FnArg::Typed(pat_type) = arg { - SolType::from_rust_type(&pat_type.ty) - } else { None - } - }).collect(); - let all_resolved = raw_param_types.iter().all(|ty| SolType::from_rust_type(ty).is_some()); + }) + .collect(); + let raw_param_types: Vec = func + .sig + .inputs + .iter() + .filter_map(|arg| { + if let syn::FnArg::Typed(pat_type) = arg { + Some((*pat_type.ty).clone()) + } else { + None + } + }) + .collect(); + let param_types: Vec = func + .sig + .inputs + .iter() + .filter_map(|arg| { + if let syn::FnArg::Typed(pat_type) = arg { + SolType::from_rust_type(&pat_type.ty) + } else { + None + } + }) + .collect(); + let all_resolved = raw_param_types + .iter() + .all(|ty| SolType::from_rust_type(ty).is_some()); constructor = Some(ConstructorInfo { fn_name: func.sig.ident.clone(), param_names, @@ -352,7 +369,9 @@ fn parse_contract( let (all_inputs_resolved, return_resolved) = if is_sol_interface { (true, true) } else { - let inputs_ok = param_types.iter().all(|ty| SolType::from_rust_type(ty).is_some()); + let inputs_ok = param_types + .iter() + .all(|ty| SolType::from_rust_type(ty).is_some()); let return_ok = match &return_type { None => true, Some(ty) => SolType::from_rust_type(ty).is_some(), @@ -463,8 +482,11 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result = param_names.iter().zip(decodes.iter()) - .map(|(name, decode)| quote! { let #name = #decode; }).collect(); + let decode_stmts: Vec<_> = param_names + .iter() + .zip(decodes.iter()) + .map(|(name, decode)| quote! { let #name = #decode; }) + .collect(); let call_args: Vec<_> = param_names.iter().map(|n| quote!(#n)).collect(); quote! { @@ -491,15 +513,22 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result = raw_types.iter().map(|ty| { - quote! { <#ty as pvm_contract::SolAbi>::HEAD_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; - } - }).collect(); + let head_size_exprs: Vec = raw_types + .iter() + .map(|ty| { + 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>::SLOT_SIZE; + } + }) + .collect(); let call_args: Vec<_> = param_names.iter().map(|n| quote!(#n)).collect(); quote! { @@ -535,7 +564,9 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result, Vec<_>) = parsed.methods.iter() + let (static_methods, trait_methods): (Vec<_>, Vec<_>) = parsed + .methods + .iter() .partition(|m| m.all_inputs_resolved && m.return_resolved); let static_dispatch_arms: Vec<_> = static_methods @@ -544,7 +575,9 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result = parsed.methods.iter() + let mixed_methods: Vec<_> = parsed + .methods + .iter() .filter(|m| m.all_inputs_resolved && !m.return_resolved) .collect(); let mixed_dispatch_arms: Vec<_> = mixed_methods @@ -553,7 +586,8 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result = trait_methods.iter() + let trait_dispatch_blocks: Vec<_> = trait_methods + .iter() .filter(|m| !m.all_inputs_resolved) .map(|m| generate_trait_dispatch_arm(m, mod_name)) .collect(); @@ -676,7 +710,10 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result syn::Result) -> TokenStream { +fn generate_abi_section( + parsed: &ParsedContract, + sol_interface: Option<&SolInterface>, +) -> TokenStream { let is_sol = sol_interface.is_some(); // Build a flat list of concatcp expressions. Each element is a single expr @@ -735,9 +775,18 @@ fn generate_abi_section(parsed: &ParsedContract, sol_interface: Option<&SolInter if let Some(ref ctor) = parsed.constructor { first_entry = false; parts.push(quote! { "{\"type\":\"constructor\",\"inputs\":[" }); - push_abi_params(&mut parts, &ctor.param_names, &ctor.raw_param_types, - if ctor.all_resolved { Some(&ctor.param_types) } else { None }, - ctor.all_resolved, &ctor.param_types); + push_abi_params( + &mut parts, + &ctor.param_names, + &ctor.raw_param_types, + if ctor.all_resolved { + Some(&ctor.param_types) + } else { + None + }, + ctor.all_resolved, + &ctor.param_types, + ); parts.push(quote! { "],\"stateMutability\":\"nonpayable\"}" }); } @@ -749,20 +798,41 @@ fn generate_abi_section(parsed: &ParsedContract, sol_interface: Option<&SolInter first_entry = false; let method_name = &method.signature.name; - let mutability = if method.return_type.is_some() { "view" } else { "nonpayable" }; + let mutability = if method.return_type.is_some() { + "view" + } else { + "nonpayable" + }; parts.push(quote! { "{\"type\":\"function\",\"name\":\"" }); parts.push(quote! { #method_name }); parts.push(quote! { "\",\"inputs\":[" }); - push_abi_params(&mut parts, &method.param_names, &method.param_types, - if is_sol { Some(&method.signature.inputs) } else { None }, - is_sol, &method.signature.inputs); + push_abi_params( + &mut parts, + &method.param_names, + &method.param_types, + if is_sol { + Some(&method.signature.inputs) + } else { + None + }, + is_sol, + &method.signature.inputs, + ); parts.push(quote! { "],\"outputs\":[" }); - push_abi_outputs(&mut parts, &method.return_type, - if is_sol { &method.signature.outputs } else { &[] }, is_sol); + push_abi_outputs( + &mut parts, + &method.return_type, + if is_sol { + &method.signature.outputs + } else { + &[] + }, + is_sol, + ); parts.push(quote! { "],\"stateMutability\":\"" }); parts.push(quote! { #mutability }); @@ -944,11 +1014,22 @@ fn generate_reference_method(method: &MethodInfo) -> TokenStream { // --- Varying piece 1: function params --- let params: Vec = if fully_resolved { - method.param_names.iter().zip(sig.inputs.iter()) - .map(|(name, ty)| { let rt = ty.rust_type(true); quote! { #name: #rt } }).collect() + method + .param_names + .iter() + .zip(sig.inputs.iter()) + .map(|(name, ty)| { + let rt = ty.rust_type(true); + quote! { #name: #rt } + }) + .collect() } else { - method.param_names.iter().zip(method.param_types.iter()) - .map(|(name, ty)| quote! { #name: #ty }).collect() + method + .param_names + .iter() + .zip(method.param_types.iter()) + .map(|(name, ty)| quote! { #name: #ty }) + .collect() }; // --- Varying piece 2: selector + calldata init --- @@ -958,9 +1039,12 @@ fn generate_reference_method(method: &MethodInfo) -> TokenStream { } else { let sol_name = &sig.name; let param_types = &method.param_types; - let sol_name_exprs: Vec = param_types.iter().map(|ty| { - quote! { <#ty as pvm_contract::SolAbi>::SOL_NAME } - }).collect(); + let sol_name_exprs: Vec = param_types + .iter() + .map(|ty| { + quote! { <#ty as pvm_contract::SolAbi>::SOL_NAME } + }) + .collect(); quote! { let __sel = pvm_contract::compute_selector(#sol_name, &[ #(#sol_name_exprs),* @@ -977,33 +1061,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 +1086,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..aa41c1f7 100644 --- a/crates/pvm-contract-macros/src/codegen/derive_sol_abi.rs +++ b/crates/pvm-contract-macros/src/codegen/derive_sol_abi.rs @@ -11,24 +11,26 @@ pub fn expand_derive_sol_abi(input: DeriveInput) -> Result { let inner_ty = &fields.unnamed[0].ty; - Ok(expand_newtype(name, &impl_generics, &ty_generics, where_clause, inner_ty)) + Ok(expand_newtype( + name, + &impl_generics, + &ty_generics, + where_clause, + inner_ty, + )) } // Named struct: struct Foo { x: u64, y: u64 } Fields::Named(fields) => { expand_named_struct(name, &impl_generics, &ty_generics, where_clause, fields) } - Fields::Unnamed(fields) => { - Err(syn::Error::new_spanned( - fields, - "SolAbi can only be derived for newtypes (single-field tuple structs) or named structs", - )) - } - Fields::Unit => { - Err(syn::Error::new_spanned( - name, - "SolAbi cannot be derived for unit structs", - )) - } + Fields::Unnamed(fields) => Err(syn::Error::new_spanned( + fields, + "SolAbi can only be derived for newtypes (single-field tuple structs) or named structs", + )), + Fields::Unit => Err(syn::Error::new_spanned( + name, + "SolAbi cannot be derived for unit structs", + )), }, Data::Enum(_) => Err(syn::Error::new_spanned( name, @@ -51,32 +53,33 @@ fn expand_newtype( inner_ty: &syn::Type, ) -> TokenStream { // If the newtype wraps Option, resolve the const strings through the inner type - let (sol_name_expr, abi_type_expr, abi_components_expr) = - if let Some(inner) = unwrap_option_inner(inner_ty) { - ( - quote! { - pvm_contract::const_format::concatcp!( - "(bool,", <#inner as pvm_contract::SolAbi>::SOL_NAME, ")" - ) - }, - quote! { "tuple" }, - quote! { - pvm_contract::const_format::concatcp!( - ",\"components\":[{\"name\":\"isSome\",\"type\":\"bool\"},{\"name\":\"value\",\"type\":\"", - <#inner as pvm_contract::SolAbi>::ABI_TYPE, - "\"", - <#inner as pvm_contract::SolAbi>::ABI_COMPONENTS, - "}]" - ) - }, - ) - } else { - ( - quote! { <#inner_ty as pvm_contract::SolAbi>::SOL_NAME }, - quote! { <#inner_ty as pvm_contract::SolAbi>::ABI_TYPE }, - quote! { <#inner_ty as pvm_contract::SolAbi>::ABI_COMPONENTS }, - ) - }; + let (sol_name_expr, abi_type_expr, abi_components_expr) = if let Some(inner) = + unwrap_option_inner(inner_ty) + { + ( + quote! { + pvm_contract::const_format::concatcp!( + "(bool,", <#inner as pvm_contract::SolAbi>::SOL_NAME, ")" + ) + }, + quote! { "tuple" }, + quote! { + pvm_contract::const_format::concatcp!( + ",\"components\":[{\"name\":\"isSome\",\"type\":\"bool\"},{\"name\":\"value\",\"type\":\"", + <#inner as pvm_contract::SolAbi>::ABI_TYPE, + "\"", + <#inner as pvm_contract::SolAbi>::ABI_COMPONENTS, + "}]" + ) + }, + ) + } else { + ( + quote! { <#inner_ty as pvm_contract::SolAbi>::SOL_NAME }, + quote! { <#inner_ty as pvm_contract::SolAbi>::ABI_TYPE }, + quote! { <#inner_ty as pvm_contract::SolAbi>::ABI_COMPONENTS }, + ) + }; quote! { impl #impl_generics pvm_contract::SolAbi for #name #ty_generics #where_clause { @@ -115,19 +118,27 @@ fn expand_named_struct( // Build SOL_NAME using concatcp! so it works with trait-delegated names (nested structs) // For Option fields, resolve through the inner type to avoid placeholder const strings - let sol_name_parts: Vec = field_types.iter().enumerate().map(|(i, ty)| { - let comma = if i > 0 { quote! { "," } } else { quote! {} }; - let type_name = if let Some(inner) = unwrap_option_inner(ty) { - quote! { "(bool,", <#inner as pvm_contract::SolAbi>::SOL_NAME, ")" } - } else { - quote! { <#ty as pvm_contract::SolAbi>::SOL_NAME } - }; - if i > 0 { - quote! { #comma, #type_name } - } else { - quote! { #type_name } - } - }).collect(); + let sol_name_parts: Vec = field_types + .iter() + .enumerate() + .map(|(i, ty)| { + let comma = if i > 0 { + quote! { "," } + } else { + quote! {} + }; + let type_name = if let Some(inner) = unwrap_option_inner(ty) { + quote! { "(bool,", <#inner as pvm_contract::SolAbi>::SOL_NAME, ")" } + } else { + quote! { <#ty as pvm_contract::SolAbi>::SOL_NAME } + }; + if i > 0 { + quote! { #comma, #type_name } + } else { + quote! { #type_name } + } + }) + .collect(); // Build ABI_COMPONENTS: ,"components":[{field entries}] // For Option fields, inline the tuple component structure @@ -226,7 +237,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..1d8bffeb 100644 --- a/crates/pvm-contract-macros/src/codegen/dispatch.rs +++ b/crates/pvm-contract-macros/src/codegen/dispatch.rs @@ -3,7 +3,7 @@ use quote::quote; use super::decode::{calculate_min_input_size, generate_decode_params}; use super::encode::generate_encode_return; -use crate::signature::{compute_selector, FunctionSignature}; +use crate::signature::{FunctionSignature, compute_selector}; pub struct MethodInfo { pub fn_name: syn::Ident, @@ -130,31 +130,38 @@ pub fn generate_dispatch_arm( /// Generate an if-else dispatch block for a method where input types are not all resolvable. /// This produces a block (not a match arm) that uses `return` to exit early when matched. -pub fn generate_trait_dispatch_arm( - method: &MethodInfo, - mod_name: &syn::Ident, -) -> TokenStream { +pub fn generate_trait_dispatch_arm(method: &MethodInfo, mod_name: &syn::Ident) -> TokenStream { let param_names = &method.param_names; let param_types = &method.param_types; let sol_name = &method.signature.name; // Build the selector computation using SolAbi::SOL_NAME for each param type - let sol_name_exprs: Vec = param_types.iter().map(|ty| { - quote! { <#ty as pvm_contract::SolAbi>::SOL_NAME } - }).collect(); + let sol_name_exprs: Vec = param_types + .iter() + .map(|ty| { + quote! { <#ty as pvm_contract::SolAbi>::SOL_NAME } + }) + .collect(); - // Build min size computation using SolAbi::HEAD_SIZE - let head_size_exprs: Vec = param_types.iter().map(|ty| { - quote! { <#ty as pvm_contract::SolAbi>::HEAD_SIZE } - }).collect(); + // 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>::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; - } - }).collect(); + 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>::SLOT_SIZE; + } + }) + .collect(); let call_args: Vec<_> = param_names.iter().map(|name| quote!(#name)).collect(); @@ -217,32 +224,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..238c36d5 100644 --- a/crates/pvm-contract-macros/src/codegen/mod.rs +++ b/crates/pvm-contract-macros/src/codegen/mod.rs @@ -8,16 +8,16 @@ mod encode; mod method; mod storage; -pub use abi_import::{expand_abi_import, AbiImportArgs}; -pub use contract::{expand_contract, ContractArgs}; +pub use abi_import::{AbiImportArgs, expand_abi_import}; +pub use contract::{ContractArgs, expand_contract}; pub use derive_sol_abi::expand_derive_sol_abi; -pub use method::{expand_constructor, expand_fallback, expand_method, MethodArgs}; +pub use method::{MethodArgs, expand_constructor, expand_fallback, expand_method}; pub use storage::expand_storage; use proc_macro2::TokenStream; use quote::quote; -use crate::signature::{compute_selector, SolType}; +use crate::signature::{SolType, compute_selector}; use decode::generate_decode; /// Generate the `cdm_reference()` function that resolves the contract address at runtime @@ -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..d506d446 100644 --- a/crates/pvm_contract/src/abi.rs +++ b/crates/pvm_contract/src/abi.rs @@ -21,6 +21,16 @@ 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 +41,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 +414,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 +455,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..bcdb1cde 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::{HostFn, api}; + #[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"); +} From f240273bee1cb31e8ce49ff9572236feaa705cb9 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 7 Apr 2026 16:59:22 -0400 Subject: [PATCH 2/2] Trim formatting-only diffs from ABI fix --- .../src/codegen/abi_import.rs | 85 +++----- .../src/codegen/contract.rs | 200 +++++------------- .../src/codegen/derive_sol_abi.rs | 115 +++++----- .../src/codegen/dispatch.rs | 41 ++-- crates/pvm-contract-macros/src/codegen/mod.rs | 8 +- crates/pvm_contract/src/abi.rs | 6 +- crates/pvm_contract/src/call.rs | 2 +- 7 files changed, 157 insertions(+), 300 deletions(-) diff --git a/crates/pvm-contract-macros/src/codegen/abi_import.rs b/crates/pvm-contract-macros/src/codegen/abi_import.rs index 8d326efc..94e27a9e 100644 --- a/crates/pvm-contract-macros/src/codegen/abi_import.rs +++ b/crates/pvm-contract-macros/src/codegen/abi_import.rs @@ -7,9 +7,9 @@ use crate::codegen::decode::generate_decode; use crate::codegen::encode::generate_encode_params; use crate::codegen::generate_cdm_reference; use crate::codegen::generate_resolved_return_parts; +use crate::signature::compute_selector; use crate::signature::FunctionSignature; use crate::signature::SolType; -use crate::signature::compute_selector; use crate::solidity::to_snake_case; // --------------------------------------------------------------------------- @@ -92,7 +92,9 @@ fn parse_abi_param(val: &serde_json::Value) -> Result { fn parse_abi_json(json_str: &str) -> Result, String> { let value: serde_json::Value = serde_json::from_str(json_str).map_err(|e| format!("Invalid ABI JSON: {}", e))?; - let arr = value.as_array().ok_or("ABI JSON must be an array")?; + let arr = value + .as_array() + .ok_or("ABI JSON must be an array")?; let mut functions = Vec::new(); for entry in arr { @@ -108,20 +110,12 @@ fn parse_abi_json(json_str: &str) -> Result, String> { let inputs = entry .get("inputs") .and_then(|v| v.as_array()) - .map(|arr| { - arr.iter() - .map(parse_abi_param) - .collect::, _>>() - }) + .map(|arr| arr.iter().map(parse_abi_param).collect::, _>>()) .unwrap_or(Ok(vec![]))?; let outputs = entry .get("outputs") .and_then(|v| v.as_array()) - .map(|arr| { - arr.iter() - .map(parse_abi_param) - .collect::, _>>() - }) + .map(|arr| arr.iter().map(parse_abi_param).collect::, _>>()) .unwrap_or(Ok(vec![]))?; functions.push(AbiFunction { name, @@ -259,9 +253,7 @@ fn has_named_components(outputs: &[AbiParam]) -> bool { _ => return false, }; // Check that at least one component has a non-empty, non-positional name - comps - .iter() - .any(|c| !c.name.is_empty() && !c.name.starts_with("_field")) + comps.iter().any(|c| !c.name.is_empty() && !c.name.starts_with("_field")) } // --------------------------------------------------------------------------- @@ -321,7 +313,8 @@ fn generate_abi_reference_method(func: &AbiFunction) -> Result = field_names .iter() .enumerate() @@ -405,10 +397,7 @@ fn generate_return_struct(func: &AbiFunction) -> Result, Str .map(|c| format_ident!("{}", to_snake_case(&c.name))) .collect(); - let field_types: Vec = comp_types - .iter() - .map(|t: &SolType| t.rust_type(true)) - .collect(); + let field_types: Vec = comp_types.iter().map(|t: &SolType| t.rust_type(true)).collect(); let result: TokenStream = quote! { pub struct #struct_name { @@ -434,8 +423,9 @@ pub fn expand_abi_import(args: AbiImportArgs) -> syn::Result { ) })?; - let functions = parse_abi_json(&json_str) - .map_err(|e| syn::Error::new(proc_macro2::Span::call_site(), e))?; + let functions = parse_abi_json(&json_str).map_err(|e| { + syn::Error::new(proc_macro2::Span::call_site(), e) + })?; // Generate return structs for functions with named tuple outputs let mut return_structs = Vec::new(); @@ -452,8 +442,9 @@ pub fn expand_abi_import(args: AbiImportArgs) -> syn::Result { // Generate methods let mut methods = Vec::new(); for func in &functions { - let method = generate_abi_reference_method(func) - .map_err(|e| syn::Error::new(proc_macro2::Span::call_site(), e))?; + let method = generate_abi_reference_method(func).map_err(|e| { + syn::Error::new(proc_macro2::Span::call_site(), e) + })?; methods.push(method); } @@ -520,21 +511,9 @@ mod tests { #[test] fn test_parse_tuple_type() { let comps = vec![ - AbiParam { - name: "id".into(), - type_str: "bytes32".into(), - components: None, - }, - AbiParam { - name: "status".into(), - type_str: "uint8".into(), - components: None, - }, - AbiParam { - name: "proposer".into(), - type_str: "address".into(), - components: None, - }, + AbiParam { name: "id".into(), type_str: "bytes32".into(), components: None }, + AbiParam { name: "status".into(), type_str: "uint8".into(), components: None }, + AbiParam { name: "proposer".into(), type_str: "address".into(), components: None }, ]; let result = parse_abi_type("tuple", Some(&comps)).unwrap(); assert_eq!( @@ -572,16 +551,8 @@ mod tests { name: "".into(), type_str: "tuple".into(), components: Some(vec![ - AbiParam { - name: "id".into(), - type_str: "bytes32".into(), - components: None, - }, - AbiParam { - name: "status".into(), - type_str: "uint8".into(), - components: None, - }, + AbiParam { name: "id".into(), type_str: "bytes32".into(), components: None }, + AbiParam { name: "status".into(), type_str: "uint8".into(), components: None }, ]), }]; assert!(has_named_components(&outputs)); @@ -596,16 +567,8 @@ mod tests { // Multiple outputs (not a named struct case) let outputs = vec![ - AbiParam { - name: "".into(), - type_str: "uint64".into(), - components: None, - }, - AbiParam { - name: "".into(), - type_str: "address".into(), - components: None, - }, + AbiParam { name: "".into(), type_str: "uint64".into(), components: None }, + AbiParam { name: "".into(), type_str: "address".into(), components: None }, ]; assert!(!has_named_components(&outputs)); } diff --git a/crates/pvm-contract-macros/src/codegen/contract.rs b/crates/pvm-contract-macros/src/codegen/contract.rs index f10a12b4..2b977916 100644 --- a/crates/pvm-contract-macros/src/codegen/contract.rs +++ b/crates/pvm-contract-macros/src/codegen/contract.rs @@ -1,13 +1,13 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::{Attribute, Ident, ItemMod, LitInt, LitStr, Token, parse::Parse, parse::ParseStream}; +use syn::{parse::Parse, parse::ParseStream, Attribute, Ident, ItemMod, LitInt, LitStr, Token}; use super::decode::{calculate_min_input_size, generate_decode_params}; -use super::dispatch::{MethodInfo, generate_dispatch_arm, generate_trait_dispatch_arm}; +use super::dispatch::{generate_dispatch_arm, generate_trait_dispatch_arm, MethodInfo}; use super::encode::{generate_encode_params, generate_encode_params_trait}; use super::{generate_cdm_reference, generate_resolved_return_parts, unwrap_option_inner}; -use crate::signature::{FunctionSignature, SolType, compute_selector}; -use crate::solidity::{SolInterface, parse_solidity_interface, to_snake_case}; +use crate::signature::{compute_selector, FunctionSignature, SolType}; +use crate::solidity::{parse_solidity_interface, to_snake_case, SolInterface}; pub struct ContractArgs { pub no_alloc: bool, @@ -243,46 +243,29 @@ fn parse_contract( for item in &content.1 { if let syn::Item::Fn(func) = item { if has_pvm_attr(&func.attrs, "constructor") { - let param_names: Vec = func - .sig - .inputs - .iter() - .filter_map(|arg| { - if let syn::FnArg::Typed(pat_type) = arg { - if let syn::Pat::Ident(pat_ident) = &*pat_type.pat { - return Some(pat_ident.ident.clone()); - } + let param_names: Vec = func.sig.inputs.iter().filter_map(|arg| { + if let syn::FnArg::Typed(pat_type) = arg { + if let syn::Pat::Ident(pat_ident) = &*pat_type.pat { + return Some(pat_ident.ident.clone()); } + } + None + }).collect(); + let raw_param_types: Vec = func.sig.inputs.iter().filter_map(|arg| { + if let syn::FnArg::Typed(pat_type) = arg { + Some((*pat_type.ty).clone()) + } else { None - }) - .collect(); - let raw_param_types: Vec = func - .sig - .inputs - .iter() - .filter_map(|arg| { - if let syn::FnArg::Typed(pat_type) = arg { - Some((*pat_type.ty).clone()) - } else { - None - } - }) - .collect(); - let param_types: Vec = func - .sig - .inputs - .iter() - .filter_map(|arg| { - if let syn::FnArg::Typed(pat_type) = arg { - SolType::from_rust_type(&pat_type.ty) - } else { - None - } - }) - .collect(); - let all_resolved = raw_param_types - .iter() - .all(|ty| SolType::from_rust_type(ty).is_some()); + } + }).collect(); + let param_types: Vec = func.sig.inputs.iter().filter_map(|arg| { + if let syn::FnArg::Typed(pat_type) = arg { + SolType::from_rust_type(&pat_type.ty) + } else { + None + } + }).collect(); + let all_resolved = raw_param_types.iter().all(|ty| SolType::from_rust_type(ty).is_some()); constructor = Some(ConstructorInfo { fn_name: func.sig.ident.clone(), param_names, @@ -369,9 +352,7 @@ fn parse_contract( let (all_inputs_resolved, return_resolved) = if is_sol_interface { (true, true) } else { - let inputs_ok = param_types - .iter() - .all(|ty| SolType::from_rust_type(ty).is_some()); + let inputs_ok = param_types.iter().all(|ty| SolType::from_rust_type(ty).is_some()); let return_ok = match &return_type { None => true, Some(ty) => SolType::from_rust_type(ty).is_some(), @@ -482,11 +463,8 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result = param_names - .iter() - .zip(decodes.iter()) - .map(|(name, decode)| quote! { let #name = #decode; }) - .collect(); + let decode_stmts: Vec<_> = param_names.iter().zip(decodes.iter()) + .map(|(name, decode)| quote! { let #name = #decode; }).collect(); let call_args: Vec<_> = param_names.iter().map(|n| quote!(#n)).collect(); quote! { @@ -513,22 +491,15 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result = raw_types - .iter() - .map(|ty| { - 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>::SLOT_SIZE; - } - }) - .collect(); + let head_size_exprs: Vec = raw_types.iter().map(|ty| { + 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>::SLOT_SIZE; + } + }).collect(); let call_args: Vec<_> = param_names.iter().map(|n| quote!(#n)).collect(); quote! { @@ -564,9 +535,7 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result, Vec<_>) = parsed - .methods - .iter() + let (static_methods, trait_methods): (Vec<_>, Vec<_>) = parsed.methods.iter() .partition(|m| m.all_inputs_resolved && m.return_resolved); let static_dispatch_arms: Vec<_> = static_methods @@ -575,9 +544,7 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result = parsed - .methods - .iter() + let mixed_methods: Vec<_> = parsed.methods.iter() .filter(|m| m.all_inputs_resolved && !m.return_resolved) .collect(); let mixed_dispatch_arms: Vec<_> = mixed_methods @@ -586,8 +553,7 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result = trait_methods - .iter() + let trait_dispatch_blocks: Vec<_> = trait_methods.iter() .filter(|m| !m.all_inputs_resolved) .map(|m| generate_trait_dispatch_arm(m, mod_name)) .collect(); @@ -710,10 +676,7 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result syn::Result, -) -> TokenStream { +fn generate_abi_section(parsed: &ParsedContract, sol_interface: Option<&SolInterface>) -> TokenStream { let is_sol = sol_interface.is_some(); // Build a flat list of concatcp expressions. Each element is a single expr @@ -775,18 +735,9 @@ fn generate_abi_section( if let Some(ref ctor) = parsed.constructor { first_entry = false; parts.push(quote! { "{\"type\":\"constructor\",\"inputs\":[" }); - push_abi_params( - &mut parts, - &ctor.param_names, - &ctor.raw_param_types, - if ctor.all_resolved { - Some(&ctor.param_types) - } else { - None - }, - ctor.all_resolved, - &ctor.param_types, - ); + push_abi_params(&mut parts, &ctor.param_names, &ctor.raw_param_types, + if ctor.all_resolved { Some(&ctor.param_types) } else { None }, + ctor.all_resolved, &ctor.param_types); parts.push(quote! { "],\"stateMutability\":\"nonpayable\"}" }); } @@ -798,41 +749,20 @@ fn generate_abi_section( first_entry = false; let method_name = &method.signature.name; - let mutability = if method.return_type.is_some() { - "view" - } else { - "nonpayable" - }; + let mutability = if method.return_type.is_some() { "view" } else { "nonpayable" }; parts.push(quote! { "{\"type\":\"function\",\"name\":\"" }); parts.push(quote! { #method_name }); parts.push(quote! { "\",\"inputs\":[" }); - push_abi_params( - &mut parts, - &method.param_names, - &method.param_types, - if is_sol { - Some(&method.signature.inputs) - } else { - None - }, - is_sol, - &method.signature.inputs, - ); + push_abi_params(&mut parts, &method.param_names, &method.param_types, + if is_sol { Some(&method.signature.inputs) } else { None }, + is_sol, &method.signature.inputs); parts.push(quote! { "],\"outputs\":[" }); - push_abi_outputs( - &mut parts, - &method.return_type, - if is_sol { - &method.signature.outputs - } else { - &[] - }, - is_sol, - ); + push_abi_outputs(&mut parts, &method.return_type, + if is_sol { &method.signature.outputs } else { &[] }, is_sol); parts.push(quote! { "],\"stateMutability\":\"" }); parts.push(quote! { #mutability }); @@ -1014,22 +944,11 @@ fn generate_reference_method(method: &MethodInfo) -> TokenStream { // --- Varying piece 1: function params --- let params: Vec = if fully_resolved { - method - .param_names - .iter() - .zip(sig.inputs.iter()) - .map(|(name, ty)| { - let rt = ty.rust_type(true); - quote! { #name: #rt } - }) - .collect() + method.param_names.iter().zip(sig.inputs.iter()) + .map(|(name, ty)| { let rt = ty.rust_type(true); quote! { #name: #rt } }).collect() } else { - method - .param_names - .iter() - .zip(method.param_types.iter()) - .map(|(name, ty)| quote! { #name: #ty }) - .collect() + method.param_names.iter().zip(method.param_types.iter()) + .map(|(name, ty)| quote! { #name: #ty }).collect() }; // --- Varying piece 2: selector + calldata init --- @@ -1039,12 +958,9 @@ fn generate_reference_method(method: &MethodInfo) -> TokenStream { } else { let sol_name = &sig.name; let param_types = &method.param_types; - let sol_name_exprs: Vec = param_types - .iter() - .map(|ty| { - quote! { <#ty as pvm_contract::SolAbi>::SOL_NAME } - }) - .collect(); + let sol_name_exprs: Vec = param_types.iter().map(|ty| { + quote! { <#ty as pvm_contract::SolAbi>::SOL_NAME } + }).collect(); quote! { let __sel = pvm_contract::compute_selector(#sol_name, &[ #(#sol_name_exprs),* 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 aa41c1f7..7e5b66ef 100644 --- a/crates/pvm-contract-macros/src/codegen/derive_sol_abi.rs +++ b/crates/pvm-contract-macros/src/codegen/derive_sol_abi.rs @@ -11,26 +11,24 @@ pub fn expand_derive_sol_abi(input: DeriveInput) -> Result { let inner_ty = &fields.unnamed[0].ty; - Ok(expand_newtype( - name, - &impl_generics, - &ty_generics, - where_clause, - inner_ty, - )) + Ok(expand_newtype(name, &impl_generics, &ty_generics, where_clause, inner_ty)) } // Named struct: struct Foo { x: u64, y: u64 } Fields::Named(fields) => { expand_named_struct(name, &impl_generics, &ty_generics, where_clause, fields) } - Fields::Unnamed(fields) => Err(syn::Error::new_spanned( - fields, - "SolAbi can only be derived for newtypes (single-field tuple structs) or named structs", - )), - Fields::Unit => Err(syn::Error::new_spanned( - name, - "SolAbi cannot be derived for unit structs", - )), + Fields::Unnamed(fields) => { + Err(syn::Error::new_spanned( + fields, + "SolAbi can only be derived for newtypes (single-field tuple structs) or named structs", + )) + } + Fields::Unit => { + Err(syn::Error::new_spanned( + name, + "SolAbi cannot be derived for unit structs", + )) + } }, Data::Enum(_) => Err(syn::Error::new_spanned( name, @@ -53,33 +51,32 @@ fn expand_newtype( inner_ty: &syn::Type, ) -> TokenStream { // If the newtype wraps Option, resolve the const strings through the inner type - let (sol_name_expr, abi_type_expr, abi_components_expr) = if let Some(inner) = - unwrap_option_inner(inner_ty) - { - ( - quote! { - pvm_contract::const_format::concatcp!( - "(bool,", <#inner as pvm_contract::SolAbi>::SOL_NAME, ")" - ) - }, - quote! { "tuple" }, - quote! { - pvm_contract::const_format::concatcp!( - ",\"components\":[{\"name\":\"isSome\",\"type\":\"bool\"},{\"name\":\"value\",\"type\":\"", - <#inner as pvm_contract::SolAbi>::ABI_TYPE, - "\"", - <#inner as pvm_contract::SolAbi>::ABI_COMPONENTS, - "}]" - ) - }, - ) - } else { - ( - quote! { <#inner_ty as pvm_contract::SolAbi>::SOL_NAME }, - quote! { <#inner_ty as pvm_contract::SolAbi>::ABI_TYPE }, - quote! { <#inner_ty as pvm_contract::SolAbi>::ABI_COMPONENTS }, - ) - }; + let (sol_name_expr, abi_type_expr, abi_components_expr) = + if let Some(inner) = unwrap_option_inner(inner_ty) { + ( + quote! { + pvm_contract::const_format::concatcp!( + "(bool,", <#inner as pvm_contract::SolAbi>::SOL_NAME, ")" + ) + }, + quote! { "tuple" }, + quote! { + pvm_contract::const_format::concatcp!( + ",\"components\":[{\"name\":\"isSome\",\"type\":\"bool\"},{\"name\":\"value\",\"type\":\"", + <#inner as pvm_contract::SolAbi>::ABI_TYPE, + "\"", + <#inner as pvm_contract::SolAbi>::ABI_COMPONENTS, + "}]" + ) + }, + ) + } else { + ( + quote! { <#inner_ty as pvm_contract::SolAbi>::SOL_NAME }, + quote! { <#inner_ty as pvm_contract::SolAbi>::ABI_TYPE }, + quote! { <#inner_ty as pvm_contract::SolAbi>::ABI_COMPONENTS }, + ) + }; quote! { impl #impl_generics pvm_contract::SolAbi for #name #ty_generics #where_clause { @@ -118,27 +115,19 @@ fn expand_named_struct( // Build SOL_NAME using concatcp! so it works with trait-delegated names (nested structs) // For Option fields, resolve through the inner type to avoid placeholder const strings - let sol_name_parts: Vec = field_types - .iter() - .enumerate() - .map(|(i, ty)| { - let comma = if i > 0 { - quote! { "," } - } else { - quote! {} - }; - let type_name = if let Some(inner) = unwrap_option_inner(ty) { - quote! { "(bool,", <#inner as pvm_contract::SolAbi>::SOL_NAME, ")" } - } else { - quote! { <#ty as pvm_contract::SolAbi>::SOL_NAME } - }; - if i > 0 { - quote! { #comma, #type_name } - } else { - quote! { #type_name } - } - }) - .collect(); + let sol_name_parts: Vec = field_types.iter().enumerate().map(|(i, ty)| { + let comma = if i > 0 { quote! { "," } } else { quote! {} }; + let type_name = if let Some(inner) = unwrap_option_inner(ty) { + quote! { "(bool,", <#inner as pvm_contract::SolAbi>::SOL_NAME, ")" } + } else { + quote! { <#ty as pvm_contract::SolAbi>::SOL_NAME } + }; + if i > 0 { + quote! { #comma, #type_name } + } else { + quote! { #type_name } + } + }).collect(); // Build ABI_COMPONENTS: ,"components":[{field entries}] // For Option fields, inline the tuple component structure diff --git a/crates/pvm-contract-macros/src/codegen/dispatch.rs b/crates/pvm-contract-macros/src/codegen/dispatch.rs index 1d8bffeb..351094bc 100644 --- a/crates/pvm-contract-macros/src/codegen/dispatch.rs +++ b/crates/pvm-contract-macros/src/codegen/dispatch.rs @@ -3,7 +3,7 @@ use quote::quote; use super::decode::{calculate_min_input_size, generate_decode_params}; use super::encode::generate_encode_return; -use crate::signature::{FunctionSignature, compute_selector}; +use crate::signature::{compute_selector, FunctionSignature}; pub struct MethodInfo { pub fn_name: syn::Ident, @@ -130,38 +130,31 @@ pub fn generate_dispatch_arm( /// Generate an if-else dispatch block for a method where input types are not all resolvable. /// This produces a block (not a match arm) that uses `return` to exit early when matched. -pub fn generate_trait_dispatch_arm(method: &MethodInfo, mod_name: &syn::Ident) -> TokenStream { +pub fn generate_trait_dispatch_arm( + method: &MethodInfo, + mod_name: &syn::Ident, +) -> TokenStream { let param_names = &method.param_names; let param_types = &method.param_types; let sol_name = &method.signature.name; // Build the selector computation using SolAbi::SOL_NAME for each param type - let sol_name_exprs: Vec = param_types - .iter() - .map(|ty| { - quote! { <#ty as pvm_contract::SolAbi>::SOL_NAME } - }) - .collect(); + let sol_name_exprs: Vec = param_types.iter().map(|ty| { + quote! { <#ty as pvm_contract::SolAbi>::SOL_NAME } + }).collect(); // 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>::SLOT_SIZE } - }) - .collect(); + let head_size_exprs: Vec = param_types.iter().map(|ty| { + 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>::SLOT_SIZE; - } - }) - .collect(); + 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>::SLOT_SIZE; + } + }).collect(); let call_args: Vec<_> = param_names.iter().map(|name| quote!(#name)).collect(); diff --git a/crates/pvm-contract-macros/src/codegen/mod.rs b/crates/pvm-contract-macros/src/codegen/mod.rs index 238c36d5..749c79e7 100644 --- a/crates/pvm-contract-macros/src/codegen/mod.rs +++ b/crates/pvm-contract-macros/src/codegen/mod.rs @@ -8,16 +8,16 @@ mod encode; mod method; mod storage; -pub use abi_import::{AbiImportArgs, expand_abi_import}; -pub use contract::{ContractArgs, expand_contract}; +pub use abi_import::{expand_abi_import, AbiImportArgs}; +pub use contract::{expand_contract, ContractArgs}; pub use derive_sol_abi::expand_derive_sol_abi; -pub use method::{MethodArgs, expand_constructor, expand_fallback, expand_method}; +pub use method::{expand_constructor, expand_fallback, expand_method, MethodArgs}; pub use storage::expand_storage; use proc_macro2::TokenStream; use quote::quote; -use crate::signature::{SolType, compute_selector}; +use crate::signature::{compute_selector, SolType}; use decode::generate_decode; /// Generate the `cdm_reference()` function that resolves the contract address at runtime diff --git a/crates/pvm_contract/src/abi.rs b/crates/pvm_contract/src/abi.rs index d506d446..ffe4c1eb 100644 --- a/crates/pvm_contract/src/abi.rs +++ b/crates/pvm_contract/src/abi.rs @@ -25,11 +25,7 @@ pub trait SolAbi: Sized { /// /// 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 - }; + 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; diff --git a/crates/pvm_contract/src/call.rs b/crates/pvm_contract/src/call.rs index bcdb1cde..575ffc8a 100644 --- a/crates/pvm_contract/src/call.rs +++ b/crates/pvm_contract/src/call.rs @@ -4,7 +4,7 @@ use alloc::vec::Vec; use crate::CallFlags; #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] -use crate::{HostFn, api}; +use crate::{api, HostFn}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CallError {