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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 18 additions & 6 deletions crates/cargo-pvm-contract-builder/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ pub struct AbiParam {
pub name: String,
#[serde(rename = "type")]
pub param_type: String,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub components: Vec<AbiParam>,
}

pub fn generate_abi_for_bin(manifest_dir: &Path, bin_name: &str) -> Result<Option<AbiJson>> {
Expand Down Expand Up @@ -278,7 +281,11 @@ pub(crate) fn parse_sol_params(params_str: &str) -> Vec<AbiParam> {
.find(|s| !matches!(**s, "memory" | "calldata" | "storage"))
.map(|s| s.to_string())
.unwrap_or_default();
Some(AbiParam { name, param_type })
Some(AbiParam {
name,
param_type,
components: vec![],
})
})
.collect()
}
Expand Down Expand Up @@ -381,11 +388,13 @@ mod tests {
inputs: vec![
AbiParam {
name: "to".to_string(),
param_type: "address".to_string()
param_type: "address".to_string(),
components: vec![],
},
AbiParam {
name: "amount".to_string(),
param_type: "uint256".to_string()
param_type: "uint256".to_string(),
components: vec![],
},
],
outputs: vec![],
Expand All @@ -406,11 +415,13 @@ mod tests {
name: "balanceOf".to_string(),
inputs: vec![AbiParam {
name: "account".to_string(),
param_type: "address".to_string()
param_type: "address".to_string(),
components: vec![],
}],
outputs: vec![AbiParam {
name: "".to_string(),
param_type: "uint256".to_string()
param_type: "uint256".to_string(),
components: vec![],
}],
state_mutability: Some("view".to_string()),
}
Expand All @@ -429,7 +440,8 @@ mod tests {
inputs: vec![],
outputs: vec![AbiParam {
name: "".to_string(),
param_type: "uint256".to_string()
param_type: "uint256".to_string(),
components: vec![],
}],
state_mutability: Some("view".to_string()),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ ruint = { version = "1.17", default-features = false }

{% if !use_dsl -%}
[features]
abi-gen = []
abi-gen = ["pvm-contract-types/abi-gen"]

{% endif -%}
[build-dependencies]
Expand Down
106 changes: 30 additions & 76 deletions crates/pvm-contract-macros/src/codegen/abi_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,36 +27,22 @@ pub fn generate_abi_gen(parsed: &ParsedContract, has_sol_path: bool) -> (TokenSt

fn generate_abi_gen_impl(parsed: &ParsedContract) -> syn::Result<(TokenStream, TokenStream)> {
let constructor_entry = if parsed.has_constructor {
let constructor_input_entries: Vec<TokenStream> = parsed
let ctor_params: Vec<TokenStream> = parsed
.constructor_inputs
.iter()
.map(|(name, ty)| {
let name_str = name.to_string();
Ok(quote! {
if !__first_ctor_input {
__abi.push(',');
} else {
__first_ctor_input = false;
}
__abi.push_str("{\"name\":\"");
__abi.push_str(#name_str);
__abi.push_str("\",\"type\":\"");
__abi.push_str(<#ty as ::pvm_contract_types::SolEncode>::SOL_NAME);
__abi.push_str("\"}");
})
quote! {
<#ty as ::pvm_contract_types::SolEncode>::abi_param(#name_str)
}
})
.collect::<syn::Result<Vec<_>>>()?;
.collect();

quote! {
if !__first_item {
__abi.push(',');
} else {
__first_item = false;
}
__abi.push_str("{\"type\":\"constructor\",\"inputs\":[");
let mut __first_ctor_input = true;
#(#constructor_input_entries)*
__abi.push_str("],\"stateMutability\":\"payable\"}");
__items.push(::pvm_contract_types::AbiItem::Constructor {
inputs: vec![#(#ctor_params),*],
state_mutability: "nonpayable".into(),
});
}
} else {
quote! {}
Expand All @@ -72,15 +58,13 @@ fn generate_abi_gen_impl(parsed: &ParsedContract) -> syn::Result<(TokenStream, T
#[cfg(feature = "abi-gen")]
#[doc(hidden)]
pub fn __abi_json() -> ::std::string::String {
let mut __abi = ::std::string::String::from("[");
let mut __first_item = true;
let mut __items: ::std::vec::Vec<::pvm_contract_types::AbiItem> = ::std::vec::Vec::new();

#constructor_entry

#(#method_entries)*

__abi.push(']');
__abi
::pvm_contract_types::abi_to_json(&__items)
}
};

Expand All @@ -95,71 +79,41 @@ fn generate_abi_gen_impl(parsed: &ParsedContract) -> syn::Result<(TokenStream, T
Ok((helper, main_fn))
}

// NOTE: All methods and constructors are emitted with `"stateMutability":"payable"`
// because we don't yet support `payable`/`nonpayable`/`view`/`pure` attributes.
// Once state mutability attributes are added, this should be derived from the method
// annotation instead of hardcoded.
fn generate_method_entry(method: &MethodInfo) -> syn::Result<TokenStream> {
let method_name = &method.sol_name;

let input_entries: Vec<TokenStream> = method
let input_params: Vec<TokenStream> = method
.param_types
.iter()
.zip(method.param_names.iter())
.map(|(ty, name)| {
let name_str = name.to_string();
Ok(quote! {
if !__first_input {
__abi.push(',');
} else {
__first_input = false;
}
__abi.push_str("{\"name\":\"");
__abi.push_str(#name_str);
__abi.push_str("\",\"type\":\"");
__abi.push_str(<#ty as ::pvm_contract_types::SolEncode>::SOL_NAME);
__abi.push_str("\"}");
})
quote! {
<#ty as ::pvm_contract_types::SolEncode>::abi_param(#name_str)
}
})
.collect::<syn::Result<Vec<_>>>()?;
.collect();

let output_entries: Vec<TokenStream> = method
let output_params: Vec<TokenStream> = method
.return_types
.iter()
.map(|ty| {
Ok(quote! {
if !__first_output {
__abi.push(',');
} else {
__first_output = false;
}
__abi.push_str("{\"name\":\"\",\"type\":\"");
__abi.push_str(<#ty as ::pvm_contract_types::SolEncode>::SOL_NAME);
__abi.push_str("\"}");
})
quote! {
<#ty as ::pvm_contract_types::SolEncode>::abi_param("")
}
})
.collect::<syn::Result<Vec<_>>>()?;

Ok(quote! {
if !__first_item {
__abi.push(',');
} else {
__first_item = false;
}
.collect();

__abi.push_str("{\"type\":\"function\",\"name\":\"");
__abi.push_str(#method_name);
__abi.push_str("\",\"inputs\":[");
let has_return = !method.return_types.is_empty();
let mutability = if has_return { "view" } else { "nonpayable" };

let mut __first_input = true;
#(#input_entries)*

__abi.push_str("],\"outputs\":[");

let mut __first_output = true;
#(#output_entries)*

__abi.push_str("],\"stateMutability\":\"payable\"}");
Ok(quote! {
__items.push(::pvm_contract_types::AbiItem::Function {
name: #method_name.into(),
inputs: vec![#(#input_params),*],
outputs: vec![#(#output_params),*],
state_mutability: #mutability.into(),
});
})
}

Expand Down
8 changes: 4 additions & 4 deletions crates/pvm-contract-macros/src/codegen/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -742,13 +742,13 @@ mod tests {

// abi-gen main is generated (no sol_path)
assert!(output.contains("feature = \"abi-gen\""));
// constructor entry is present with its inputs array
assert!(output.contains("{\\\"type\\\":\\\"constructor\\\",\\\"inputs\\\":["));
// constructor entry uses AbiItem struct
assert!(output.contains("AbiItem"));
// param names are emitted
assert!(output.contains("\"owner\""));
assert!(output.contains("\"supply\""));
// param types are resolved via trait SOL_NAME
assert!(output.contains("SOL_NAME"));
// param types are resolved via trait abi_param
assert!(output.contains("abi_param"));
}

#[test]
Expand Down
43 changes: 43 additions & 0 deletions crates/pvm-contract-macros/src/codegen/sol_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ fn expand_static_sol_type(
let encode_body = generate_static_encode_body(fields);
let decode_body = generate_static_decode_body(fields);

let abi_param_fn = generate_abi_param_fn(fields, field_info);

Ok(quote! {
impl ::pvm_contract_types::SolEncode for #name {
const IS_DYNAMIC: bool = false;
Expand All @@ -68,6 +70,8 @@ fn expand_static_sol_type(
fn encode_body_to(&self, buf: &mut [u8]) {
#encode_body
}

#abi_param_fn
}

impl ::pvm_contract_types::StaticEncodedLen for #name {
Expand Down Expand Up @@ -96,6 +100,8 @@ fn expand_dynamic_sol_type(
let encode_body = generate_dynamic_encode_body(fields, field_info, &head_size_expr);
let decode_body = generate_dynamic_decode_body(fields, field_info);

let abi_param_fn = generate_abi_param_fn(fields, field_info);

Ok(quote! {
impl ::pvm_contract_types::SolEncode for #name {
const IS_DYNAMIC: bool = #is_dynamic_expr;
Expand All @@ -109,6 +115,8 @@ fn expand_dynamic_sol_type(
fn encode_body_to(&self, buf: &mut [u8]) {
#encode_body
}

#abi_param_fn
}

impl ::pvm_contract_types::SolDecode for #name {
Expand All @@ -125,6 +133,41 @@ fn expand_dynamic_sol_type(
})
}

/// Generate the `abi_param()` method override for a struct.
/// Returns `"type": "tuple"` with `components` listing each field.
fn generate_abi_param_fn(
fields: &Fields,
field_info: &[(Option<syn::Ident>, SolType)],
) -> TokenStream {
let field_types = get_field_types(fields);

let component_exprs: Vec<TokenStream> = field_info
.iter()
.zip(field_types.iter())
.map(|((field_name, _), field_ty)| {
let name_str = match field_name {
Some(ident) => ident.to_string(),
None => String::new(),
};
quote! {
<#field_ty as ::pvm_contract_types::SolEncode>::abi_param(#name_str)
}
})
.collect();

quote! {
#[cfg(feature = "abi-gen")]
fn abi_param(name: &str) -> ::pvm_contract_types::AbiParam {
extern crate alloc;
::pvm_contract_types::AbiParam {
name: alloc::string::String::from(name),
param_type: alloc::string::String::from("tuple"),
components: alloc::vec![#(#component_exprs),*],
}
}
}
}

fn build_is_dynamic_expr(
fields: &Fields,
field_info: &[(Option<syn::Ident>, SolType)],
Expand Down
2 changes: 1 addition & 1 deletion crates/pvm-contract-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ use syn::{DeriveInput, ItemFn, ItemMod, parse_macro_input};
/// let result = balance_of(::core::convert::Into::into(account));
/// let mut __buf = [0u8;
/// <U256 as ::pvm_contract_types::StaticEncodedLen>::ENCODED_SIZE];
/// <U256 as ::pvm_contract_types::SolEncode>::encode_body_to(&result, &mut __buf);
/// <U256 as ::pvm_contract_types::SolEncode>::encode_to(&result, &mut __buf);
/// pallet_revive_uapi::HostFnImpl::return_value(
/// pallet_revive_uapi::ReturnFlags::empty(), &__buf);
/// }
Expand Down
Loading
Loading