Skip to content

Commit c8b7307

Browse files
authored
Merge pull request #5376 from igor-casper/core-235
[VM2] Properly handle ABI convention in the SDK proc-macro
2 parents f273666 + a6781a9 commit c8b7307

File tree

2 files changed

+181
-13
lines changed
  • smart_contracts

2 files changed

+181
-13
lines changed

smart_contracts/contracts/vm2/vm2-named-args/src/lib.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,31 @@ impl Contract {
3939
pub fn args_with_overriden_abi_convention(a: u32, b: u32, c: u32) -> Vec<u32> {
4040
vec![a, b, c]
4141
}
42+
43+
// Explicit Positional ABI with unit return
44+
#[casper(abi_convention = AbiConvention::Positional)]
45+
pub fn positional_unit_no_args() {}
4246
}
4347

4448
#[casper]
4549
impl ContractTrait for Contract {}
4650

51+
// Default (Positional) export with a value return.
52+
#[casper(export)]
53+
pub fn positional_export_inc(x: u32) -> u32 {
54+
x + 1
55+
}
56+
57+
// Default (Positional) export with unit return.
58+
#[casper(export)]
59+
pub fn positional_export_no_args_unit() {}
60+
61+
// Named export
62+
#[casper(export, abi_convention = AbiConvention::Named)]
63+
pub fn named_export_add(a: u32, b: u32) -> u32 {
64+
a + b
65+
}
66+
4767
#[cfg(test)]
4868
mod tests {
4969
use std::sync::Arc;
@@ -242,4 +262,68 @@ mod tests {
242262
.expect("Expected entry point");
243263
assert_eq!(e2.abi_convention, AbiConvention::Named);
244264
}
265+
266+
#[test]
267+
fn test_positional_impl_unit_ret_calls_ret_none() {
268+
let env = Arc::new(EnvironmentMock::new());
269+
env.add_expectation(ExpectedCall::expect_copy_input(&[]));
270+
env.add_expectation(ExpectedCall::expect_get_info(Some(EnvInfo::default())));
271+
env.add_expectation(ExpectedCall::expect_return(
272+
Some((0, None)),
273+
HOST_ERROR_SUCCESS,
274+
));
275+
with_env(env.clone(), || {
276+
let _ = run_expecting_panic(|| __casper_export_positional_unit_no_args());
277+
});
278+
}
279+
280+
#[test]
281+
fn test_positional_export_value_ret() {
282+
let env = Arc::new(EnvironmentMock::new());
283+
284+
let input_bytes = borsh::to_vec(&(7u32,)).expect("borsh");
285+
env.add_expectation(ExpectedCall::expect_copy_input(&input_bytes));
286+
287+
let expected_ret = borsh::to_vec(&8u32).expect("borsh");
288+
env.add_expectation(ExpectedCall::expect_return(
289+
Some((0, Some(expected_ret))),
290+
HOST_ERROR_SUCCESS,
291+
));
292+
with_env(env.clone(), || {
293+
let _ = run_expecting_panic(|| __casper_export_positional_export_inc());
294+
});
295+
}
296+
297+
#[test]
298+
fn test_named_export_value_ret() {
299+
let env = Arc::new(EnvironmentMock::new());
300+
let mut runtime_args = RuntimeArgs::new();
301+
runtime_args.insert("a", 2u32).unwrap();
302+
runtime_args.insert("b", 3u32).unwrap();
303+
let input_bytes = borsh::to_vec(&runtime_args).expect("borsh");
304+
env.add_expectation(ExpectedCall::expect_copy_input(&input_bytes));
305+
// Named returns CLValue-encoded value
306+
let ret_clvalue = CLValue::from_t(&5u32).expect("clvalue");
307+
let expected_ret = borsh::to_vec(&ret_clvalue).expect("borsh");
308+
env.add_expectation(ExpectedCall::expect_return(
309+
Some((0, Some(expected_ret))),
310+
HOST_ERROR_SUCCESS,
311+
));
312+
with_env(env.clone(), || {
313+
let _ = run_expecting_panic(|| __casper_export_named_export_add());
314+
});
315+
}
316+
317+
#[test]
318+
fn test_positional_export_unit_ret_calls_ret_none() {
319+
let env = Arc::new(EnvironmentMock::new());
320+
env.add_expectation(ExpectedCall::expect_copy_input(&[]));
321+
env.add_expectation(ExpectedCall::expect_return(
322+
Some((0, None)),
323+
HOST_ERROR_SUCCESS,
324+
));
325+
with_env(env.clone(), || {
326+
let _ = run_expecting_panic(|| __casper_export_positional_export_no_args_unit());
327+
});
328+
}
245329
}

