Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/native token #52

Merged
merged 23 commits into from
Oct 17, 2022
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
2 changes: 2 additions & 0 deletions core/src/contract_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub enum EntrypointType {
Constructor,
/// A regular entrypoint.
Public,
/// A payable entrypoint.
PublicPayable,
kpob marked this conversation as resolved.
Show resolved Hide resolved
}

/// A trait that should be implemented by each smart contract to allow the backend
Expand Down
28 changes: 26 additions & 2 deletions core/src/external_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ pub mod test_env;
pub mod contract_env;

// This mock here is required because when loading a code of a module
// in new contract repository, ContractEnv is required, but we semanticaly
// in new contract repository, ContractEnv is required, but we semantically
// doesn't make sense for `wasm-test` feature.
#[cfg(feature = "wasm-test")]
pub mod contract_env {
use odra_types::{
bytesrepr::{FromBytes, ToBytes},
event::Event,
Address, CLTyped, CLValue, ExecutionError, RuntimeArgs,
Address, CLTyped, CLValue, ExecutionError, RuntimeArgs, U512,
};

pub struct ContractEnv;
Expand Down Expand Up @@ -71,5 +71,29 @@ pub mod contract_env {
pub fn print(_message: &str) {
unimplemented!()
}

pub fn attached_value() -> U512 {
unimplemented!()
}

pub fn one_token() -> U512 {
unimplemented!()
}

pub fn with_tokens(_amount: U512) {
unimplemented!()
}

pub fn token_balance(_address: Address) -> U512 {
unimplemented!()
}

pub fn transfer_tokens(_to: Address, _amount: U512) {
unimplemented!()
}

pub fn self_balance() -> U512 {
unimplemented!()
}
}
}
45 changes: 40 additions & 5 deletions core/src/external_api/contract_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ use crate::UnwrapOrRevert;
use odra_types::{
bytesrepr::{FromBytes, ToBytes},
event::Event,
Address, CLTyped, CLValue, EventData, ExecutionError, RuntimeArgs,
Address, CLTyped, CLValue, EventData, ExecutionError, RuntimeArgs, U512,
};

