Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 102 additions & 44 deletions crates/pvm-contract-macros/src/codegen/abi_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,10 @@ fn generate_abi_reference_method(func: &AbiFunction) -> Result<TokenStream, Stri
let param_names: Vec<Ident> = 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)
};
Expand All @@ -312,42 +313,32 @@ fn generate_abi_reference_method(func: &AbiFunction) -> Result<TokenStream, Stri
let encode_block = generate_encode_params(&param_names, &input_types);

// --- Return type and decode ---
let (ret_ty, output_setup, decode_return, has_output) =
let (ret_ty, decode_return, has_output) =
generate_return_handling(func, &output_types)?;

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(()) }
};

Ok(quote! {
pub fn #fn_name(&self, #(#params),*) -> pvm_contract::call::CallResult<#ret_ty> {
extern crate alloc;
#selector_setup
#encode_block
#output_setup
let mut output_ref: &mut [u8] = &mut output_buf[..];
let result = <pvm_contract::api as pvm_contract::HostFn>::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
}
})
}

fn generate_return_handling(
func: &AbiFunction,
output_types: &[SolType],
) -> 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();
Expand All @@ -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<TokenStream> = comp_types
let tuple_decode = generate_decode(&SolType::Tuple(comp_types.clone()), quote!(output), 0, true);
let tuple_bindings: Vec<TokenStream> = 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<TokenStream> = 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))
}
Expand Down Expand Up @@ -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"));
}
}
41 changes: 11 additions & 30 deletions crates/pvm-contract-macros/src/codegen/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,12 +492,12 @@ pub fn expand_contract(args: ContractArgs, input: ItemMod) -> syn::Result<TokenS
// Trait-based constructor decode for unresolved types
let raw_types = &ctor.raw_param_types;
let head_size_exprs: Vec<TokenStream> = 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<TokenStream> = 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();
Expand Down Expand Up @@ -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 ---
Expand All @@ -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 = <pvm_contract::api as pvm_contract::HostFn>::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
}
}
}
Expand Down
Loading