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

Group all derived module structs into single trait #509

Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 2 additions & 2 deletions benchmark/bin/benchmark.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use benchmark::benchmark::{BenchmarkHostRef, StructVariable};
use benchmark::benchmark::{Benchmark, StructVariable};
use odra::host::{Deployer, HostRef, NoArgs};
use odra_test::env;
use std::fs;
Expand All @@ -8,7 +8,7 @@ pub fn main() {
println!("Running benchmark...");
let env = env();

let mut contract = BenchmarkHostRef::deploy(&env, NoArgs);
let mut contract = Benchmark::deploy(&env, NoArgs);
// Var
contract.set_variable(true);
assert!(contract.get_variable());
Expand Down
13 changes: 13 additions & 0 deletions core/src/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// The contract trait.
pub trait OdraContract {
/// The host reference type.
#[cfg(not(target_arch = "wasm32"))]
type HostRef: crate::host::HostRef
+ crate::host::EntryPointsCallerProvider
+ crate::contract_def::HasIdent;
/// The contract reference type.
type ContractRef: crate::ContractRef;
/// The init args type.
#[cfg(not(target_arch = "wasm32"))]
type InitArgs: crate::host::InitArgs;
}
1 change: 1 addition & 0 deletions core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ impl ExecutionError {
unsafe {
match self {
ExecutionError::User(code) => *code,
ExecutionError::MaxUserError => 64535,
Copy link
Contributor

Choose a reason for hiding this comment

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

why?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was a missing mapping and was causing an overflow error.

ExecutionError::UserErrorTooHigh => 64536,
_ => ExecutionError::UserErrorTooHigh.code() + *(self as *const Self as *const u16)
}
Expand Down
140 changes: 70 additions & 70 deletions core/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
use crate::address::Addressable;
use crate::gas_report::GasReport;
use crate::{
call_result::CallResult, contract_def::HasIdent, entry_point_callback::EntryPointsCaller,
Address, CallDef, ContractCallResult, ContractEnv, EventError, OdraError, OdraResult, VmError
call_result::CallResult, entry_point_callback::EntryPointsCaller, Address, CallDef,
ContractCallResult, ContractEnv, EventError, OdraError, OdraResult, VmError
};
use crate::{consts, prelude::*, utils, ExecutionError};
#[cfg(not(target_arch = "wasm32"))]
use crate::{contract::OdraContract, contract_def::HasIdent};
use casper_event_standard::EventInstance;
use casper_types::{
bytesrepr::{Bytes, FromBytes, ToBytes},
Expand Down Expand Up @@ -45,9 +47,9 @@ impl<T: HostRef> Addressable for T {
/// Trait for loading a contract from the host environment.
///
/// Similar to [Deployer], but does not deploy a new contract, but loads an existing one.
pub trait HostRefLoader {
pub trait HostRefLoader<T: HostRef> {
/// Loads an existing contract from the host environment.
fn load(env: &HostEnv, address: Address) -> Self;
fn load(env: &HostEnv, address: Address) -> T;
}

/// A type which can provide an [EntryPointsCaller].
Expand All @@ -62,29 +64,25 @@ pub trait EntryPointsCallerProvider {
/// on a virtual machine or on a real blockchain.
///
/// The `Deployer` trait provides a simple way to deploy a contract.
pub trait Deployer: Sized {
#[cfg(not(target_arch = "wasm32"))]
kpob marked this conversation as resolved.
Show resolved Hide resolved
pub trait Deployer<R: OdraContract>: Sized {
/// Deploys a contract with given init args.
///
/// If the init args are provided, the contract is deployed and initialized
/// by calling the constructor. If the init args are not provided, the contract
/// is deployed without initialization.
///
/// Returns a host reference to the deployed contract.
fn deploy<T: InitArgs>(env: &HostEnv, init_args: T) -> Self;
fn deploy(env: &HostEnv, init_args: R::InitArgs) -> R::HostRef;
kpob marked this conversation as resolved.
Show resolved Hide resolved

/// Tries to deploy a contract with given init args.
///
/// Similar to `deploy`, but returns a result instead of panicking.
fn try_deploy<T: InitArgs>(env: &HostEnv, init_args: T) -> OdraResult<Self>;
fn try_deploy(env: &HostEnv, init_args: R::InitArgs) -> OdraResult<R::HostRef>;
}

/// A type which can be used as initialization arguments for a contract.
pub trait InitArgs: Into<RuntimeArgs> {
/// Validates the args are used to initialized the right contact.
///
/// If the `expected_ident` does not match the contract ident, the method returns `false`.
fn validate(expected_ident: &str) -> bool;
}
pub trait InitArgs: Into<RuntimeArgs> {}

/// Default implementation of [InitArgs]. Should be used when the contract
/// does not require initialization arguments.
Expand All @@ -93,21 +91,21 @@ pub trait InitArgs: Into<RuntimeArgs> {
/// or does not require any arguments.
pub struct NoArgs;

impl InitArgs for NoArgs {
fn validate(_expected_ident: &str) -> bool {
true
}
}
impl InitArgs for NoArgs {}

impl From<NoArgs> for RuntimeArgs {
fn from(_: NoArgs) -> Self {
RuntimeArgs::new()
}
}

impl<R: HostRef + EntryPointsCallerProvider + HasIdent> Deployer for R {
fn deploy<T: InitArgs>(env: &HostEnv, init_args: T) -> Self {
let contract_ident = R::ident();
#[cfg(not(target_arch = "wasm32"))]
impl<R: OdraContract> Deployer<R> for R {
fn deploy(
env: &HostEnv,
init_args: <R as OdraContract>::InitArgs
) -> <R as OdraContract>::HostRef {
let contract_ident = R::HostRef::ident();
match Self::try_deploy(env, init_args) {
Ok(contract) => contract,
Err(OdraError::ExecutionError(ExecutionError::MissingArg)) => {
Expand All @@ -117,24 +115,25 @@ impl<R: HostRef + EntryPointsCallerProvider + HasIdent> Deployer for R {
}
}

fn try_deploy<T: InitArgs>(env: &HostEnv, init_args: T) -> OdraResult<Self> {
let contract_ident = R::ident();
if !T::validate(&contract_ident) {
return Err(OdraError::ExecutionError(ExecutionError::MissingArg));
}
fn try_deploy(
env: &HostEnv,
init_args: <R as OdraContract>::InitArgs
) -> OdraResult<<R as OdraContract>::HostRef> {
let contract_ident = R::HostRef::ident();

let caller = R::entry_points_caller(env);
let caller = R::HostRef::entry_points_caller(env);
let address = env.new_contract(&contract_ident, init_args.into(), caller)?;
Ok(R::new(address, env.clone()))
Ok(R::HostRef::new(address, env.clone()))
}
}

impl<T: EntryPointsCallerProvider + HostRef + HasIdent> HostRefLoader for T {
fn load(env: &HostEnv, address: Address) -> Self {
let caller = T::entry_points_caller(env);
let contract_name = T::ident();
#[cfg(not(target_arch = "wasm32"))]
impl<T: OdraContract> HostRefLoader<T::HostRef> for T {
fn load(env: &HostEnv, address: Address) -> T::HostRef {
let caller = T::HostRef::entry_points_caller(env);
let contract_name = T::HostRef::ident();
env.register_contract(address, contract_name, caller);
T::new(address, env.clone())
T::HostRef::new(address, env.clone())
}
}

Expand Down Expand Up @@ -311,8 +310,24 @@ impl HostEnv {
address: Address,
call_def: CallDef
) -> OdraResult<T> {
let backend = self.backend.borrow();
let use_proxy = T::cl_type() != <()>::cl_type() || !call_def.amount().is_zero();
let call_result = self.raw_call_contract(address, call_def, use_proxy);
call_result.map(|bytes| {
T::from_bytes(&bytes)
.map(|(obj, _)| obj)
.map_err(|_| OdraError::VmError(VmError::Deserialization))
})?
}

/// Calls a contract at the specified address with the given call definition. Returns raw,
/// not serialized bytes.
pub fn raw_call_contract(
&self,
address: Address,
call_def: CallDef,
use_proxy: bool
) -> OdraResult<Bytes> {
let backend = self.backend.borrow();
let call_result = backend.call_contract(&address, call_def, use_proxy);

let mut events_map: BTreeMap<Address, Vec<Bytes>> = BTreeMap::new();
Expand Down Expand Up @@ -347,11 +362,7 @@ impl HostEnv {
events_map
)));

call_result.map(|bytes| {
T::from_bytes(&bytes)
.map(|(obj, _)| obj)
.map_err(|_| OdraError::VmError(VmError::Deserialization))
})?
call_result
}

/// Returns the gas cost of the last contract call.
Expand Down Expand Up @@ -553,7 +564,6 @@ mod test {

static IDENT_MTX: Mutex<()> = Mutex::new(());
static EPC_MTX: Mutex<()> = Mutex::new(());
static VALIDATE_MTX: Mutex<()> = Mutex::new(());

#[derive(Debug, Event, PartialEq)]
struct TestEv {}
Expand All @@ -576,11 +586,25 @@ mod test {
}
}

impl ContractRef for MockTestRef {
fn new(_env: Rc<ContractEnv>, _address: Address) -> Self {
unimplemented!()
}
fn address(&self) -> &Address {
unimplemented!()
}
}

impl OdraContract for MockTestRef {
type HostRef = MockTestRef;

type ContractRef = MockTestRef;

type InitArgs = NoArgs;
}

mock! {
Ev {}
impl InitArgs for Ev {
fn validate(expected_ident: &str) -> bool;
}
impl Into<RuntimeArgs> for Ev {
fn into(self) -> RuntimeArgs;
}
Expand Down Expand Up @@ -615,43 +639,19 @@ mod test {
ctx.expect_new_contract()
.returning(|_, _, _| Ok(Address::Account(AccountHash::new([0; 32]))));
let env = HostEnv::new(Rc::new(RefCell::new(ctx)));
<MockTestRef as Deployer>::deploy(&env, NoArgs);
}

#[test]
#[should_panic(expected = "Invalid init args for contract TestRef.")]
fn test_deploy_with_invalid_args() {
// MockTestRef::ident() and MockEv::validate() are static and can't be safely used
// from multiple tests at the same time. Should be to protected with a Mutex. Each function has
// a separate Mutex.
// https://github.com/asomers/mockall/blob/master/mockall/tests/mock_struct_with_static_method.rs
let _i = IDENT_MTX.lock();
let _v = VALIDATE_MTX.lock();

// stubs
let args_ctx = MockEv::validate_context();
args_ctx.expect().returning(|_| false);
let indent_ctx = MockTestRef::ident_context();
indent_ctx.expect().returning(|| "TestRef".to_string());

let env = HostEnv::new(Rc::new(RefCell::new(MockHostContext::new())));
let args = MockEv::new();
MockTestRef::deploy(&env, args);
MockTestRef::deploy(&env, NoArgs);
}

#[test]
fn test_load_ref() {
// MockTestRef::ident(), MockEv::validate(), MockTestRef::entry_points_caller() are static and can't be safely used
// MockTestRef::ident() and MockTestRef::entry_points_caller() are static and can't be safely used
// from multiple tests at the same time. Should be to protected with a Mutex. Each function has
// a separate Mutex.
// https://github.com/asomers/mockall/blob/master/mockall/tests/mock_struct_with_static_method.rs
let _e = EPC_MTX.lock();
let _i = IDENT_MTX.lock();
let _v = VALIDATE_MTX.lock();

// stubs
let args_ctx = MockEv::validate_context();
args_ctx.expect().returning(|_| true);
let epc_ctx = MockTestRef::entry_points_caller_context();
epc_ctx
.expect()
Expand All @@ -672,7 +672,7 @@ mod test {

let env = HostEnv::new(Rc::new(RefCell::new(ctx)));
let address = Address::Account(AccountHash::new([0; 32]));
<MockTestRef as HostRefLoader>::load(&env, address);
MockTestRef::load(&env, address);
}

#[test]
Expand Down
2 changes: 2 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod call_result;
pub mod callstack;
#[doc(hidden)]
pub mod consts;
mod contract;
mod contract_container;
mod contract_context;
pub mod contract_def;
Expand Down Expand Up @@ -47,6 +48,7 @@ pub use error::{
};
pub use unwrap_or_revert::UnwrapOrRevert;

pub use contract::OdraContract;
pub use external::External;
pub use gas_report::*;
pub use list::{List, ListIter};
Expand Down
6 changes: 3 additions & 3 deletions examples/bin/cep18_on_livenet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use odra::casper_types::U256;
use odra::host::{Deployer, HostEnv, HostRefLoader};
use odra::Address;
use odra_modules::cep18::utils::Cep18Modality;
use odra_modules::cep18_token::{Cep18HostRef, Cep18InitArgs};
use odra_modules::cep18_token::{Cep18, Cep18HostRef, Cep18InitArgs};

fn main() {
let env = odra_casper_livenet_env::env();
Expand Down Expand Up @@ -40,7 +40,7 @@ fn _load_cep18(env: &HostEnv) -> Cep18HostRef {
let address =
Address::new("hash-568fd396922fbbc8f8499f9b888795b2155aa60a68ef9cc38752b2771693a9ce")
.unwrap();
Cep18HostRef::load(env, address)
Cep18::load(env, address)
}

/// Deploys a CEP-18 contract.
Expand All @@ -61,5 +61,5 @@ pub fn deploy_cep18(env: &HostEnv) -> Cep18HostRef {
};

env.set_gas(300_000_000_000u64);
Cep18HostRef::deploy(env, init_args)
Cep18::deploy(env, init_args)
}
8 changes: 4 additions & 4 deletions examples/bin/cep78_on_livenet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use odra::Address;
use odra_modules::cep78::modalities::{
EventsMode, MetadataMutability, NFTIdentifierMode, NFTKind, NFTMetadataKind, OwnershipMode
};
use odra_modules::cep78::token::{TestCep78HostRef, TestCep78InitArgs};
use odra_modules::cep78::token::{TestCep78, TestCep78HostRef, TestCep78InitArgs};
use odra_modules::cep78::utils::InitArgsBuilder;

const CEP78_METADATA: &str = r#"{
Expand Down Expand Up @@ -47,9 +47,9 @@ fn main() {
}

/// Loads a Cep78 contract.
pub fn load_contract(env: &HostEnv, address: &str) -> TestCep78HostRef {
pub fn load_contract(env: &HostEnv, address: &'static str) -> TestCep78HostRef {
let address = Address::new(address).expect("Should be a valid contract address");
kpob marked this conversation as resolved.
Show resolved Hide resolved
TestCep78HostRef::load(env, address)
TestCep78::load(env, address)
}

/// Deploys a Cep78 contract.
Expand All @@ -72,5 +72,5 @@ pub fn deploy_contract(env: &HostEnv) -> TestCep78HostRef {
.build();

env.set_gas(430_000_000_000u64);
TestCep78HostRef::deploy(env, init_args)
TestCep78::deploy(env, init_args)
}
6 changes: 3 additions & 3 deletions examples/bin/erc20_on_livenet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use odra::casper_types::U256;
use odra::host::{Deployer, HostEnv, HostRef, HostRefLoader};
use odra::Address;
use odra_modules::erc20::{Erc20HostRef, Erc20InitArgs};
use odra_modules::erc20::{Erc20, Erc20HostRef, Erc20InitArgs};
use std::str::FromStr;

fn main() {
Expand Down Expand Up @@ -32,7 +32,7 @@ fn main() {
fn _load_erc20(env: &HostEnv) -> Erc20HostRef {
let address = "hash-d26fcbd2106e37be975d2045c580334a6d7b9d0a241c2358a4db970dfd516945";
let address = Address::from_str(address).unwrap();
Erc20HostRef::load(env, address)
Erc20::load(env, address)
}

/// Deploys an ERC20 contract.
Expand All @@ -50,5 +50,5 @@ pub fn deploy_erc20(env: &HostEnv) -> Erc20HostRef {
};

env.set_gas(100_000_000_000u64);
Erc20HostRef::deploy(env, init_args)
Erc20::deploy(env, init_args)
}
Loading
Loading