#[allow(improper_ctypes)]
extern "C" {
extern "Rust" {
fn __get_block_time() -> u64;
fn __self_address() -> Address;
fn __caller() -> Address;
Expand All @@ -15,9 +15,19 @@ extern "C" {
fn __set_dict_value(dict: &str, key: &[u8], value: &CLValue);
fn __get_dict_value(dict: &str, key: &[u8]) -> Option<CLValue>;
fn __emit_event(event: &EventData);
fn __call_contract(address: &Address, entrypoint: &str, args: &RuntimeArgs) -> Vec<u8>;
fn __call_contract(
address: &Address,
entrypoint: &str,
args: &RuntimeArgs,
amount: Option<U512>,
) -> Vec<u8>;
fn __revert(reason: &ExecutionError) -> !;
fn __print(message: &str);
fn __self_balance() -> U512;
fn __one_token() -> U512;
fn __attached_value() -> U512;
fn __with_tokens(amount: U512);
fn __transfer_tokens(to: Address, amount: U512);
}

pub struct ContractEnv;
Expand Down Expand Up @@ -72,8 +82,13 @@ impl ContractEnv {
unsafe { __emit_event(&event_data) }
}

pub fn call_contract(address: &Address, entrypoint: &str, args: &RuntimeArgs) -> Vec<u8> {
unsafe { __call_contract(address, entrypoint, args) }
pub fn call_contract(
address: &Address,
entrypoint: &str,
args: &RuntimeArgs,
amount: Option<U512>,
) -> Vec<u8> {
unsafe { __call_contract(address, entrypoint, args, amount) }
}

pub fn revert<E>(error: E) -> !
Expand All @@ -86,4 +101,24 @@ impl ContractEnv {
pub fn print(message: &str) {
unsafe { __print(message) }
}

pub fn attached_value() -> U512 {
unsafe { __attached_value() }
}

pub fn self_balance() -> U512 {
unsafe { __self_balance() }
}

pub fn one_token() -> U512 {
unsafe { __one_token() }
}

pub fn with_tokens(amount: U512) {
unsafe { __with_tokens(amount) }
}

pub fn transfer_tokens(to: Address, amount: U512) {
unsafe { __transfer_tokens(to, amount) }
}
}
8 changes: 7 additions & 1 deletion core/src/external_api/test_env.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use odra_types::{bytesrepr::Bytes, event::EventError, Address, EventData, OdraError, RuntimeArgs};
use odra_types::{
bytesrepr::Bytes, event::EventError, Address, EventData, OdraError, RuntimeArgs, U512,
};

macro_rules! delegate_to_wrapper {
(
Expand Down Expand Up @@ -47,6 +49,10 @@ impl TestEnv {
fn get_error() -> Option<OdraError>
///Increases the current value of block_time.
fn advance_block_time_by(seconds: u64)
/// Returns the balance of the account associated with the given address.
fn token_balance(address: Address) -> U512
/// Returns the value that represents one native token.
fn one_token() -> U512
}

/// Expects the `block` execution will fail with the specific error.
Expand Down
15 changes: 10 additions & 5 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mod unwrap_or_revert;
mod variable;

use std::fmt::Debug;
use types::{bytesrepr::FromBytes, Address, CLTyped, RuntimeArgs};
use types::{bytesrepr::FromBytes, Address, CLTyped, RuntimeArgs, U512};

pub use {
instance::Instance,
Expand Down Expand Up @@ -36,26 +36,31 @@ cfg_if::cfg_if! {
/// Calls contract at `address` invoking the `entrypoint` with `args`.
///
/// Returns already parsed result.
pub fn call_contract<T>(address: &Address, entrypoint: &str, args: &RuntimeArgs) -> T
pub fn call_contract<T>(
address: &Address,
entrypoint: &str,
args: &RuntimeArgs,
amount: Option<U512>,
) -> T
where
T: CLTyped + FromBytes + Debug,
{
cfg_if::cfg_if! {
if #[cfg(feature = "mock-vm")] {
let result = TestEnv::call_contract(address, entrypoint, args);
let result = TestEnv::call_contract(address, entrypoint, args, amount);
match result {
Some(bytes) => T::from_bytes(bytes.as_slice()).unwrap().0,
None => T::from_bytes(&[]).unwrap().0,
}
} else if #[cfg(feature = "wasm-test")] {
let has_return = types::CLType::Unit != T::cl_type();
let result = TestEnv::call_contract(address, entrypoint, args, has_return);
let result = TestEnv::call_contract(address, entrypoint, args, has_return, amount);
match result {
Some(bytes) => T::from_bytes(bytes.as_slice()).unwrap().0,
None => T::from_bytes(&[]).unwrap().0,
}
} else if #[cfg(feature = "wasm")] {
let res = ContractEnv::call_contract(address, entrypoint, args);
let res = ContractEnv::call_contract(address, entrypoint, args, amount);
types::bytesrepr::deserialize(res).unwrap_or_revert()
} else {
compile_error!("Unknown feature")
Expand Down
33 changes: 30 additions & 3 deletions lang/codegen/src/generator/common.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use proc_macro2::TokenStream;
use proc_macro2::{Ident, TokenStream};
use quote::{quote, TokenStreamExt};

pub(crate) fn generate_fn_body<T>(
Expand All @@ -14,12 +14,12 @@ where
match ret {
syn::ReturnType::Default => quote! {
#args
odra::call_contract::<()>(&self.address, #entrypoint_name, &args);
odra::call_contract::<()>(&self.address, #entrypoint_name, &args, self.attached_value);
},
syn::ReturnType::Type(_, _) => quote! {
use odra::types::CLTyped;
#args
odra::call_contract(&self.address, #entrypoint_name, &args)
odra::call_contract(&self.address, #entrypoint_name, &args, self.attached_value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you set self.attached_value to None after those call_contract calls?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I should, good catch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the thing is, it implies all the functions must be mutable. If so, a user has to define a contract ref as mutable but is not too intuitive.

},
}
}
Expand All @@ -36,6 +36,33 @@ where
.collect::<Vec<_>>()
}

pub(crate) fn build_ref(ref_ident: &Ident) -> TokenStream {
quote! {
pub struct #ref_ident {
address: odra::types::Address,
attached_value: Option<odra::types::U512>,
}

impl #ref_ident {
pub fn at(address: odra::types::Address) -> Self {
Self { address, attached_value: None }
}

pub fn address(&self) -> odra::types::Address {
self.address.clone()
}

pub fn with_tokens<T>(&self, amount: T) -> Self
where T: Into<odra::types::U512> {
Self {
address: self.address,
attached_value: Some(amount.into()),
}
}
}
}
}

fn parse_args<T>(syn_args: T) -> TokenStream
where
T: IntoIterator<Item = syn::PatType>,
Expand Down
17 changes: 3 additions & 14 deletions lang/codegen/src/generator/external_contract_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use derive_more::From;
use odra_ir::ExternalContractItem as IrExternalContractItem;
use syn::parse_quote;

use super::common;
use super::common::{self, build_ref};
use crate::GenerateCode;

#[derive(From)]
Expand Down Expand Up @@ -40,23 +40,12 @@ impl GenerateCode for ExternalContractItem<'_> {
};
result
});
let contract_ref = build_ref(ref_ident);

quote::quote! {
#item_trait

pub struct #ref_ident {
address: odra::types::Address,
}

impl #ref_ident {
fn at(address: odra::types::Address) -> Self {
Self { address }
}

fn address(&self) -> odra::types::Address {
self.address.clone()
}
}
#contract_ref

impl #trait_ident for #ref_ident {
# ( #methods) *
Expand Down
18 changes: 13 additions & 5 deletions lang/codegen/src/generator/module_impl/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ impl GenerateCode for Deploy<'_> {
impl #struct_ident {
pub fn deploy() -> #ref_ident {
let address = odra::TestEnv::register_contract(&#struct_snake_case, &odra::types::RuntimeArgs::new());
#ref_ident { address }
#ref_ident::at(address)
}

#constructors_wasm_test
Expand All @@ -93,7 +93,7 @@ impl GenerateCode for Deploy<'_> {
#constructors

let address = odra::TestEnv::register_contract(None, constructors, entrypoints);
#ref_ident { address }
#ref_ident::at(address)
}

#constructors_mock_vm
Expand Down Expand Up @@ -158,7 +158,7 @@ where
}
));
let address = odra::TestEnv::register_contract(constructor, constructors, entrypoints);
#ref_ident { address }
#ref_ident::at(address)
}
}
}).collect::<TokenStream>()
Expand Down Expand Up @@ -209,7 +209,7 @@ where
let mut args = { #args };
args.insert("constructor", stringify!(#constructor_ident)).unwrap();
let address = odra::TestEnv::register_contract(#struct_name_snake_case, &args);
#ref_ident { address }
#ref_ident::at(address)
}
}
})
Expand All @@ -233,9 +233,17 @@ where
};
let args = args_to_fn_args(&entrypoint.args);
let arg_names = args_to_arg_names_stream(&entrypoint.args);