smart_contracts/vm2/macros/src/lib.rs

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,6 @@ struct TraitMeta {
6262
abi_convention: Option<syn::Path>,
6363
}
6464

65-
#[derive(Debug, FromMeta)]
66-
enum ItemFnMeta {
67-
Export,
68-
}
69-
7065
#[derive(Debug, FromMeta)]
7166
struct ImplTraitForContractMeta {
7267
/// Fully qualified path of the trait.
@@ -143,9 +138,33 @@ pub fn casper(attrs: TokenStream, item: TokenStream) -> TokenStream {
143138
generate_impl_for_contract(entry_points)
144139
}
145140
} else if let Ok(func) = syn::parse::<ItemFn>(item.clone()) {
146-
let func_meta = ItemFnMeta::from_list(&attr_args).unwrap();
147-
match func_meta {
148-
ItemFnMeta::Export => generate_export_function(&func),
141+
let mut is_export = false;
142+
let mut abi_convention: Option<syn::Path> = None;
143+
for meta in &attr_args {
144+
match meta {
145+
ast::NestedMeta::Meta(syn::Meta::Path(path)) => {
146+
if path.is_ident("export") {
147+
is_export = true;
148+
}
149+
}
150+
ast::NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
151+
if nv.path.is_ident("abi_convention") {
152+
if let syn::Expr::Path(expr_path) = &nv.value {
153+
abi_convention = Some(expr_path.path.clone());
154+
}
155+
}
156+
}
157+
_ => {}
158+
}
159+
}
160+
if is_export {
161+
generate_export_function(&func, abi_convention)
162+
} else {
163+
let err = syn::Error::new(
164+
Span::call_site(),
165+
"Unsupported function attribute; expected #[casper(export ...)]",
166+
);
167+
TokenStream::from(err.to_compile_error())
149168
}
150169
} else {
151170
let err = syn::Error::new(
@@ -248,7 +267,7 @@ fn process_casper_message_for_struct(
248267
.into()
249268
}
250269

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

293+
let resolve_abi_convention = match abi_convention {
294+
Some(convention_path) => quote! { #convention_path },
295+
None => quote! { casper_contract_sdk::serializers::AbiConvention::Positional },
296+
};
297+
298+
// Generate return handling tokens
299+
let handle_ret = match &func.sig.output {
300+
syn::ReturnType::Default => {
301+
quote! {
302+
match resolved_abi_convention {
303+
casper_contract_sdk::serializers::AbiConvention::Positional => {
304+
casper_contract_sdk::casper::ret(flags, None)
305+
}
306+
casper_contract_sdk::serializers::AbiConvention::Named => {
307+
let ret_bytes = casper_contract_sdk::serializers::borsh::to_vec(&casper_contract_sdk::compat::types::CLValue::UNIT).expect("Failed to serialize return CLValue");
308+
casper_contract_sdk::casper::ret(flags, Some(&ret_bytes))
309+
}
310+
}
311+
}
312+
}
313+
syn::ReturnType::Type(_, _ty) => {
314+
quote! {
315+
let ret_bytes = match resolved_abi_convention {
316+
casper_contract_sdk::serializers::AbiConvention::Positional => {
317+
casper_contract_sdk::serializers::borsh::to_vec(&_ret).expect("Failed to serialize return value")
318+
}
319+
casper_contract_sdk::serializers::AbiConvention::Named => {
320+
let ret_clvalue = casper_contract_sdk::compat::types::CLValue::from_t(&_ret).expect("Failed to convert return value to CLValue");
321+
casper_contract_sdk::serializers::borsh::to_vec(&ret_clvalue).expect("Failed to serialize return CLValue")
322+
}
323+
};
324+
casper_contract_sdk::casper::ret(flags, Some(&ret_bytes))
325+
}
326+
}
327+
};
328+
274329
let _ctor_name = format_ident!("{func_name}_ctor");
275330

276331
let exported_func_name = format_ident!("__casper_export_{func_name}");
@@ -288,9 +343,38 @@ fn generate_export_function(func: &ItemFn) -> TokenStream {
288343
struct Arguments {
289344
#(#arg_names: #arg_types,)*
290345
}
346+
347+
let mut flags = casper_contract_sdk::common::flags::ReturnFlags::empty();
291348
let input = casper_contract_sdk::prelude::casper::copy_input();
292-
let args: Arguments = casper_contract_sdk::serializers::borsh::from_slice(&input).unwrap();
349+
let resolved_abi_convention = #resolve_abi_convention;
350+
let args: Arguments = {
351+
match resolved_abi_convention {
352+
casper_contract_sdk::serializers::AbiConvention::Positional => {
353+
casper_contract_sdk::serializers::borsh::from_slice(&input).unwrap()
354+
}
355+
casper_contract_sdk::serializers::AbiConvention::Named => {
356+
let runtime_args: casper_contract_sdk::compat::types::RuntimeArgs =
357+
casper_contract_sdk::serializers::borsh::from_slice(&input).unwrap();
358+
#(
359+
let #arg_names: #arg_types = {
360+
let cl_value = runtime_args.get(stringify!(#arg_names)).unwrap_or_else(|| panic!(concat!("Failed to get named argument \"", stringify!(#arg_names), "\"")));
361+
cl_value.to_t::<#arg_types>().unwrap_or_else(|error| {
362+
panic!(concat!("Failed to convert named argument \"", stringify!(#arg_names), "\": {}"), error)
363+
})
364+
};
365+
)*
366+
Arguments {
367+
#(
368+
#arg_names,
369+
)*
370+
}
371+
}
372+
}
373+
};
374+
293375
let _ret = #func_name(#(args.#arg_names,)*);
376+
377+
#handle_ret
294378
}
295379

296380
#[cfg(not(target_arch = "wasm32"))]
@@ -324,7 +408,7 @@ fn generate_export_function(func: &ItemFn) -> TokenStream {
324408
},
325409
)*
326410
],
327-
abi_convention: casper_contract_sdk::serializers::AbiConvention::Positional, // todo
411+
abi_convention: #resolve_abi_convention,
328412
result_decl: {
329413
casper_contract_sdk::abi::collector::AbiType {
330414
type_name: core::any::type_name::<#ret>,
@@ -511,7 +595,7 @@ fn generate_impl_for_contract(mut entry_points: ItemImpl) -> TokenStream {
511595
Some(quote! {
512596
match #resolve_abi_convention {
513597
casper_contract_sdk::serializers::AbiConvention::Positional => {
514-
// Do nothing as lack of ret is synonymous with returning empty bytes (unit serializes to empty buffer)
598+
casper_contract_sdk::casper::ret(flags, None)
515599
}
516600
casper_contract_sdk::serializers::AbiConvention::Named => {
517601
// For a named ABI convention we'd always ret with the bytes of unit CLValue.
@@ -1204,7 +1288,7 @@ fn casper_trait_definition(mut item_trait: ItemTrait, trait_meta: TraitMeta) ->
12041288
Some(quote! {
12051289
match #resolve_abi_convention {
12061290
casper_contract_sdk::serializers::AbiConvention::Positional => {
1207-
// Do nothing as lack of ret is synonymous with returning empty bytes (unit serializes to empty buffer)
1291+
casper_contract_sdk::casper::ret(flags, None)
12081292
}
12091293
casper_contract_sdk::serializers::AbiConvention::Named => {
12101294
// For a named ABI convention we'd always ret with the bytes of unit CLValue.

0 commit comments

Comments
 (0)