Skip to content

Commit

Permalink
Group all derived module structs into single trait (#509)
Browse files Browse the repository at this point in the history
* Add OdraContract trait grouping module-related structs
* Change `Deploy` definition to take advantage of `OdraContract`
* Update `deploy` calls in modules and examples
  • Loading branch information
kpob authored Aug 6, 2024
1 parent 6dba46d commit efbe1ff
Show file tree
Hide file tree
Showing 73 changed files with 472 additions and 402 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Changelog for `odra`.

## [1.3.0] - 2024-08-xx
### Added
- `OdraContract` trait grouping module-related structures.
### Changed
- `Deployer` is no longer implemented for `HostRef` but for `OdraContract`.

## [1.2.0] - 2024-07-30
### Added
- `Extern` type for simple external calls.
Expand Down
16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ exclude = [ "examples", "modules", "benchmark", "odra-casper/proxy-caller", "tem
resolver = "2"

[workspace.package]
version = "1.2.0"
version = "1.3.0"
authors = ["Jakub Płaskonka <[email protected]>", "Krzysztof Pobiarżyn <[email protected]>", "Maciej Zieliński <[email protected]>"]
license = "MIT"
homepage = "https://odra.dev/docs"
repository = "https://github.com/odradev/odra"

[workspace.dependencies]
odra-core = { path = "core", version = "1.2.0" }
odra-macros = { path = "odra-macros", version = "1.2.0" }
odra-casper-test-vm = { path = "odra-casper/test-vm", version = "1.2.0" }
odra-casper-rpc-client = { path = "odra-casper/rpc-client", version = "1.2.0" }
odra-vm = { path = "odra-vm", version = "1.2.0" }
odra-casper-wasm-env = { path = "odra-casper/wasm-env", version = "1.2.0"}
odra-schema = { path = "odra-schema", version = "1.2.0" }
odra-core = { path = "core", version = "1.3.0" }
odra-macros = { path = "odra-macros", version = "1.3.0" }
odra-casper-test-vm = { path = "odra-casper/test-vm", version = "1.3.0" }
odra-casper-rpc-client = { path = "odra-casper/rpc-client", version = "1.3.0" }
odra-vm = { path = "odra-vm", version = "1.3.0" }
odra-casper-wasm-env = { path = "odra-casper/wasm-env", version = "1.3.0"}
odra-schema = { path = "odra-schema", version = "1.3.0" }
casper-contract = { version = "4.0.0", default-features = false }
casper-types = { version = "4.0.1", default-features = false }
casper-execution-engine = "7.0.1"
Expand Down
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,
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"))]
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;

/// 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
3 changes: 3 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 All @@ -22,6 +23,7 @@ pub mod entry_point_callback;
mod error;
mod external;
mod gas_report;
// TODO: consider making this not wasm32
pub mod host;
mod list;
mod mapping;
Expand All @@ -47,6 +49,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
2 changes: 1 addition & 1 deletion examples/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "odra-examples"
version = "1.2.0"
version = "1.3.0"
edition = "2021"

[dependencies]
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)
}
Loading

0 comments on commit efbe1ff

Please sign in to comment.