Skip to content
Merged
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
84 changes: 84 additions & 0 deletions smart_contracts/contracts/vm2/vm2-named-args/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,31 @@ impl Contract {
pub fn args_with_overriden_abi_convention(a: u32, b: u32, c: u32) -> Vec<u32> {
vec![a, b, c]
}

// Explicit Positional ABI with unit return
#[casper(abi_convention = AbiConvention::Positional)]
pub fn positional_unit_no_args() {}
}

#[casper]
impl ContractTrait for Contract {}

// Default (Positional) export with a value return.
#[casper(export)]
pub fn positional_export_inc(x: u32) -> u32 {
x + 1
}

// Default (Positional) export with unit return.
#[casper(export)]
pub fn positional_export_no_args_unit() {}

// Named export
#[casper(export, abi_convention = AbiConvention::Named)]
pub fn named_export_add(a: u32, b: u32) -> u32 {
a + b
}

#[cfg(test)]
mod tests {
use std::sync::Arc;
Expand Down Expand Up @@ -242,4 +262,68 @@ mod tests {
.expect("Expected entry point");
assert_eq!(e2.abi_convention, AbiConvention::Named);
}

#[test]
fn test_positional_impl_unit_ret_calls_ret_none() {
let env = Arc::new(EnvironmentMock::new());
env.add_expectation(ExpectedCall::expect_copy_input(&[]));
env.add_expectation(ExpectedCall::expect_get_info(Some(EnvInfo::default())));
env.add_expectation(ExpectedCall::expect_return(
Some((0, None)),
HOST_ERROR_SUCCESS,
));
with_env(env.clone(), || {
let _ = run_expecting_panic(|| __casper_export_positional_unit_no_args());
});
}

#[test]
fn test_positional_export_value_ret() {
let env = Arc::new(EnvironmentMock::new());

let input_bytes = borsh::to_vec(&(7u32,)).expect("borsh");
env.add_expectation(ExpectedCall::expect_copy_input(&input_bytes));

let expected_ret = borsh::to_vec(&8u32).expect("borsh");
env.add_expectation(ExpectedCall::expect_return(
Some((0, Some(expected_ret))),
HOST_ERROR_SUCCESS,
));
with_env(env.clone(), || {
let _ = run_expecting_panic(|| __casper_export_positional_export_inc());
});
}

#[test]
fn test_named_export_value_ret() {
let env = Arc::new(EnvironmentMock::new());
let mut runtime_args = RuntimeArgs::new();
runtime_args.insert("a", 2u32).unwrap();
runtime_args.insert("b", 3u32).unwrap();
let input_bytes = borsh::to_vec(&runtime_args).expect("borsh");
env.add_expectation(ExpectedCall::expect_copy_input(&input_bytes));
// Named returns CLValue-encoded value
let ret_clvalue = CLValue::from_t(&5u32).expect("clvalue");
let expected_ret = borsh::to_vec(&ret_clvalue).expect("borsh");
env.add_expectation(ExpectedCall::expect_return(
Some((0, Some(expected_ret))),
HOST_ERROR_SUCCESS,
));
with_env(env.clone(), || {
let _ = run_expecting_panic(|| __casper_export_named_export_add());
});
}

#[test]
fn test_positional_export_unit_ret_calls_ret_none() {
let env = Arc::new(EnvironmentMock::new());
env.add_expectation(ExpectedCall::expect_copy_input(&[]));
env.add_expectation(ExpectedCall::expect_return(
Some((0, None)),
HOST_ERROR_SUCCESS,
));
with_env(env.clone(), || {
let _ = run_expecting_panic(|| __casper_export_positional_export_no_args_unit());
});
}
}
110 changes: 97 additions & 13 deletions smart_contracts/vm2/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,6 @@ struct TraitMeta {
abi_convention: Option<syn::Path>,
}

#[derive(Debug, FromMeta)]
enum ItemFnMeta {
Export,
}