let attached_value_check = match entrypoint.is_payable() {
true => quote!(),
false => quote! {
if odra::ContractEnv::attached_value() > odra::types::U512::zero() {
odra::ContractEnv::revert(odra::types::ExecutionError::non_payable());
}
},
};
quote! {
entrypoints.insert(#name, (#arg_names, |name, args| {
#attached_value_check
let instance = <#struct_ident as odra::Instance>::instance(name.as_str());
let result = instance.#ident(#args);
#return_value
Expand Down
19 changes: 7 additions & 12 deletions lang/codegen/src/generator/module_impl/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ use odra_ir::module::{ImplItem, ModuleImpl};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};

use crate::{generator::common, GenerateCode};
use crate::{
generator::common::{self, build_ref},
GenerateCode,
};

#[derive(From)]
pub struct ContractReference<'a> {
Expand All @@ -23,23 +26,15 @@ impl GenerateCode for ContractReference<'_> {

let ref_constructors = build_constructors(&methods);

let contract_ref = build_ref(&ref_ident);

quote! {
pub struct #ref_ident {
address: odra::types::Address,
}
#contract_ref

impl #ref_ident {
#ref_entrypoints

#ref_constructors

pub fn address(&self) -> odra::types::Address {
self.address.clone()
}

pub fn at(address: odra::types::Address) -> Self {
Self { address }
}
}
}
}
Expand Down
Loading