From b69369cbf69c58b0090e5a5242f935ef86ac3a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Fri, 8 Mar 2024 17:15:47 +0100 Subject: [PATCH 1/4] wip --- core/src/args.rs | 225 ++++++++++++++++++ core/src/contract_container.rs | 16 +- core/src/contract_context.rs | 3 + core/src/contract_def.rs | 7 +- core/src/contract_env.rs | 13 +- core/src/entry_point_callback.rs | 7 +- core/src/lib.rs | 1 + examples/Odra.toml | 3 + examples/src/features/collecting_events.rs | 3 +- examples/src/features/mod.rs | 1 + examples/src/features/optional_args.rs | 53 +++++ odra-casper/wasm-env/src/host_functions.rs | 18 +- odra-casper/wasm-env/src/wasm_contract_env.rs | 11 +- odra-macros/src/ast/contract_ref_item.rs | 15 +- odra-macros/src/ast/deployer_item.rs | 31 +-- odra-macros/src/ast/entrypoints_item.rs | 55 +---- odra-macros/src/ast/exec_parts.rs | 3 +- odra-macros/src/ast/external_contract_item.rs | 4 +- odra-macros/src/ast/host_ref_item.rs | 19 +- odra-macros/src/ast/ref_utils.rs | 5 +- odra-macros/src/ast/test_parts.rs | 28 ++- odra-macros/src/ast/wasm_parts.rs | 59 ++--- odra-macros/src/ast/wasm_parts_utils.rs | 12 +- odra-macros/src/lib.rs | 13 +- odra-macros/src/test_utils.rs | 2 +- odra-macros/src/utils/expr.rs | 17 +- odra-macros/src/utils/ty.rs | 12 +- odra-macros/tests/test.rs | 12 + odra-vm/src/odra_vm_contract_env.rs | 6 +- odra-vm/src/vm/odra_vm.rs | 4 +- odra/src/lib.rs | 4 +- 31 files changed, 475 insertions(+), 187 deletions(-) create mode 100644 core/src/args.rs create mode 100644 examples/src/features/optional_args.rs diff --git a/core/src/args.rs b/core/src/args.rs new file mode 100644 index 00000000..0a223dbd --- /dev/null +++ b/core/src/args.rs @@ -0,0 +1,225 @@ +//! This module provides types and traits for working with entrypoint arguments. + +use crate::{contract_def::Argument, prelude::*, ContractEnv, ExecutionError}; +use casper_types::{ + bytesrepr::{FromBytes, ToBytes}, CLType, CLTyped, Parameter, RuntimeArgs +}; + +/// A type that represents an entrypoint arg that may or may not be present. +#[derive(Debug, Clone)] +pub enum Maybe { + /// A value is present. + Some(T), + /// No value is present. + None +} + +impl Maybe { + /// Returns `true` if the value is present. + pub fn is_some(&self) -> bool { + matches!(self, Maybe::Some(_)) + } + + /// Returns `true` if the value is not present. + pub fn is_none(&self) -> bool { + matches!(self, Maybe::None) + } + + /// Unwraps the value. + /// If the value is not present, the contract reverts with an `ExecutionError::UnwrapError`. + pub fn unwrap(self, env: &ContractEnv) -> T { + match self { + Maybe::Some(value) => value, + Maybe::None => env.revert(ExecutionError::UnwrapError) + } + } +} + +impl Maybe { + /// Unwraps the value or returns the default value. + pub fn unwrap_or_default(self) -> T { + match self { + Maybe::Some(value) => value, + Maybe::None => T::default() + } + } +} + +impl ToBytes for Maybe { + fn to_bytes(&self) -> Result, casper_types::bytesrepr::Error> { + match self { + Maybe::Some(value) => value.to_bytes(), + Maybe::None => Ok(Vec::new()) + } + } + + fn serialized_length(&self) -> usize { + match self { + Maybe::Some(value) => value.serialized_length(), + Maybe::None => 0 + } + } +} + +impl FromBytes for Maybe { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), casper_types::bytesrepr::Error> { + let res = T::from_bytes(bytes); + if let Ok((value, rem)) = res { + Ok((Maybe::Some(value), rem)) + } else { + Ok((Maybe::None, bytes)) + } + } + + fn from_vec(bytes: Vec) -> Result<(Self, Vec), casper_types::bytesrepr::Error> { + Self::from_bytes(bytes.as_slice()).map(|(x, remainder)| (x, Vec::from(remainder))) + } +} + +/// A trait for types that can be used as entrypoint arguments. +pub trait EntrypointArgument: Sized { + /// Returns `true` if the argument is required. + fn is_required() -> bool; + /// Returns the CLType of the argument. + fn cl_type() -> CLType; + /// Inserts the argument into the runtime args. + fn insert_runtime_arg(self, name: &str, args: &mut RuntimeArgs); + fn unwrap(value: Option, env: &ContractEnv) -> Self; +} + +impl EntrypointArgument for Maybe { + fn is_required() -> bool { + false + } + + fn cl_type() -> CLType { + T::cl_type() + } + + fn insert_runtime_arg(self, name: &str, args: &mut RuntimeArgs) { + if let Maybe::Some(v) = self { + let _ = args.insert(name, v); + } + } + + fn unwrap(value: Option, env: &ContractEnv) -> Self { + match value { + Some(v) => v, + None => Maybe::None + } + } +} + +impl EntrypointArgument for T { + fn is_required() -> bool { + true + } + + fn cl_type() -> CLType { + T::cl_type() + } + + fn insert_runtime_arg(self, name: &str, args: &mut RuntimeArgs) { + let _ = args.insert(name, self); + } + + fn unwrap(value: Option, env: &ContractEnv) -> Self { + match value { + Some(v) => v, + None => env.revert(ExecutionError::UnwrapError) + + } + } +} + +/// Converts a type into Casper's entrypoint argument representation. +pub fn into_parameter(name: &str) -> Option { + match T::is_required() { + true => Some(Parameter::new(name, T::cl_type())), + false => None + } +} + +/// Converts a type into Odra's entrypoint argument representation. +pub fn into_argument(name: &str) -> Argument { + Argument { + ident: name.to_string(), + ty: T::cl_type(), + is_ref: false, + is_slice: false, + is_required: T::is_required() + } +} + +#[cfg(test)] +mod tests { + use casper_types::U256; + + use crate::{contract_context::MockContractContext, Address}; + + use super::*; + + #[test] + fn test_maybe() { + let some = Maybe::Some(1); + let none: Maybe = Maybe::None; + + let ctx = MockContractContext::new(); + let env = ContractEnv::new(0, Rc::new(RefCell::new(ctx))); + + assert_eq!(some.is_some(), true); + assert_eq!(some.is_none(), false); + assert_eq!(some.clone().unwrap(&env), 1); + assert_eq!(some.unwrap_or_default(), 1); + + assert_eq!(none.is_some(), false); + assert_eq!(none.is_none(), true); + assert_eq!(none.unwrap_or_default(), 0); + } + + #[test] + #[should_panic(expected = "revert")] + fn unwrap_on_none() { + let none: Maybe = Maybe::None; + let mut ctx = MockContractContext::new(); + ctx.expect_revert().returning(|_| panic!("revert")); + let env = ContractEnv::new(0, Rc::new(RefCell::new(ctx))); + + none.unwrap(&env); + } + + #[test] + fn test_into_args() { + let args = vec![ + into_argument::>("arg1"), + into_argument::("arg2"), + into_argument::>("arg3"), + ]; + + assert_eq!(args.len(), 3); + } + + + #[test] + fn test_into_casper_parameters() { + let params = vec![ + into_parameter::>("arg1"), + into_parameter::>("arg2"), + into_parameter::>>("arg3"), + into_parameter::
("arg4"), + ] + .into_iter() + .filter_map(|x| x) + .collect::>(); + + let params = vec![ + into_parameter::("name"), + into_parameter::>("metadata"), + ] + .into_iter() + .filter_map(|x| x) + .collect::>(); + + assert_eq!(params.len(), 2); + } +} diff --git a/core/src/contract_container.rs b/core/src/contract_container.rs index 75555491..3a955ff1 100644 --- a/core/src/contract_container.rs +++ b/core/src/contract_container.rs @@ -105,7 +105,7 @@ mod tests { #[test] fn test_call_valid_entrypoint_with_wrong_arg_name() { // Given an instance with a single entrypoint with one arg named "first". - let instance = ContractContainer::with_entrypoint(vec![("first", CLType::U32)]); + let instance = ContractContainer::with_entrypoint(vec!["first"]); // When call the registered entrypoint with an arg named "second". let call_def = CallDef::new(TEST_ENTRYPOINT, false, runtime_args! { "second" => 0u32 }); @@ -118,7 +118,7 @@ mod tests { #[test] fn test_call_valid_entrypoint_with_wrong_arg_type() { // Given an instance with a single entrypoint with one arg named "first". - let instance = ContractContainer::with_entrypoint(vec![("first", CLType::U32)]); + let instance = ContractContainer::with_entrypoint(vec!["first"]); // When call the registered entrypoint with an arg named "second". let call_def = CallDef::new(TEST_ENTRYPOINT, false, runtime_args! { "first" => true }); @@ -137,7 +137,7 @@ mod tests { #[test] fn test_call_valid_entrypoint_with_missing_arg() { // Given an instance with a single entrypoint with one arg named "first". - let instance = ContractContainer::with_entrypoint(vec![("first", CLType::U32)]); + let instance = ContractContainer::with_entrypoint(vec!["first"]); // When call a valid entrypoint without args. let call_def = CallDef::new(TEST_ENTRYPOINT, false, RuntimeArgs::new()); @@ -151,9 +151,9 @@ mod tests { fn test_many_missing_args() { // Given an instance with a single entrypoint with "first", "second" and "third" args. let instance = ContractContainer::with_entrypoint(vec![ - ("first", CLType::U32), - ("second", CLType::U32), - ("third", CLType::U32), + "first", + "second", + "third", ]); // When call a valid entrypoint with a single valid args, @@ -178,11 +178,11 @@ mod tests { } } - fn with_entrypoint(args: Vec<(&str, CLType)>) -> Self { + fn with_entrypoint(args: Vec<&str>) -> Self { let entry_points = vec![EntryPoint::new( String::from(TEST_ENTRYPOINT), args.iter() - .map(|(name, ty)| Argument::new(String::from(*name), ty.to_owned())) + .map(|name| Argument::new::(String::from(*name))) .collect() )]; let mut ctx = MockHostContext::new(); diff --git a/core/src/contract_context.rs b/core/src/contract_context.rs index 01c3fe4d..efeb71a2 100644 --- a/core/src/contract_context.rs +++ b/core/src/contract_context.rs @@ -101,6 +101,9 @@ pub trait ContractContext { /// The value of the named argument as a byte array. fn get_named_arg_bytes(&self, name: &str) -> Bytes; + /// Similar to `get_named_arg_bytes`, but returns `None` if the named argument is not present. + fn get_opt_named_arg_bytes(&self, name: &str) -> Option; + /// Handles the value attached to the call. Sets the value in the contract context. fn handle_attached_value(&self); diff --git a/core/src/contract_def.rs b/core/src/contract_def.rs index af5f78a1..4dfde56c 100644 --- a/core/src/contract_def.rs +++ b/core/src/contract_def.rs @@ -38,7 +38,9 @@ pub struct Argument { /// `true` if the argument is a reference. pub is_ref: bool, /// `true` if the argument is a slice. - pub is_slice: bool + pub is_slice: bool, + /// `true` if the argument is required. + pub is_required: bool } /// Defines an event. @@ -168,7 +170,8 @@ impl IntoEvent for T { ident: name.clone(), ty: ty.clone().downcast(), is_ref: false, - is_slice: false + is_slice: false, + is_required: true }) .collect::>(); Event { ident, args } diff --git a/core/src/contract_env.rs b/core/src/contract_env.rs index 5ce4e09a..887b99d1 100644 --- a/core/src/contract_env.rs +++ b/core/src/contract_env.rs @@ -1,3 +1,4 @@ +use crate::args::EntrypointArgument; use crate::call_def::CallDef; use crate::casper_types::bytesrepr::{Bytes, FromBytes, ToBytes}; use crate::casper_types::{CLTyped, U512}; @@ -236,9 +237,15 @@ impl ExecutionEnv { /// /// The deserialized value of the named argument. If the argument does not exist or deserialization fails, /// the contract will revert. - pub fn get_named_arg(&self, name: &str) -> T { - let bytes = self.env.backend.borrow().get_named_arg_bytes(name); - deserialize_bytes(bytes, &self.env) + pub fn get_named_arg(&self, name: &str) -> T { + if T::is_required() { + let bytes = self.env.backend.borrow().get_named_arg_bytes(name); + deserialize_bytes(bytes, &self.env) + } else { + let bytes = self.env.backend.borrow().get_opt_named_arg_bytes(name); + let result = bytes.map(|bytes| deserialize_bytes(bytes, &self.env)); + T::unwrap(result, &self.env) + } } } diff --git a/core/src/entry_point_callback.rs b/core/src/entry_point_callback.rs index 8ffafc0a..7cf3c6e7 100644 --- a/core/src/entry_point_callback.rs +++ b/core/src/entry_point_callback.rs @@ -2,6 +2,7 @@ use casper_types::CLType; +use crate::args::EntrypointArgument; use crate::call_def::CallDef; use crate::casper_types::bytesrepr::Bytes; use crate::{host::HostEnv, prelude::*, ContractEnv, OdraResult}; @@ -80,12 +81,12 @@ pub struct Argument { /// The name of the argument. pub name: String, /// The type of the argument. - pub ty: CLType + pub ty: CLType, } impl Argument { /// Creates a new instance of `Argument`. - pub fn new(name: String, ty: CLType) -> Self { - Self { name, ty } + pub fn new(name: String) -> Self { + Self { name, ty: T::cl_type() } } } diff --git a/core/src/lib.rs b/core/src/lib.rs index ab31e974..001e4f97 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -5,6 +5,7 @@ extern crate alloc; mod address; +pub mod args; pub mod arithmetic; mod call_def; mod call_result; diff --git a/examples/Odra.toml b/examples/Odra.toml index 435a8992..c9516eac 100644 --- a/examples/Odra.toml +++ b/examples/Odra.toml @@ -60,3 +60,6 @@ fqn = "features::pauseable::PauseableCounter" [[contracts]] fqn = "features::livenet::LivenetContract" + +[[contracts]] +fqn = "features::optional_args::Token" diff --git a/examples/src/features/collecting_events.rs b/examples/src/features/collecting_events.rs index ea70156b..018cb69e 100644 --- a/examples/src/features/collecting_events.rs +++ b/examples/src/features/collecting_events.rs @@ -91,7 +91,8 @@ mod test { ident: "msg".to_string(), ty: CLType::String, is_ref: false, - is_slice: false + is_slice: false, + is_required: true }; Event { ident: "Info".to_string(), diff --git a/examples/src/features/mod.rs b/examples/src/features/mod.rs index b5a640b4..bce62bd3 100644 --- a/examples/src/features/mod.rs +++ b/examples/src/features/mod.rs @@ -14,3 +14,4 @@ pub mod reentrancy_guard; pub mod signature_verifier; pub mod storage; pub mod testing; +pub mod optional_args; \ No newline at end of file diff --git a/examples/src/features/optional_args.rs b/examples/src/features/optional_args.rs new file mode 100644 index 00000000..d11453c8 --- /dev/null +++ b/examples/src/features/optional_args.rs @@ -0,0 +1,53 @@ +#![allow(missing_docs)] + +use odra::{args::Maybe, Var}; +use odra::prelude::*; + +#[odra::module] +pub struct Token { + name: Var, + metadata: Var, +} + +#[odra::module] +impl Token { + pub fn init(&mut self, name: String, metadata: Maybe) { + self.name.set(name.clone()); + self.metadata.set(metadata.unwrap_or_default()); + } + + pub fn metadata(&self) -> String { + self.metadata.get_or_default() + } +} + + +#[cfg(test)] +mod test { + use crate::features::optional_args::TokenInitArgs; + + use super::*; + use odra::host::Deployer; + + #[test] + fn test_no_opt_arg() { + let test_env = odra_test::env(); + let init_args = TokenInitArgs { + name: String::from("MyToken"), + metadata: Maybe::None + }; + let my_contract = TokenHostRef::deploy(&test_env, init_args); + assert_eq!(my_contract.metadata(), String::from("")); + } + + #[test] + fn test_with_opt_arg() { + let test_env = odra_test::env(); + let init_args = TokenInitArgs { + name: String::from("MyToken"), + metadata: Maybe::Some(String::from("MyMetadata")) + }; + let my_contract = TokenHostRef::deploy(&test_env, init_args); + assert_eq!(my_contract.metadata(), String::from("MyMetadata")); + } +} diff --git a/odra-casper/wasm-env/src/host_functions.rs b/odra-casper/wasm-env/src/host_functions.rs index c66fe568..78c92a00 100644 --- a/odra-casper/wasm-env/src/host_functions.rs +++ b/odra-casper/wasm-env/src/host_functions.rs @@ -19,7 +19,7 @@ use casper_contract::{ unwrap_or_revert::UnwrapOrRevert }; use core::mem::MaybeUninit; -use odra_core::casper_event_standard::{self, Schema, Schemas}; +use odra_core::{args::EntrypointArgument, casper_event_standard::{self, Schema, Schemas}}; use odra_core::casper_types::{ api_error, bytesrepr::{Bytes, FromBytes, ToBytes}, @@ -116,8 +116,8 @@ pub fn revert(error: u16) -> ! { /// Returns given named argument passed to the host. The result is not deserialized, /// is returned as a `Vec`. -pub fn get_named_arg(name: &str) -> Vec { - let arg_size = get_named_arg_size(name); +pub fn get_named_arg(name: &str) -> Result, ApiError> { + let arg_size = get_named_arg_size(name)?; if arg_size > 0 { let data_non_null_ptr = contract_api::alloc_bytes(arg_size); let ret = unsafe { @@ -129,11 +129,11 @@ pub fn get_named_arg(name: &str) -> Vec { ) }; if ret != 0 { - runtime::revert(ApiError::from(ret as u32)) + return Err(ApiError::from(ret as u32)) } - unsafe { Vec::from_raw_parts(data_non_null_ptr.as_ptr(), arg_size, arg_size) } + unsafe { Ok(Vec::from_raw_parts(data_non_null_ptr.as_ptr(), arg_size, arg_size)) } } else { - Vec::new() + Ok(Vec::new()) } } @@ -536,7 +536,7 @@ fn read_host_buffer_into(dest: &mut [u8]) -> Result { Ok(unsafe { bytes_written.assume_init() }) } -fn get_named_arg_size(name: &str) -> usize { +fn get_named_arg_size(name: &str) -> Result { let mut arg_size: usize = 0; let ret = unsafe { ext_ffi::casper_get_named_arg_size( @@ -546,7 +546,7 @@ fn get_named_arg_size(name: &str) -> usize { ) }; match ret { - 0 => arg_size, - _ => runtime::revert(ApiError::from(ret as u32)) + 0 => Ok(arg_size), + _ => Err(ApiError::from(ret as u32)) } } diff --git a/odra-casper/wasm-env/src/wasm_contract_env.rs b/odra-casper/wasm-env/src/wasm_contract_env.rs index e99dfbe9..c7886bad 100644 --- a/odra-casper/wasm-env/src/wasm_contract_env.rs +++ b/odra-casper/wasm-env/src/wasm_contract_env.rs @@ -1,4 +1,5 @@ use crate::host_functions; +use casper_contract::contract_api::runtime; use casper_types::bytesrepr::ToBytes; use casper_types::U512; use odra_core::casper_types; @@ -57,7 +58,15 @@ impl ContractContext for WasmContractEnv { } fn get_named_arg_bytes(&self, name: &str) -> Bytes { - host_functions::get_named_arg(name).into() + let result = host_functions::get_named_arg(name); + match result { + Ok(bytes) => Bytes::from(bytes), + Err(err) => runtime::revert(err) + } + } + + fn get_opt_named_arg_bytes(&self, name: &str) -> Option { + host_functions::get_named_arg(name).ok().map(Bytes::from) } fn handle_attached_value(&self) { diff --git a/odra-macros/src/ast/contract_ref_item.rs b/odra-macros/src/ast/contract_ref_item.rs index 6094d8f6..2b172ca0 100644 --- a/odra-macros/src/ast/contract_ref_item.rs +++ b/odra-macros/src/ast/contract_ref_item.rs @@ -199,7 +199,7 @@ mod ref_item_tests { true, { let mut named_args = odra::casper_types::RuntimeArgs::new(); - let _ = named_args.insert("total_supply", total_supply.clone()); + odra::args::EntrypointArgument::insert_runtime_arg(total_supply.clone(), "total_supply", &mut named_args); named_args } ), @@ -238,7 +238,7 @@ mod ref_item_tests { } /// Approve. - pub fn approve(&mut self, to: &Address, amount: &U256) { + pub fn approve(&mut self, to: &Address, amount: &U256, msg: Maybe) { self.env .call_contract( self.address, @@ -247,8 +247,9 @@ mod ref_item_tests { true, { let mut named_args = odra::casper_types::RuntimeArgs::new(); - let _ = named_args.insert("to", to.clone()); - let _ = named_args.insert("amount", amount.clone()); + odra::args::EntrypointArgument::insert_runtime_arg(to.clone(), "to", &mut named_args); + odra::args::EntrypointArgument::insert_runtime_arg(amount.clone(), "amount", &mut named_args); + odra::args::EntrypointArgument::insert_runtime_arg(msg.clone(), "msg", &mut named_args); named_args }, ), @@ -265,8 +266,8 @@ mod ref_item_tests { false, { let mut named_args = odra::casper_types::RuntimeArgs::new(); - let _ = named_args.insert("to", to.clone()); - let _ = named_args.insert("amount", amount.clone()); + odra::args::EntrypointArgument::insert_runtime_arg(to.clone(), "to", &mut named_args); + odra::args::EntrypointArgument::insert_runtime_arg(amount.clone(), "amount", &mut named_args); named_args }, ), @@ -395,7 +396,7 @@ mod ref_item_tests { true, { let mut named_args = odra::casper_types::RuntimeArgs::new(); - let _ = named_args.insert("new_owner", new_owner.clone()); + odra::args::EntrypointArgument::insert_runtime_arg(new_owner.clone(), "new_owner", &mut named_args); named_args }, ), diff --git a/odra-macros/src/ast/deployer_item.rs b/odra-macros/src/ast/deployer_item.rs index b311f189..40e5e668 100644 --- a/odra-macros/src/ast/deployer_item.rs +++ b/odra-macros/src/ast/deployer_item.rs @@ -210,10 +210,7 @@ mod deployer_impl { odra::entry_point_callback::EntryPoint::new( odra::prelude::string::String::from("init"), odra::prelude::vec![ - odra::entry_point_callback::Argument::new( - odra::prelude::string::String::from("total_supply"), - as odra::casper_types::CLTyped>::cl_type() - ) + odra::entry_point_callback::Argument::new:: >(odra::prelude::string::String::from("total_supply")) ] ), odra::entry_point_callback::EntryPoint::new(odra::prelude::string::String::from("total_supply"), odra::prelude::vec![]), @@ -221,28 +218,18 @@ mod deployer_impl { odra::entry_point_callback::EntryPoint::new( odra::prelude::string::String::from("approve"), odra::prelude::vec![ - odra::entry_point_callback::Argument::new( - odra::prelude::string::String::from("to"), -
::cl_type() - ), - odra::entry_point_callback::Argument::new( - odra::prelude::string::String::from("amount"), - ::cl_type() - ) + odra::entry_point_callback::Argument::new::
(odra::prelude::string::String::from("to")), + odra::entry_point_callback::Argument::new::(odra::prelude::string::String::from("amount")), + odra::entry_point_callback::Argument::new:: >(odra::prelude::string::String::from("msg")) ] ), odra::entry_point_callback::EntryPoint::new( odra::prelude::string::String::from("airdrop"), odra::prelude::vec![ - odra::entry_point_callback::Argument::new( - odra::prelude::string::String::from("to"), - as odra::casper_types::CLTyped>::cl_type() - ), - odra::entry_point_callback::Argument::new( - odra::prelude::string::String::from("amount"), - ::cl_type() - ) - ]) + odra::entry_point_callback::Argument::new:: >(odra::prelude::string::String::from("to")), + odra::entry_point_callback::Argument::new::(odra::prelude::string::String::from("amount")) + ] + ) ]; odra::entry_point_callback::EntryPointsCaller::new(env.clone(), entry_points, |contract_env, call_def| { @@ -321,7 +308,7 @@ mod deployer_impl { odra::entry_point_callback::EntryPoint::new(odra::prelude::string::String::from("total_supply"), odra::prelude::vec![]), odra::entry_point_callback::EntryPoint::new(odra::prelude::string::String::from("get_owner"), odra::prelude::vec![]), odra::entry_point_callback::EntryPoint::new(odra::prelude::string::String::from("set_owner"), odra::prelude::vec![ - odra::entry_point_callback::Argument::new(odra::prelude::string::String::from("new_owner"),
::cl_type()) + odra::entry_point_callback::Argument::new::
(odra::prelude::string::String::from("new_owner")) ]), odra::entry_point_callback::EntryPoint::new(odra::prelude::string::String::from("name"), odra::prelude::vec![]), odra::entry_point_callback::EntryPoint::new(odra::prelude::string::String::from("symbol"), odra::prelude::vec![]) diff --git a/odra-macros/src/ast/entrypoints_item.rs b/odra-macros/src/ast/entrypoints_item.rs index 0767c068..51268180 100644 --- a/odra-macros/src/ast/entrypoints_item.rs +++ b/odra-macros/src/ast/entrypoints_item.rs @@ -100,17 +100,9 @@ fn entrypoint_args(f: &FnIR) -> syn::Result { .named_args() .iter() .map(|arg| { - let ty_arg = utils::ty::entry_point_def_arg(); let ident = arg.name_str()?; - let ty = utils::expr::as_cl_type(&arg.ty()?); - - let expr: syn::Expr = parse_quote!(#ty_arg { - ident: String::from(#ident), - ty: #ty, - is_ref: false, - is_slice: false - }); - Ok(expr) + let ty = utils::ty::unreferenced_ty(&arg.ty()?); + Ok(utils::expr::into_arg(ty, ident)) }) .collect::>>()?; Ok(parse_quote!(vec![#(#args),*])) @@ -133,12 +125,7 @@ mod test { odra::contract_def::Entrypoint { ident: String::from("init"), args: vec![ - odra::contract_def::Argument { - ident: String::from("total_supply"), - ty: as odra::casper_types::CLTyped>::cl_type(), - is_ref: false, - is_slice: false - } + odra::args::into_argument:: >("total_supply") ], is_mut: true, ret: <() as odra::casper_types::CLTyped>::cl_type(), @@ -164,18 +151,9 @@ mod test { odra::contract_def::Entrypoint { ident: String::from("approve"), args: vec![ - odra::contract_def::Argument { - ident: String::from("to"), - ty:
::cl_type(), - is_ref: false, - is_slice: false - }, - odra::contract_def::Argument { - ident: String::from("amount"), - ty: ::cl_type(), - is_ref: false, - is_slice: false - } + odra::args::into_argument::
("to"), + odra::args::into_argument::("amount"), + odra::args::into_argument:: >("msg") ], is_mut: true, ret: <() as odra::casper_types::CLTyped>::cl_type(), @@ -185,18 +163,8 @@ mod test { odra::contract_def::Entrypoint { ident: String::from("airdrop"), args: vec![ - odra::contract_def::Argument { - ident: String::from("to"), - ty: as odra::casper_types::CLTyped>::cl_type(), - is_ref: false, - is_slice: false - }, - odra::contract_def::Argument { - ident: String::from("amount"), - ty: ::cl_type(), - is_ref: false, - is_slice: false - } + odra::args::into_argument:: >("to"), + odra::args::into_argument::("amount") ], is_mut: false, ret: <() as odra::casper_types::CLTyped>::cl_type(), @@ -268,12 +236,7 @@ mod test { odra::contract_def::Entrypoint { ident: String::from("set_owner"), args: vec![ - odra::contract_def::Argument { - ident: String::from("new_owner"), - ty:
::cl_type(), - is_ref: false, - is_slice: false - } + odra::args::into_argument::
("new_owner") ], is_mut: true, ret: <() as odra::casper_types::CLTyped>::cl_type(), diff --git a/odra-macros/src/ast/exec_parts.rs b/odra-macros/src/ast/exec_parts.rs index 931f5044..fce2e54d 100644 --- a/odra-macros/src/ast/exec_parts.rs +++ b/odra-macros/src/ast/exec_parts.rs @@ -273,8 +273,9 @@ mod test { exec_env.non_reentrant_before(); let to = exec_env.get_named_arg::
("to"); let amount = exec_env.get_named_arg::("amount"); + let msg = exec_env.get_named_arg::>("msg"); let mut contract = ::new(env_rc); - let result = contract.approve(&to, &amount); + let result = contract.approve(&to, &amount, msg); exec_env.non_reentrant_after(); return result; } diff --git a/odra-macros/src/ast/external_contract_item.rs b/odra-macros/src/ast/external_contract_item.rs index c26d5a5d..ca6422df 100644 --- a/odra-macros/src/ast/external_contract_item.rs +++ b/odra-macros/src/ast/external_contract_item.rs @@ -74,7 +74,7 @@ mod test { false, { let mut named_args = odra::casper_types::RuntimeArgs::new(); - let _ = named_args.insert("owner", owner.clone()); + odra::args::EntrypointArgument::insert_runtime_arg(owner.clone(), "owner", &mut named_args); named_args } ), @@ -150,7 +150,7 @@ mod test { if self.attached_value > odra::casper_types::U512::zero() { let _ = named_args.insert("amount", self.attached_value); } - let _ = named_args.insert("owner", owner.clone()); + odra::args::EntrypointArgument::insert_runtime_arg(owner.clone(), "owner", &mut named_args); named_args } ).with_amount(self.attached_value), diff --git a/odra-macros/src/ast/host_ref_item.rs b/odra-macros/src/ast/host_ref_item.rs index a7cdf7ca..77385cb1 100644 --- a/odra-macros/src/ast/host_ref_item.rs +++ b/odra-macros/src/ast/host_ref_item.rs @@ -406,8 +406,8 @@ mod ref_item_tests { } /// Approve. - pub fn approve(&mut self, to: &Address, amount: &U256) { - self.try_approve(to, amount).unwrap() + pub fn approve(&mut self, to: &Address, amount: &U256, msg: Maybe) { + self.try_approve(to, amount, msg).unwrap() } /// Airdrops the given amount to the given addresses. @@ -431,8 +431,7 @@ mod ref_item_tests { if self.attached_value > odra::casper_types::U512::zero() { let _ = named_args.insert("amount", self.attached_value); } - let _ = named_args - .insert("total_supply", total_supply.clone()); + odra::args::EntrypointArgument::insert_runtime_arg(total_supply.clone(), "total_supply", &mut named_args); named_args }, ) @@ -484,6 +483,7 @@ mod ref_item_tests { &mut self, to: &Address, amount: &U256, + msg: Maybe, ) -> odra::OdraResult<()> { self.env .call_contract( @@ -496,8 +496,9 @@ mod ref_item_tests { if self.attached_value > odra::casper_types::U512::zero() { let _ = named_args.insert("amount", self.attached_value); } - let _ = named_args.insert("to", to.clone()); - let _ = named_args.insert("amount", amount.clone()); + odra::args::EntrypointArgument::insert_runtime_arg(to.clone(), "to", &mut named_args); + odra::args::EntrypointArgument::insert_runtime_arg(amount.clone(), "amount", &mut named_args); + odra::args::EntrypointArgument::insert_runtime_arg(msg.clone(), "msg", &mut named_args); named_args }, ) @@ -517,8 +518,8 @@ mod ref_item_tests { if self.attached_value > odra::casper_types::U512::zero() { let _ = named_args.insert("amount", self.attached_value); } - let _ = named_args.insert("to", to.clone()); - let _ = named_args.insert("amount", amount.clone()); + odra::args::EntrypointArgument::insert_runtime_arg(to.clone(), "to", &mut named_args); + odra::args::EntrypointArgument::insert_runtime_arg(amount.clone(), "amount", &mut named_args); named_args } ).with_amount(self.attached_value), @@ -763,7 +764,7 @@ mod ref_item_tests { if self.attached_value > odra::casper_types::U512::zero() { let _ = named_args.insert("amount", self.attached_value); } - let _ = named_args.insert("new_owner", new_owner.clone()); + odra::args::EntrypointArgument::insert_runtime_arg(new_owner.clone(), "new_owner", &mut named_args); named_args }, ) diff --git a/odra-macros/src/ast/ref_utils.rs b/odra-macros/src/ast/ref_utils.rs index 3bba1cbd..dee5f7d3 100644 --- a/odra-macros/src/ast/ref_utils.rs +++ b/odra-macros/src/ast/ref_utils.rs @@ -1,4 +1,5 @@ use crate::utils::syn::visibility_default; +use crate::utils::ty; use crate::{ ast::fn_utils, ir::{FnArgIR, FnIR}, @@ -142,6 +143,6 @@ pub fn insert_arg_stmt(arg: &FnArgIR) -> syn::Stmt { let ident = arg.name().unwrap(); let name = ident.to_string(); let args = utils::ident::named_args(); - - syn::parse_quote!(let _ = #args.insert(#name, #ident.clone());) + let ty = ty::entry_point_arg(); + syn::parse_quote!(#ty::insert_runtime_arg(#ident.clone(), #name, &mut #args);) } diff --git a/odra-macros/src/ast/test_parts.rs b/odra-macros/src/ast/test_parts.rs index 2d5ed70a..3e8693b4 100644 --- a/odra-macros/src/ast/test_parts.rs +++ b/odra-macros/src/ast/test_parts.rs @@ -145,8 +145,8 @@ mod test { } /// Approve. - pub fn approve(&mut self, to: &Address, amount: &U256) { - self.try_approve(to, amount).unwrap() + pub fn approve(&mut self, to: &Address, amount: &U256, msg: Maybe) { + self.try_approve(to, amount, msg).unwrap() } /// Airdrops the given amount to the given addresses. @@ -170,8 +170,7 @@ mod test { if self.attached_value > odra::casper_types::U512::zero() { let _ = named_args.insert("amount", self.attached_value); } - let _ = named_args - .insert("total_supply", total_supply.clone()); + odra::args::EntrypointArgument::insert_runtime_arg(total_supply.clone(), "total_supply", &mut named_args); named_args }, ) @@ -223,6 +222,7 @@ mod test { &mut self, to: &Address, amount: &U256, + msg: Maybe, ) -> odra::OdraResult<()> { self.env .call_contract( @@ -235,8 +235,9 @@ mod test { if self.attached_value > odra::casper_types::U512::zero() { let _ = named_args.insert("amount", self.attached_value); } - let _ = named_args.insert("to", to.clone()); - let _ = named_args.insert("amount", amount.clone()); + odra::args::EntrypointArgument::insert_runtime_arg(to.clone(), "to", &mut named_args); + odra::args::EntrypointArgument::insert_runtime_arg(amount.clone(), "amount", &mut named_args); + odra::args::EntrypointArgument::insert_runtime_arg(msg.clone(), "msg", &mut named_args); named_args }, ) @@ -256,8 +257,8 @@ mod test { if self.attached_value > odra::casper_types::U512::zero() { let _ = named_args.insert("amount", self.attached_value); } - let _ = named_args.insert("to", to.clone()); - let _ = named_args.insert("amount", amount.clone()); + odra::args::EntrypointArgument::insert_runtime_arg(to.clone(), "to", &mut named_args); + odra::args::EntrypointArgument::insert_runtime_arg(amount.clone(), "amount", &mut named_args); named_args } ).with_amount(self.attached_value), @@ -288,17 +289,18 @@ mod test { fn entry_points_caller(env: &odra::host::HostEnv) -> odra::entry_point_callback::EntryPointsCaller { let entry_points = odra::prelude::vec![ odra::entry_point_callback::EntryPoint::new(odra::prelude::string::String::from("init"), odra::prelude::vec![ - odra::entry_point_callback::Argument::new(odra::prelude::string::String::from("total_supply"), as odra::casper_types::CLTyped>::cl_type()) + odra::entry_point_callback::Argument::new:: >(odra::prelude::string::String::from("total_supply")) ]), odra::entry_point_callback::EntryPoint::new(odra::prelude::string::String::from("total_supply"), odra::prelude::vec![]), odra::entry_point_callback::EntryPoint::new(odra::prelude::string::String::from("pay_to_mint"), odra::prelude::vec![]), odra::entry_point_callback::EntryPoint::new(odra::prelude::string::String::from("approve"), odra::prelude::vec![ - odra::entry_point_callback::Argument::new(odra::prelude::string::String::from("to"),
::cl_type()), - odra::entry_point_callback::Argument::new(odra::prelude::string::String::from("amount"), ::cl_type()) + odra::entry_point_callback::Argument::new::
(odra::prelude::string::String::from("to")), + odra::entry_point_callback::Argument::new::(odra::prelude::string::String::from("amount")), + odra::entry_point_callback::Argument::new:: >(odra::prelude::string::String::from("msg")) ]), odra::entry_point_callback::EntryPoint::new(odra::prelude::string::String::from("airdrop"), odra::prelude::vec![ - odra::entry_point_callback::Argument::new(odra::prelude::string::String::from("to"), as odra::casper_types::CLTyped>::cl_type()), - odra::entry_point_callback::Argument::new(odra::prelude::string::String::from("amount"), ::cl_type()) + odra::entry_point_callback::Argument::new:: >(odra::prelude::string::String::from("to")), + odra::entry_point_callback::Argument::new::(odra::prelude::string::String::from("amount")) ]) ]; odra::entry_point_callback::EntryPointsCaller::new(env.clone(), entry_points, |contract_env, call_def| { diff --git a/odra-macros/src/ast/wasm_parts.rs b/odra-macros/src/ast/wasm_parts.rs index f90bb63c..9d397d9e 100644 --- a/odra-macros/src/ast/wasm_parts.rs +++ b/odra-macros/src/ast/wasm_parts.rs @@ -122,7 +122,13 @@ impl TryFrom<&'_ ModuleImplIR> for CallFnItem { let runtime_args_expr: syn::Expr = match module.constructor() { Some(f) => { let arg_block = fn_utils::runtime_args_block(&f, wasm_parts_utils::insert_arg_stmt); - parse_quote!(let #ident_args = Some(#arg_block)) + parse_quote!(let #ident_args = { + let env = odra::odra_casper_wasm_env::WasmContractEnv::new_env(); + let env_rc = Rc::new(env); + let exec_env = odra::ExecutionEnv::new(env_rc); + + Some(#arg_block) + }) } None => parse_quote!(let #ident_args = Option::<#ty_args>::None) }; @@ -279,10 +285,7 @@ mod test { entry_points.add_entry_point(odra::casper_types::EntryPoint::new( "init", - vec![odra::casper_types::Parameter::new( - "total_supply", - as odra::casper_types::CLTyped>::cl_type() - )], + vec![odra::args::into_parameter:: >("total_supply")].into_iter().filter_map(|x| x).collect(), <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Groups(vec![odra::casper_types::Group::new("constructor_group")]), odra::casper_types::EntryPointType::Contract @@ -309,11 +312,10 @@ mod test { odra::casper_types::EntryPoint::new( "approve", vec![ - odra::casper_types::Parameter::new("to", < Address as - odra::casper_types::CLTyped > ::cl_type()), - odra::casper_types::Parameter::new("amount", < U256 as - odra::casper_types::CLTyped > ::cl_type()) - ], + odra::args::into_parameter::
("to"), + odra::args::into_parameter::("amount"), + odra::args::into_parameter:: >("msg") + ].into_iter().filter_map(|x| x).collect(), <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Public, odra::casper_types::EntryPointType::Contract, @@ -324,11 +326,9 @@ mod test { odra::casper_types::EntryPoint::new( "airdrop", vec![ - odra::casper_types::Parameter::new("to", as - odra::casper_types::CLTyped > ::cl_type()), - odra::casper_types::Parameter::new("amount", ::cl_type()) - ], + odra::args::into_parameter:: >("to"), + odra::args::into_parameter::("amount") + ].into_iter().filter_map(|x| x).collect(), <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Public, odra::casper_types::EntryPointType::Contract, @@ -342,14 +342,22 @@ mod test { let schemas = odra::casper_event_standard::Schemas( ::event_schemas() ); - let named_args = Some({ - let mut named_args = odra::casper_types::RuntimeArgs::new(); - let _ = named_args.insert( - "total_supply", - odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::get_named_arg::>("total_supply") - ); - named_args - }); + + let named_args = { + let env = odra::odra_casper_wasm_env::WasmContractEnv::new_env(); + let env_rc = Rc::new(env); + let exec_env = odra::ExecutionEnv::new(env_rc); + + Some({ + let mut named_args = odra::casper_types::RuntimeArgs::new(); + odra::args::EntrypointArgument::insert_runtime_arg( + exec_env.get_named_arg::>("total_supply"), + "total_supply", + &mut named_args + ); + named_args + }) + }; odra::odra_casper_wasm_env::host_functions::install_contract( entry_points(), schemas, @@ -496,10 +504,7 @@ mod test { .add_entry_point( odra::casper_types::EntryPoint::new( "set_owner", - vec![ - odra::casper_types::Parameter::new("new_owner", < Address as - odra::casper_types::CLTyped > ::cl_type()) - ], + vec![odra::args::into_parameter::
("new_owner")].into_iter().filter_map(|x| x).collect(), <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Public, odra::casper_types::EntryPointType::Contract, diff --git a/odra-macros/src/ast/wasm_parts_utils.rs b/odra-macros/src/ast/wasm_parts_utils.rs index 347f779d..756822e2 100644 --- a/odra-macros/src/ast/wasm_parts_utils.rs +++ b/odra-macros/src/ast/wasm_parts_utils.rs @@ -12,7 +12,11 @@ pub fn param_parameters(func: &FnIR) -> syn::Expr { .filter_map(Result::ok) .map(|(name, ty)| utils::expr::new_parameter(name, ty)) .collect::>(); - parse_quote!(vec![#(#params),*]) + if params.is_empty() { + parse_quote!(vec![]) + } else { + parse_quote!(vec![#(#params),*].into_iter().filter_map(|x| x).collect()) + } } pub fn param_access(func: &FnIR) -> syn::Expr { @@ -32,9 +36,9 @@ pub fn param_ret_ty(func: &FnIR) -> syn::Expr { pub fn insert_arg_stmt(arg: &FnArgIR) -> syn::Stmt { let (name, ty) = arg.name_and_ty().unwrap(); let args = utils::ident::named_args(); - - syn::parse_quote!(let _ = #args.insert( + syn::parse_quote!(odra::args::EntrypointArgument::insert_runtime_arg( + exec_env.get_named_arg::<#ty>(#name), #name, - odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::get_named_arg::<#ty>(#name) + &mut #args );) } diff --git a/odra-macros/src/lib.rs b/odra-macros/src/lib.rs index 050027ab..bc1c7914 100644 --- a/odra-macros/src/lib.rs +++ b/odra-macros/src/lib.rs @@ -6,7 +6,6 @@ use ast::*; use ir::{ModuleImplIR, ModuleStructIR, TypeIR}; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; -use syn::punctuated::Punctuated; mod ast; mod ir; @@ -91,6 +90,7 @@ pub fn external_contract(attr: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro_derive(IntoRuntimeArgs)] pub fn derive_into_runtime_args(item: TokenStream) -> TokenStream { let item = syn::parse_macro_input!(item as syn::DeriveInput); + let args_ident = syn::Ident::new("args", item.ident.span()); match item { syn::DeriveInput { ident, @@ -101,16 +101,15 @@ pub fn derive_into_runtime_args(item: TokenStream) -> TokenStream { .into_iter() .map(|f| { let name = f.ident.unwrap(); - quote::quote!(stringify!(#name) => self.#name) + quote::quote!(odra::args::EntrypointArgument::insert_runtime_arg(self.#name, stringify!(#name), &mut #args_ident);) }) - .collect::>(); + .collect::(); let res = quote::quote! { impl Into for #ident { fn into(self) -> odra::casper_types::RuntimeArgs { - use odra::casper_types::RuntimeArgs; - odra::casper_types::runtime_args! { - #fields - } + let mut #args_ident = odra::casper_types::RuntimeArgs::new(); + #fields + #args_ident } } }; diff --git a/odra-macros/src/test_utils.rs b/odra-macros/src/test_utils.rs index 668ba35b..82285aff 100644 --- a/odra-macros/src/test_utils.rs +++ b/odra-macros/src/test_utils.rs @@ -30,7 +30,7 @@ pub mod mock { /// Approve. #[odra(non_reentrant)] - pub fn approve(&mut self, to: &Address, amount: &U256) { + pub fn approve(&mut self, to: &Address, amount: &U256, msg: Maybe) { self.env.emit_event(Approval { owner: self.env.caller(), spender: to, diff --git a/odra-macros/src/utils/expr.rs b/odra-macros/src/utils/expr.rs index 664f133e..b7cd4a61 100644 --- a/odra-macros/src/utils/expr.rs +++ b/odra-macros/src/utils/expr.rs @@ -49,9 +49,9 @@ pub fn entry_point_group(name: &str) -> syn::Expr { } pub fn new_parameter(name: String, ty: syn::Type) -> syn::Expr { - let ty_param = super::ty::parameter(); - let cl_type = as_cl_type(&ty); - parse_quote!(#ty_param::new(#name, #cl_type)) + let ty = super::ty::unreferenced_ty(&ty); + + parse_quote!(odra::args::into_parameter::<#ty>(#name)) } pub fn as_cl_type(ty: &syn::Type) -> syn::Expr { @@ -168,17 +168,20 @@ pub fn new_entry_point(name: String, args: Vec) -> syn::Expr { .iter() .map(new_entry_point_arg) .collect::>(); - let args = vec(args_stream); - parse_quote!(#ty::new(#name, #args)) + let args_vec = vec(args_stream); + parse_quote!(#ty::new(#name, #args_vec)) } fn new_entry_point_arg(arg: &syn::PatType) -> syn::Expr { let ty = super::ty::odra_entry_point_arg(); - let cl_type = as_cl_type(&arg.ty); + let arg_ty = super::ty::unreferenced_ty(&arg.ty); let name = string_from(arg.pat.to_token_stream().to_string()); - parse_quote!(#ty::new(#name, #cl_type)) + parse_quote!(#ty::new::<#arg_ty>(#name)) } +pub fn into_arg(ty: syn::Type, ident: String) -> syn::Expr { + parse_quote!(odra::args::into_argument::<#ty>(#ident)) +} pub trait IntoExpr { fn into_expr(self) -> syn::Expr; } diff --git a/odra-macros/src/utils/ty.rs b/odra-macros/src/utils/ty.rs index cbd26ae2..5b5cc588 100644 --- a/odra-macros/src/utils/ty.rs +++ b/odra-macros/src/utils/ty.rs @@ -89,10 +89,6 @@ pub fn entry_point_type() -> syn::Type { parse_quote!(odra::casper_types::EntryPointType) } -pub fn parameter() -> syn::Type { - parse_quote!(odra::casper_types::Parameter) -} - pub fn group() -> syn::Type { parse_quote!(odra::casper_types::Group) } @@ -108,6 +104,10 @@ pub fn cl_typed() -> syn::Type { parse_quote!(odra::casper_types::CLTyped) } +pub fn entry_point_arg() -> syn::Type { + parse_quote!(odra::args::EntrypointArgument) +} + pub fn cl_type() -> syn::Type { parse_quote!(odra::casper_types::CLType) } @@ -164,10 +164,6 @@ pub fn entry_point_def_ty_public() -> syn::Type { parse_quote!(odra::contract_def::EntrypointType::Public) } -pub fn entry_point_def_arg() -> syn::Type { - parse_quote!(odra::contract_def::Argument) -} - pub fn string() -> syn::Type { parse_quote!(odra::prelude::string::String) } diff --git a/odra-macros/tests/test.rs b/odra-macros/tests/test.rs index 18635f5a..e8159ed5 100644 --- a/odra-macros/tests/test.rs +++ b/odra-macros/tests/test.rs @@ -1,5 +1,17 @@ pub mod odra { pub use casper_types; + + pub mod args { + pub trait EntrypointArgument { + fn insert_runtime_arg(self, name: &str, args: &mut casper_types::RuntimeArgs); + } + + impl EntrypointArgument for u32 { + fn insert_runtime_arg(self, name: &str, args: &mut casper_types::RuntimeArgs) { + let _ = args.insert(name, self); + } + } + } } use casper_types::{runtime_args, RuntimeArgs}; diff --git a/odra-vm/src/odra_vm_contract_env.rs b/odra-vm/src/odra_vm_contract_env.rs index c6e91700..b1ae9710 100644 --- a/odra-vm/src/odra_vm_contract_env.rs +++ b/odra-vm/src/odra_vm_contract_env.rs @@ -63,7 +63,11 @@ impl ContractContext for OdraVmContractEnv { } fn get_named_arg_bytes(&self, name: &str) -> Bytes { - self.vm.borrow().get_named_arg(name).into() + self.vm.borrow().get_named_arg(name).unwrap().into() + } + + fn get_opt_named_arg_bytes(&self,name: &str) -> Option { + self.vm.borrow().get_named_arg(name).map(Into::into) } fn handle_attached_value(&self) { diff --git a/odra-vm/src/vm/odra_vm.rs b/odra-vm/src/vm/odra_vm.rs index 7d6e67cc..fa7cecf5 100644 --- a/odra-vm/src/vm/odra_vm.rs +++ b/odra-vm/src/vm/odra_vm.rs @@ -119,11 +119,11 @@ impl OdraVm { /// Gets the value of the named argument. /// /// The argument must be present in the call definition. - pub fn get_named_arg(&self, name: &str) -> Vec { + pub fn get_named_arg(&self, name: &str) -> Option> { match self.state.read().unwrap().callstack_tip() { CallstackElement::Account(_) => todo!(), CallstackElement::ContractCall { call_def, .. } => { - call_def.args().get(name).unwrap().inner_bytes().to_vec() + call_def.args().get(name).map(|arg| arg.inner_bytes().to_vec()) } } } diff --git a/odra/src/lib.rs b/odra/src/lib.rs index 4c71c51b..c129ccac 100644 --- a/odra/src/lib.rs +++ b/odra/src/lib.rs @@ -37,7 +37,9 @@ //! ``` #![no_std] -pub use odra_core::{arithmetic, contract_def, entry_point_callback, host, module, prelude, uints}; +pub use odra_core::{ + args, arithmetic, contract_def, entry_point_callback, host, module, prelude, uints +}; pub use odra_core::{casper_event_standard, casper_event_standard::Event, casper_types}; pub use odra_core::{ Address, AddressError, CallDef, CollectionError, ContractCallResult, ContractContext, From 91946ce0dd097e7f7ff793b5d10fff8fe4af8a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Mon, 11 Mar 2024 09:33:35 +0100 Subject: [PATCH 2/4] fix formatting --- core/src/args.rs | 53 ++++++++----------- core/src/contract_container.rs | 6 +-- core/src/entry_point_callback.rs | 7 ++- .../livenet-env/src/livenet_contract_env.rs | 12 +++-- odra-casper/wasm-env/src/host_functions.rs | 15 ++++-- odra-vm/src/odra_vm_contract_env.rs | 4 +- odra-vm/src/vm/odra_vm.rs | 7 +-- 7 files changed, 56 insertions(+), 48 deletions(-) diff --git a/core/src/args.rs b/core/src/args.rs index 0a223dbd..600fb9d7 100644 --- a/core/src/args.rs +++ b/core/src/args.rs @@ -2,7 +2,8 @@ use crate::{contract_def::Argument, prelude::*, ContractEnv, ExecutionError}; use casper_types::{ - bytesrepr::{FromBytes, ToBytes}, CLType, CLTyped, Parameter, RuntimeArgs + bytesrepr::{FromBytes, ToBytes}, + CLType, CLTyped, Parameter, RuntimeArgs }; /// A type that represents an entrypoint arg that may or may not be present. @@ -84,10 +85,11 @@ pub trait EntrypointArgument: Sized { fn cl_type() -> CLType; /// Inserts the argument into the runtime args. fn insert_runtime_arg(self, name: &str, args: &mut RuntimeArgs); + /// Unwraps the argument from an Option. fn unwrap(value: Option, env: &ContractEnv) -> Self; } -impl EntrypointArgument for Maybe { +impl EntrypointArgument for Maybe { fn is_required() -> bool { false } @@ -101,8 +103,8 @@ impl EntrypointArgument for Maybe { let _ = args.insert(name, v); } } - - fn unwrap(value: Option, env: &ContractEnv) -> Self { + + fn unwrap(value: Option, _env: &ContractEnv) -> Self { match value { Some(v) => v, None => Maybe::None @@ -110,7 +112,7 @@ impl EntrypointArgument for Maybe { } } -impl EntrypointArgument for T { +impl EntrypointArgument for T { fn is_required() -> bool { true } @@ -120,19 +122,19 @@ impl EntrypointArgument for T { } fn insert_runtime_arg(self, name: &str, args: &mut RuntimeArgs) { - let _ = args.insert(name, self); + let _ = args.insert(name, self); } - + fn unwrap(value: Option, env: &ContractEnv) -> Self { match value { Some(v) => v, None => env.revert(ExecutionError::UnwrapError) - } } } /// Converts a type into Casper's entrypoint argument representation. +/// If the parameter is not required, it returns `None`. pub fn into_parameter(name: &str) -> Option { match T::is_required() { true => Some(Parameter::new(name, T::cl_type())), @@ -142,10 +144,10 @@ pub fn into_parameter(name: &str) -> Option { /// Converts a type into Odra's entrypoint argument representation. pub fn into_argument(name: &str) -> Argument { - Argument { - ident: name.to_string(), - ty: T::cl_type(), - is_ref: false, + Argument { + ident: name.to_string(), + ty: T::cl_type(), + is_ref: false, is_slice: false, is_required: T::is_required() } @@ -167,13 +169,13 @@ mod tests { let ctx = MockContractContext::new(); let env = ContractEnv::new(0, Rc::new(RefCell::new(ctx))); - assert_eq!(some.is_some(), true); - assert_eq!(some.is_none(), false); + assert!(some.is_some()); + assert!(!some.is_none()); assert_eq!(some.clone().unwrap(&env), 1); assert_eq!(some.unwrap_or_default(), 1); - assert_eq!(none.is_some(), false); - assert_eq!(none.is_none(), true); + assert!(!none.is_some()); + assert!(none.is_none()); assert_eq!(none.unwrap_or_default(), 0); } @@ -190,34 +192,25 @@ mod tests { #[test] fn test_into_args() { - let args = vec![ + let args = [ into_argument::>("arg1"), into_argument::("arg2"), - into_argument::>("arg3"), + into_argument::>("arg3") ]; assert_eq!(args.len(), 3); } - #[test] fn test_into_casper_parameters() { - let params = vec![ + let params = [ into_parameter::>("arg1"), into_parameter::>("arg2"), into_parameter::>>("arg3"), - into_parameter::
("arg4"), - ] - .into_iter() - .filter_map(|x| x) - .collect::>(); - - let params = vec![ - into_parameter::("name"), - into_parameter::>("metadata"), + into_parameter::
("arg4") ] .into_iter() - .filter_map(|x| x) + .flatten() .collect::>(); assert_eq!(params.len(), 2); diff --git a/core/src/contract_container.rs b/core/src/contract_container.rs index 3a955ff1..4b47ac85 100644 --- a/core/src/contract_container.rs +++ b/core/src/contract_container.rs @@ -150,11 +150,7 @@ mod tests { #[test] fn test_many_missing_args() { // Given an instance with a single entrypoint with "first", "second" and "third" args. - let instance = ContractContainer::with_entrypoint(vec![ - "first", - "second", - "third", - ]); + let instance = ContractContainer::with_entrypoint(vec!["first", "second", "third"]); // When call a valid entrypoint with a single valid args, let call_def = CallDef::new(TEST_ENTRYPOINT, false, runtime_args! { "third" => 0u32 }); diff --git a/core/src/entry_point_callback.rs b/core/src/entry_point_callback.rs index 7cf3c6e7..622b2643 100644 --- a/core/src/entry_point_callback.rs +++ b/core/src/entry_point_callback.rs @@ -81,12 +81,15 @@ pub struct Argument { /// The name of the argument. pub name: String, /// The type of the argument. - pub ty: CLType, + pub ty: CLType } impl Argument { /// Creates a new instance of `Argument`. pub fn new(name: String) -> Self { - Self { name, ty: T::cl_type() } + Self { + name, + ty: T::cl_type() + } } } diff --git a/odra-casper/livenet-env/src/livenet_contract_env.rs b/odra-casper/livenet-env/src/livenet_contract_env.rs index 9465719b..7dc32b89 100644 --- a/odra-casper/livenet-env/src/livenet_contract_env.rs +++ b/odra-casper/livenet-env/src/livenet_contract_env.rs @@ -91,11 +91,17 @@ impl ContractContext for LivenetContractEnv { } fn get_named_arg_bytes(&self, name: &str) -> Bytes { + self.get_opt_named_arg_bytes(name).unwrap() + } + + fn get_opt_named_arg_bytes(&self, name: &str) -> Option { match self.callstack.borrow().current() { CallstackElement::Account(_) => todo!("get_named_arg_bytes"), - CallstackElement::ContractCall { call_def, .. } => { - Bytes::from(call_def.args().get(name).unwrap().inner_bytes().to_vec()) - } + CallstackElement::ContractCall { call_def, .. } => call_def + .args() + .get(name) + .map(|cl_value| cl_value.inner_bytes().to_vec()) + .map(Bytes::from) } } diff --git a/odra-casper/wasm-env/src/host_functions.rs b/odra-casper/wasm-env/src/host_functions.rs index 78c92a00..38b76ce9 100644 --- a/odra-casper/wasm-env/src/host_functions.rs +++ b/odra-casper/wasm-env/src/host_functions.rs @@ -19,7 +19,6 @@ use casper_contract::{ unwrap_or_revert::UnwrapOrRevert }; use core::mem::MaybeUninit; -use odra_core::{args::EntrypointArgument, casper_event_standard::{self, Schema, Schemas}}; use odra_core::casper_types::{ api_error, bytesrepr::{Bytes, FromBytes, ToBytes}, @@ -28,6 +27,10 @@ use odra_core::casper_types::{ ApiError, CLTyped, CLValue, ContractPackageHash, ContractVersion, EntryPoints, Key, RuntimeArgs, URef, DICTIONARY_ITEM_KEY_MAX_LENGTH, U512 }; +use odra_core::{ + args::EntrypointArgument, + casper_event_standard::{self, Schema, Schemas} +}; use odra_core::{prelude::*, Address, CallDef, ExecutionError}; use crate::consts; @@ -129,9 +132,15 @@ pub fn get_named_arg(name: &str) -> Result, ApiError> { ) }; if ret != 0 { - return Err(ApiError::from(ret as u32)) + return Err(ApiError::from(ret as u32)); + } + unsafe { + Ok(Vec::from_raw_parts( + data_non_null_ptr.as_ptr(), + arg_size, + arg_size + )) } - unsafe { Ok(Vec::from_raw_parts(data_non_null_ptr.as_ptr(), arg_size, arg_size)) } } else { Ok(Vec::new()) } diff --git a/odra-vm/src/odra_vm_contract_env.rs b/odra-vm/src/odra_vm_contract_env.rs index b1ae9710..eb0b9527 100644 --- a/odra-vm/src/odra_vm_contract_env.rs +++ b/odra-vm/src/odra_vm_contract_env.rs @@ -65,8 +65,8 @@ impl ContractContext for OdraVmContractEnv { fn get_named_arg_bytes(&self, name: &str) -> Bytes { self.vm.borrow().get_named_arg(name).unwrap().into() } - - fn get_opt_named_arg_bytes(&self,name: &str) -> Option { + + fn get_opt_named_arg_bytes(&self, name: &str) -> Option { self.vm.borrow().get_named_arg(name).map(Into::into) } diff --git a/odra-vm/src/vm/odra_vm.rs b/odra-vm/src/vm/odra_vm.rs index fa7cecf5..a42d0913 100644 --- a/odra-vm/src/vm/odra_vm.rs +++ b/odra-vm/src/vm/odra_vm.rs @@ -122,9 +122,10 @@ impl OdraVm { pub fn get_named_arg(&self, name: &str) -> Option> { match self.state.read().unwrap().callstack_tip() { CallstackElement::Account(_) => todo!(), - CallstackElement::ContractCall { call_def, .. } => { - call_def.args().get(name).map(|arg| arg.inner_bytes().to_vec()) - } + CallstackElement::ContractCall { call_def, .. } => call_def + .args() + .get(name) + .map(|arg| arg.inner_bytes().to_vec()) } } From 88f1e897b86a4ed7478ce16cd6b7b0e6a30a5610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Mon, 11 Mar 2024 10:12:54 +0100 Subject: [PATCH 3/4] refactor update changelog --- CHANGELOG.md | 12 ++++++++++-- core/src/args.rs | 22 +++++++++++----------- examples/src/features/optional_args.rs | 22 +++++++++++++--------- odra-macros/src/ast/entrypoints_item.rs | 14 +++++++------- odra-macros/src/ast/wasm_parts.rs | 14 +++++++------- odra-macros/src/utils/expr.rs | 4 ++-- 6 files changed, 50 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52380c7a..c3f4bc7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,15 @@ Changelog for `odra`. -## [0.9.0] - 2024-XX-XX +## [0.8.2] - 2024-03-xx +### Added +- `Maybe` - a type that represents an entrypoint arg that may or may not be present. +- `EntrypointArgument` - a trait for types that can be used as entrypoint arguments. +- an example of using `Maybe` in `odra-examples` crate. +- `get_opt_named_arg_bytes(&str)` in `ContractEnv` +- `odra::contract_def::Argument` has a new `is_required` field. + +## [0.8.1] - 2024-03-01 ### Added - `ContractRef` trait with `new` and `address` functions. All contract references now implement it. - `disable-allocator` feature for `odra` crate. It allows to disable the allocator used by Odra Framework in @@ -11,7 +19,7 @@ wasm build. ### Changed - Traits implemented by modules are now also implemented by their `ContractRefs` and `HostRefs`. -## [0.8.0] - 2024-XX-XX +## [0.8.0] - 2024-02-06 ### Changed - Replaced `contract_env::` with `self.env()` in the contract context (of type `ContractEnv`). diff --git a/core/src/args.rs b/core/src/args.rs index 600fb9d7..6ff99979 100644 --- a/core/src/args.rs +++ b/core/src/args.rs @@ -133,17 +133,17 @@ impl EntrypointArgument for T { } } -/// Converts a type into Casper's entrypoint argument representation. +/// Returns a Casper entrypoint argument representation. /// If the parameter is not required, it returns `None`. -pub fn into_parameter(name: &str) -> Option { +pub fn parameter(name: &str) -> Option { match T::is_required() { true => Some(Parameter::new(name, T::cl_type())), false => None } } -/// Converts a type into Odra's entrypoint argument representation. -pub fn into_argument(name: &str) -> Argument { +/// Returns an Odra's entrypoint argument representation. +pub fn odra_argument(name: &str) -> Argument { Argument { ident: name.to_string(), ty: T::cl_type(), @@ -193,9 +193,9 @@ mod tests { #[test] fn test_into_args() { let args = [ - into_argument::>("arg1"), - into_argument::("arg2"), - into_argument::>("arg3") + odra_argument::>("arg1"), + odra_argument::("arg2"), + odra_argument::>("arg3") ]; assert_eq!(args.len(), 3); @@ -204,10 +204,10 @@ mod tests { #[test] fn test_into_casper_parameters() { let params = [ - into_parameter::>("arg1"), - into_parameter::>("arg2"), - into_parameter::>>("arg3"), - into_parameter::
("arg4") + parameter::>("arg1"), + parameter::>("arg2"), + parameter::>>("arg3"), + parameter::
("arg4") ] .into_iter() .flatten() diff --git a/examples/src/features/optional_args.rs b/examples/src/features/optional_args.rs index d11453c8..6aa3e295 100644 --- a/examples/src/features/optional_args.rs +++ b/examples/src/features/optional_args.rs @@ -1,27 +1,31 @@ -#![allow(missing_docs)] +//! Optional arguments example. -use odra::{args::Maybe, Var}; use odra::prelude::*; +use odra::{args::Maybe, Var}; +/// Contract structure. #[odra::module] pub struct Token { name: Var, - metadata: Var, + metadata: Var } #[odra::module] impl Token { + /// Initializes the contract with the given name and optional metadata. + /// Maybe is different from Option in that the Option value is always present, but + /// it can be either Some or None. pub fn init(&mut self, name: String, metadata: Maybe) { - self.name.set(name.clone()); + self.name.set(name); self.metadata.set(metadata.unwrap_or_default()); } + /// Returns the token metadata. pub fn metadata(&self) -> String { self.metadata.get_or_default() } } - #[cfg(test)] mod test { use crate::features::optional_args::TokenInitArgs; @@ -32,8 +36,8 @@ mod test { #[test] fn test_no_opt_arg() { let test_env = odra_test::env(); - let init_args = TokenInitArgs { - name: String::from("MyToken"), + let init_args = TokenInitArgs { + name: String::from("MyToken"), metadata: Maybe::None }; let my_contract = TokenHostRef::deploy(&test_env, init_args); @@ -43,8 +47,8 @@ mod test { #[test] fn test_with_opt_arg() { let test_env = odra_test::env(); - let init_args = TokenInitArgs { - name: String::from("MyToken"), + let init_args = TokenInitArgs { + name: String::from("MyToken"), metadata: Maybe::Some(String::from("MyMetadata")) }; let my_contract = TokenHostRef::deploy(&test_env, init_args); diff --git a/odra-macros/src/ast/entrypoints_item.rs b/odra-macros/src/ast/entrypoints_item.rs index 51268180..39b05714 100644 --- a/odra-macros/src/ast/entrypoints_item.rs +++ b/odra-macros/src/ast/entrypoints_item.rs @@ -125,7 +125,7 @@ mod test { odra::contract_def::Entrypoint { ident: String::from("init"), args: vec![ - odra::args::into_argument:: >("total_supply") + odra::args::odra_argument:: >("total_supply") ], is_mut: true, ret: <() as odra::casper_types::CLTyped>::cl_type(), @@ -151,9 +151,9 @@ mod test { odra::contract_def::Entrypoint { ident: String::from("approve"), args: vec![ - odra::args::into_argument::
("to"), - odra::args::into_argument::("amount"), - odra::args::into_argument:: >("msg") + odra::args::odra_argument::
("to"), + odra::args::odra_argument::("amount"), + odra::args::odra_argument:: >("msg") ], is_mut: true, ret: <() as odra::casper_types::CLTyped>::cl_type(), @@ -163,8 +163,8 @@ mod test { odra::contract_def::Entrypoint { ident: String::from("airdrop"), args: vec![ - odra::args::into_argument:: >("to"), - odra::args::into_argument::("amount") + odra::args::odra_argument:: >("to"), + odra::args::odra_argument::("amount") ], is_mut: false, ret: <() as odra::casper_types::CLTyped>::cl_type(), @@ -236,7 +236,7 @@ mod test { odra::contract_def::Entrypoint { ident: String::from("set_owner"), args: vec![ - odra::args::into_argument::
("new_owner") + odra::args::odra_argument::
("new_owner") ], is_mut: true, ret: <() as odra::casper_types::CLTyped>::cl_type(), diff --git a/odra-macros/src/ast/wasm_parts.rs b/odra-macros/src/ast/wasm_parts.rs index 9d397d9e..4bf56654 100644 --- a/odra-macros/src/ast/wasm_parts.rs +++ b/odra-macros/src/ast/wasm_parts.rs @@ -285,7 +285,7 @@ mod test { entry_points.add_entry_point(odra::casper_types::EntryPoint::new( "init", - vec![odra::args::into_parameter:: >("total_supply")].into_iter().filter_map(|x| x).collect(), + vec![odra::args::parameter:: >("total_supply")].into_iter().filter_map(|x| x).collect(), <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Groups(vec![odra::casper_types::Group::new("constructor_group")]), odra::casper_types::EntryPointType::Contract @@ -312,9 +312,9 @@ mod test { odra::casper_types::EntryPoint::new( "approve", vec![ - odra::args::into_parameter::
("to"), - odra::args::into_parameter::("amount"), - odra::args::into_parameter:: >("msg") + odra::args::parameter::
("to"), + odra::args::parameter::("amount"), + odra::args::parameter:: >("msg") ].into_iter().filter_map(|x| x).collect(), <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Public, @@ -326,8 +326,8 @@ mod test { odra::casper_types::EntryPoint::new( "airdrop", vec![ - odra::args::into_parameter:: >("to"), - odra::args::into_parameter::("amount") + odra::args::parameter:: >("to"), + odra::args::parameter::("amount") ].into_iter().filter_map(|x| x).collect(), <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Public, @@ -504,7 +504,7 @@ mod test { .add_entry_point( odra::casper_types::EntryPoint::new( "set_owner", - vec![odra::args::into_parameter::
("new_owner")].into_iter().filter_map(|x| x).collect(), + vec![odra::args::parameter::
("new_owner")].into_iter().filter_map(|x| x).collect(), <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Public, odra::casper_types::EntryPointType::Contract, diff --git a/odra-macros/src/utils/expr.rs b/odra-macros/src/utils/expr.rs index b7cd4a61..44fde25e 100644 --- a/odra-macros/src/utils/expr.rs +++ b/odra-macros/src/utils/expr.rs @@ -51,7 +51,7 @@ pub fn entry_point_group(name: &str) -> syn::Expr { pub fn new_parameter(name: String, ty: syn::Type) -> syn::Expr { let ty = super::ty::unreferenced_ty(&ty); - parse_quote!(odra::args::into_parameter::<#ty>(#name)) + parse_quote!(odra::args::parameter::<#ty>(#name)) } pub fn as_cl_type(ty: &syn::Type) -> syn::Expr { @@ -180,7 +180,7 @@ fn new_entry_point_arg(arg: &syn::PatType) -> syn::Expr { } pub fn into_arg(ty: syn::Type, ident: String) -> syn::Expr { - parse_quote!(odra::args::into_argument::<#ty>(#ident)) + parse_quote!(odra::args::odra_argument::<#ty>(#ident)) } pub trait IntoExpr { fn into_expr(self) -> syn::Expr; From d6c2c59f3dcc2cfe333faf62c147b336b87ce864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Mon, 11 Mar 2024 13:05:59 +0100 Subject: [PATCH 4/4] fmt --- examples/src/features/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/features/mod.rs b/examples/src/features/mod.rs index bce62bd3..44370e6f 100644 --- a/examples/src/features/mod.rs +++ b/examples/src/features/mod.rs @@ -9,9 +9,9 @@ pub mod livenet; pub mod module_nesting; pub mod modules; pub mod native_token; +pub mod optional_args; pub mod pauseable; pub mod reentrancy_guard; pub mod signature_verifier; pub mod storage; pub mod testing; -pub mod optional_args; \ No newline at end of file