#[derive(Debug, FromMeta)]
struct ImplTraitForContractMeta {
/// Fully qualified path of the trait.
Expand Down Expand Up @@ -143,9 +138,33 @@ pub fn casper(attrs: TokenStream, item: TokenStream) -> TokenStream {
generate_impl_for_contract(entry_points)
}
} else if let Ok(func) = syn::parse::<ItemFn>(item.clone()) {
let func_meta = ItemFnMeta::from_list(&attr_args).unwrap();
match func_meta {
ItemFnMeta::Export => generate_export_function(&func),
let mut is_export = false;
let mut abi_convention: Option<syn::Path> = None;
for meta in &attr_args {
match meta {
ast::NestedMeta::Meta(syn::Meta::Path(path)) => {
if path.is_ident("export") {
is_export = true;
}
}
ast::NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
if nv.path.is_ident("abi_convention") {
if let syn::Expr::Path(expr_path) = &nv.value {
abi_convention = Some(expr_path.path.clone());
}
}
}
_ => {}
}
}
if is_export {
generate_export_function(&func, abi_convention)
} else {
let err = syn::Error::new(
Span::call_site(),
"Unsupported function attribute; expected #[casper(export ...)]",
);
TokenStream::from(err.to_compile_error())
}
} else {
let err = syn::Error::new(
Expand Down Expand Up @@ -248,7 +267,7 @@ fn process_casper_message_for_struct(
.into()
}

fn generate_export_function(func: &ItemFn) -> TokenStream {
fn generate_export_function(func: &ItemFn, abi_convention: Option<syn::Path>) -> TokenStream {
let func_name = &func.sig.ident;
let mut arg_names = Vec::new();
let mut arg_types = Vec::new();
Expand All @@ -271,6 +290,42 @@ fn generate_export_function(func: &ItemFn) -> TokenStream {
syn::ReturnType::Type(_, ty) => quote! { #ty },
};

let resolve_abi_convention = match abi_convention {
Some(convention_path) => quote! { #convention_path },
None => quote! { casper_contract_sdk::serializers::AbiConvention::Positional },
};

// Generate return handling tokens
let handle_ret = match &func.sig.output {
syn::ReturnType::Default => {
quote! {
match resolved_abi_convention {
casper_contract_sdk::serializers::AbiConvention::Positional => {
casper_contract_sdk::casper::ret(flags, None)
}
casper_contract_sdk::serializers::AbiConvention::Named => {
let ret_bytes = casper_contract_sdk::serializers::borsh::to_vec(&casper_contract_sdk::compat::types::CLValue::UNIT).expect("Failed to serialize return CLValue");
casper_contract_sdk::casper::ret(flags, Some(&ret_bytes))
}
}
}
}
syn::ReturnType::Type(_, _ty) => {
quote! {
let ret_bytes = match resolved_abi_convention {
casper_contract_sdk::serializers::AbiConvention::Positional => {
casper_contract_sdk::serializers::borsh::to_vec(&_ret).expect("Failed to serialize return value")
}
casper_contract_sdk::serializers::AbiConvention::Named => {
let ret_clvalue = casper_contract_sdk::compat::types::CLValue::from_t(&_ret).expect("Failed to convert return value to CLValue");
casper_contract_sdk::serializers::borsh::to_vec(&ret_clvalue).expect("Failed to serialize return CLValue")
}
};
casper_contract_sdk::casper::ret(flags, Some(&ret_bytes))
}
}
};

let _ctor_name = format_ident!("{func_name}_ctor");

let exported_func_name = format_ident!("__casper_export_{func_name}");
Expand All @@ -288,9 +343,38 @@ fn generate_export_function(func: &ItemFn) -> TokenStream {
struct Arguments {
#(#arg_names: #arg_types,)*
}

let mut flags = casper_contract_sdk::common::flags::ReturnFlags::empty();
let input = casper_contract_sdk::prelude::casper::copy_input();
let args: Arguments = casper_contract_sdk::serializers::borsh::from_slice(&input).unwrap();
let resolved_abi_convention = #resolve_abi_convention;
let args: Arguments = {
match resolved_abi_convention {
casper_contract_sdk::serializers::AbiConvention::Positional => {
casper_contract_sdk::serializers::borsh::from_slice(&input).unwrap()
}
casper_contract_sdk::serializers::AbiConvention::Named => {
let runtime_args: casper_contract_sdk::compat::types::RuntimeArgs =
casper_contract_sdk::serializers::borsh::from_slice(&input).unwrap();
#(
let #arg_names: #arg_types = {
let cl_value = runtime_args.get(stringify!(#arg_names)).unwrap_or_else(|| panic!(concat!("Failed to get named argument \"", stringify!(#arg_names), "\"")));
cl_value.to_t::<#arg_types>().unwrap_or_else(|error| {
panic!(concat!("Failed to convert named argument \"", stringify!(#arg_names), "\": {}"), error)
})
};
)*
Arguments {
#(
#arg_names,
)*
}
}
}
};

let _ret = #func_name(#(args.#arg_names,)*);

#handle_ret
}

#[cfg(not(target_arch = "wasm32"))]
Expand Down Expand Up @@ -324,7 +408,7 @@ fn generate_export_function(func: &ItemFn) -> TokenStream {
},
)*
],
abi_convention: casper_contract_sdk::serializers::AbiConvention::Positional, // todo
abi_convention: #resolve_abi_convention,
result_decl: {
casper_contract_sdk::abi::collector::AbiType {
type_name: core::any::type_name::<#ret>,
Expand Down Expand Up @@ -511,7 +595,7 @@ fn generate_impl_for_contract(mut entry_points: ItemImpl) -> TokenStream {
Some(quote! {
match #resolve_abi_convention {
casper_contract_sdk::serializers::AbiConvention::Positional => {
// Do nothing as lack of ret is synonymous with returning empty bytes (unit serializes to empty buffer)
casper_contract_sdk::casper::ret(flags, None)
}
casper_contract_sdk::serializers::AbiConvention::Named => {
// For a named ABI convention we'd always ret with the bytes of unit CLValue.
Expand Down Expand Up @@ -1204,7 +1288,7 @@ fn casper_trait_definition(mut item_trait: ItemTrait, trait_meta: TraitMeta) ->
Some(quote! {
match #resolve_abi_convention {
casper_contract_sdk::serializers::AbiConvention::Positional => {
// Do nothing as lack of ret is synonymous with returning empty bytes (unit serializes to empty buffer)
casper_contract_sdk::casper::ret(flags, None)
}
casper_contract_sdk::serializers::AbiConvention::Named => {
// For a named ABI convention we'd always ret with the bytes of unit CLValue.
Expand Down