diff --git a/examples2/src/counter_pack.rs b/examples2/src/counter_pack.rs index db64004a..4deedf05 100644 --- a/examples2/src/counter_pack.rs +++ b/examples2/src/counter_pack.rs @@ -196,29 +196,33 @@ mod __counter_pack_test_parts { impl CounterPackHostRef { pub fn get_count(&self, index_a: u8, index_b: u8) -> u32 { - self.env.call_contract( - &self.address, - CallDef::new( - String::from("get_count"), - runtime_args! { - "index_a" => index_a, - "index_b" => index_b - } + self.env + .call_contract( + &self.address, + CallDef::new( + String::from("get_count"), + runtime_args! { + "index_a" => index_a, + "index_b" => index_b + } + ) ) - ).unwrap() + .unwrap() } pub fn increment(&self, index_a: u8, index_b: u8) { - self.env.call_contract( - &self.address, - CallDef::new( - String::from("increment"), - runtime_args! { - "index_a" => index_a, - "index_b" => index_b - } + self.env + .call_contract( + &self.address, + CallDef::new( + String::from("increment"), + runtime_args! { + "index_a" => index_a, + "index_b" => index_b + } + ) ) - ).unwrap() + .unwrap() } } diff --git a/examples2/src/erc20.rs b/examples2/src/erc20.rs index 793cb8da..cdd27e69 100644 --- a/examples2/src/erc20.rs +++ b/examples2/src/erc20.rs @@ -12,6 +12,14 @@ pub struct Transfer { pub amount: U256 } +#[derive(Event, Eq, PartialEq, Debug)] +pub struct CrossTransfer { + pub from: Option
, + pub to: Option
, + pub other_contract: Address, + pub amount: U256 +} + #[derive(Event, Eq, PartialEq, Debug)] pub struct Approval { pub owner: Address, @@ -45,6 +53,14 @@ impl Erc20 { } } + pub fn approve(&mut self, to: Address, amount: U256) { + self.env.emit_event(Approval { + owner: self.env.caller(), + spender: to, + value: amount + }); + } + pub fn total_supply(&self) -> U256 { self.total_supply.get_or_default() } @@ -105,6 +121,23 @@ impl Erc20 { self.env() .transfer_tokens(&caller, &U512::from(amount.as_u64())); } + + pub fn cross_transfer(&mut self, other: Address, to: Address, value: U256) { + let caller = self.env().caller(); + + let other_erc20 = Erc20ContractRef { + address: other, + env: self.env() + }; + + other_erc20.transfer(to, value); + self.env.emit_event(CrossTransfer { + from: Some(self.env.self_address()), + to: Some(to), + other_contract: other, + amount: value + }); + } } // autogenerated for general purpose module. @@ -221,6 +254,27 @@ mod __erc20_wasm_parts { EntryPointAccess::Public, EntryPointType::Contract )); + entry_points.add_entry_point(EntryPoint::new( + "approve", + alloc::vec![ + Parameter::new("to", Address::cl_type()), + Parameter::new("amount", U256::cl_type()), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract + )); + entry_points.add_entry_point(EntryPoint::new( + "cross_transfer", + alloc::vec![ + Parameter::new("other", Address::cl_type()), + Parameter::new("to", Address::cl_type()), + Parameter::new("value", U256::cl_type()), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract + )); entry_points } @@ -297,6 +351,23 @@ mod __erc20_wasm_parts { contract.burn_and_get_paid(amount); } + pub fn execute_approve() { + let to: Address = runtime::get_named_arg("to"); + let amount: U256 = runtime::get_named_arg("amount"); + let env = WasmContractEnv::new(); + let mut contract: Erc20 = Erc20::new(Rc::new(env)); + contract.approve(to, amount); + } + + pub fn execute_cross_transfer() { + let other: Address = runtime::get_named_arg("other"); + let to: Address = runtime::get_named_arg("to"); + let value: U256 = runtime::get_named_arg("value"); + let env = WasmContractEnv::new(); + let mut contract: Erc20 = Erc20::new(Rc::new(env)); + contract.cross_transfer(other, to, value); + } + #[no_mangle] fn call() { execute_call(); @@ -341,6 +412,16 @@ mod __erc20_wasm_parts { fn burn_and_get_paid() { execute_burn_and_get_paid(); } + + #[no_mangle] + fn approve() { + execute_approve(); + } + + #[no_mangle] + fn cross_transfer() { + execute_cross_transfer(); + } } pub struct Erc20ContractRef { @@ -355,6 +436,19 @@ impl Erc20ContractRef { CallDef::new(String::from("total_supply"), RuntimeArgs::new()) ) } + + pub fn transfer(&self, to: Address, value: U256) { + self.env.call_contract( + self.address, + CallDef::new( + String::from("transfer"), + runtime_args! { + "to" => to, + "value" => value + } + ) + ) + } } #[cfg(not(target_arch = "wasm32"))] @@ -366,7 +460,9 @@ mod __erc20_test_parts { use odra2::event::EventError; use odra2::prelude::*; use odra2::types::casper_types::EntryPoints; - use odra2::types::{runtime_args, Address, Bytes, RuntimeArgs, ToBytes, U256, U512, FromBytes, OdraError}; + use odra2::types::{ + runtime_args, Address, Bytes, FromBytes, OdraError, RuntimeArgs, ToBytes, U256, U512 + }; use odra2::{CallDef, ContractEnv, EntryPointsCaller, HostEnv}; pub struct Erc20HostRef { @@ -394,7 +490,7 @@ mod __erc20_test_parts { pub fn total_supply(&self) -> U256 { self.try_total_supply().unwrap() } - + pub fn try_balance_of(&self, owner: Address) -> Result { self.env.call_contract( &self.address, @@ -406,7 +502,7 @@ mod __erc20_test_parts { ) ) } - + pub fn balance_of(&self, owner: Address) -> U256 { self.try_balance_of(owner).unwrap() } @@ -488,9 +584,48 @@ mod __erc20_test_parts { self.try_burn_and_get_paid(amount).unwrap() } + pub fn try_approve(&self, to: Address, amount: U256) -> Result<(), OdraError> { + self.env.call_contract( + &self.address, + CallDef::new( + String::from("approve"), + runtime_args! { + "to" => to, + "amount" => amount + } + ) + ) + } + + pub fn approve(&self, to: Address, amount: U256) { + self.try_approve(to, amount).unwrap() + } + pub fn get_event(&self, index: i32) -> Result { self.env.get_event(&self.address, index) } + pub fn try_cross_transfer( + &self, + other: Address, + to: Address, + value: U256 + ) -> Result<(), OdraError> { + self.env.call_contract( + &self.address, + CallDef::new( + String::from("cross_transfer"), + runtime_args! { + "other" => other, + "to" => to, + "value" => value + } + ) + ) + } + + pub fn cross_transfer(&self, other: Address, to: Address, value: U256) { + self.try_cross_transfer(other, to, value).unwrap() + } } pub struct Erc20Deployer; @@ -539,6 +674,19 @@ mod __erc20_test_parts { let result = erc20.burn_and_get_paid(amount); Bytes::from(result.to_bytes().unwrap()) } + "approve" => { + let to: Address = call_def.get("to").unwrap(); + let amount: U256 = call_def.get("amount").unwrap(); + let result = erc20.approve(to, amount); + Bytes::from(result.to_bytes().unwrap()) + } + "cross_transfer" => { + let other: Address = call_def.get("other").unwrap(); + let to: Address = call_def.get("to").unwrap(); + let value: U256 = call_def.get("value").unwrap(); + let result = erc20.cross_transfer(other, to, value); + Bytes::from(result.to_bytes().unwrap()) + } _ => panic!("Unknown method") } }); @@ -560,20 +708,20 @@ mod __erc20_test_parts { } } -#[cfg(not(target_arch = "wasm32"))] -use crate::erc20::tests::casper_event_standard::casper_types::system::mint::Error::InsufficientFunds; #[cfg(not(target_arch = "wasm32"))] pub use __erc20_test_parts::*; -use odra2::types::{ExecutionError, OdraError, RuntimeArgs}; +use odra2::types::{runtime_args, ExecutionError, OdraError, RuntimeArgs}; #[cfg(not(target_arch = "wasm32"))] #[cfg(test)] mod tests { pub use super::*; + use odra2::types::casper_types::system::mint::Error::InsufficientFunds; use odra2::types::ExecutionError; use odra2::types::OdraError; - use odra2::types::ToBytes; use odra2::types::U512; + use odra2::types::{Bytes, ToBytes}; + use odra2::CallResult; #[test] fn erc20_works() { @@ -629,25 +777,156 @@ mod tests { pobcoin.burn_and_get_paid(100.into()); assert_eq!(env.balance_of(&alice), current_balance + U512::from(100)); + env.print_gas_report() + } + + #[test] + fn erc20_call_result() { + let env = odra2::test_env(); + let alice = env.get_account(0); + let bob = env.get_account(1); + + // Deploy the contract as Alice. + let erc20 = Erc20Deployer::init(&env, Some(100.into())); + let pobcoin = Erc20Deployer::init(&env, Some(100.into())); + + // Make a call or two + erc20.transfer(bob, 10.into()); + erc20.transfer(bob, 30.into()); + + // Test call result + assert_eq!(env.get_events_count(&erc20.address), 2); + + let call_result = env.last_call_result(); + assert!(call_result.result.is_ok()); + assert_eq!(call_result.contract_address, erc20.address); + assert_eq!(call_result.caller, alice); + assert_eq!(call_result.get_result(), vec![].into()); + assert_eq!( + call_result.get_events(&erc20.address), + vec![Bytes::from( + Transfer { + from: Some(alice), + to: Some(bob), + amount: 30.into() + } + .to_bytes() + .unwrap() + )] + ); + + // call with error + erc20.try_transfer(bob, 100_000_000.into()).unwrap_err(); + let call_result = env.last_call_result(); + assert!(call_result.result.is_err()); + assert_eq!(call_result.contract_address, erc20.address); + assert_eq!(call_result.caller, alice); + assert_eq!(call_result.events.get(&erc20.address).unwrap(), &vec![]); + + // cross call + pobcoin.transfer(erc20.address, 100.into()); + erc20.cross_transfer(pobcoin.address, alice, 50.into()); + let call_result = env.last_call_result(); + + assert_eq!( + call_result.get_events(&pobcoin.address), + vec![Bytes::from( + Transfer { + from: Some(erc20.address), + to: Some(alice), + amount: 50.into() + } + .to_bytes() + .unwrap() + )] + ); + assert_eq!( + call_result.get_events(&erc20.address), + vec![Bytes::from( + CrossTransfer { + from: Some(erc20.address), + to: Some(alice), + other_contract: pobcoin.address, + amount: 50.into() + } + .to_bytes() + .unwrap() + )] + ); + } + + #[test] + fn erc20_events_work() { + let env = odra2::test_env(); + let alice = env.get_account(0); + let bob = env.get_account(1); + let charlie = env.get_account(2); + + // Deploy the contract as Alice. + let erc20 = Erc20Deployer::init(&env, Some(100.into())); + + // Emit some events + erc20.transfer(bob, 10.into()); + erc20.approve(bob, 10.into()); + erc20.transfer(charlie, 20.into()); + // Test events let event: Transfer = erc20.get_event(0).unwrap(); - assert_eq!(event.from, Some(alice)); - assert_eq!(event.to, Some(bob)); - assert_eq!(event.amount, 10.into()); + assert_eq!( + event, + Transfer { + from: Some(alice), + to: Some(bob), + amount: 10.into() + } + ); - // Test errors - // Following line panics - // erc20.transfer(alice, 1_000_000.into()); - // But this one doesn't - let result = erc20.try_transfer(alice, 1_000_000.into()); + let event: Approval = erc20.get_event(1).unwrap(); + assert_eq!( + event, + Approval { + owner: alice, + spender: bob, + value: 10.into() + } + ); + + let event: Transfer = erc20.get_event(2).unwrap(); assert_eq!( - result, - Err(Erc20Error::InsufficientBalance.into()) + event, + Transfer { + from: Some(alice), + to: Some(charlie), + amount: 20.into() + } ); + + // Test negative indices + let event: Transfer = erc20.get_event(-1).unwrap(); + assert_eq!( + event, + Transfer { + from: Some(alice), + to: Some(charlie), + amount: 20.into() + } + ); + } + + #[test] + fn test_erc20_errors() { + let env = odra2::test_env(); + let alice = env.get_account(0); + + // Deploy the contract as Alice. + let erc20 = Erc20Deployer::init(&env, Some(100.into())); + + // Test errors + let result = erc20.try_transfer(alice, 1_000_000.into()); + assert_eq!(result, Err(Erc20Error::InsufficientBalance.into())); + // With return value let result = erc20.try_balance_of(alice); assert_eq!(result, Ok(100.into())); - - env.print_gas_report() } } diff --git a/justfile b/justfile index 592581a1..2c2c59cc 100644 --- a/justfile +++ b/justfile @@ -76,7 +76,7 @@ build-erc20: mv examples2/target/wasm32-unknown-unknown/release/contract.wasm examples2/wasm/erc20.wasm test-erc20: build-erc20 - cd examples2 && ODRA_BACKEND=casper cargo test --lib erc20_works -- --nocapture + cd examples2 && ODRA_BACKEND=casper cargo test --lib erc20 -- --nocapture build-counter-pack: cd examples2 && ODRA_MODULE=CounterPack cargo build --release --target wasm32-unknown-unknown --bin counter_pack diff --git a/odra-casper/test-vm/src/casper_host.rs b/odra-casper/test-vm/src/casper_host.rs index c4e07f30..81bc4924 100644 --- a/odra-casper/test-vm/src/casper_host.rs +++ b/odra-casper/test-vm/src/casper_host.rs @@ -22,7 +22,7 @@ use odra_types::casper_types::bytesrepr::{Bytes, ToBytes}; use odra_types::casper_types::{ runtime_args, BlockTime, ContractPackageHash, Key, Motes, SecretKey }; -use odra_types::{Address, PublicKey, U512, OdraError, VmError}; +use odra_types::{Address, OdraError, PublicKey, VmError, U512}; use odra_types::{EventData, RuntimeArgs}; pub struct CasperHost { @@ -34,6 +34,10 @@ impl HostContext for CasperHost { self.vm.borrow_mut().set_caller(caller) } + fn caller(&self) -> Address { + self.vm.borrow().get_caller() + } + fn get_account(&self, index: usize) -> Address { self.vm.borrow().get_account(index) } @@ -51,12 +55,19 @@ impl HostContext for CasperHost { } // TODO: has the same logic as OdraVmHost::call_contract, try to share this logic in HostEnv - fn call_contract(&self, address: &Address, call_def: CallDef, use_proxy: bool) -> Result { + fn call_contract( + &self, + address: &Address, + call_def: CallDef, + use_proxy: bool + ) -> Result { let mut opt_result: Option = None; let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - opt_result = Some(self.vm - .borrow_mut() - .call_contract(address, call_def, use_proxy)); + opt_result = Some( + self.vm + .borrow_mut() + .call_contract(address, call_def, use_proxy) + ); })); match opt_result { @@ -64,10 +75,14 @@ impl HostContext for CasperHost { None => { let error = self.vm.borrow().error.clone(); Err(error.unwrap_or(OdraError::VmError(VmError::Panic))) - }, + } } } + fn get_events_count(&self, contract_address: &Address) -> u32 { + self.vm.borrow().get_events_count(contract_address) + } + fn new_contract( &self, name: &str, @@ -86,6 +101,10 @@ impl HostContext for CasperHost { fn print_gas_report(&self) { self.vm.borrow().print_gas_report() } + + fn last_call_gas_cost(&self) -> u64 { + self.vm.borrow().last_call_gas_cost() + } } impl CasperHost { diff --git a/odra-casper/test-vm/src/vm/casper_vm.rs b/odra-casper/test-vm/src/vm/casper_vm.rs index 1a608b3f..d47bb97f 100644 --- a/odra-casper/test-vm/src/vm/casper_vm.rs +++ b/odra-casper/test-vm/src/vm/casper_vm.rs @@ -14,7 +14,7 @@ use casper_engine_test_support::{ use casper_event_standard::try_full_name_from_bytes; use std::rc::Rc; -use casper_execution_engine::core::engine_state::{GenesisAccount, RunGenesisRequest, self}; +use casper_execution_engine::core::engine_state::{self, GenesisAccount, RunGenesisRequest}; use odra_casper_shared::consts; use odra_casper_shared::consts::*; use odra_core::entry_point_callback::EntryPointsCaller; @@ -23,10 +23,10 @@ use odra_core::{CallDef, ContractEnv, HostContext, HostEnv}; use odra_types::casper_types::account::{Account, AccountHash}; use odra_types::casper_types::bytesrepr::{Bytes, ToBytes}; use odra_types::casper_types::{ - runtime_args, BlockTime, Contract, ContractHash, ContractPackageHash, Key, Motes, SecretKey, - StoredValue, URef, ApiError + runtime_args, ApiError, BlockTime, Contract, ContractHash, ContractPackageHash, Key, Motes, + SecretKey, StoredValue, URef }; -use odra_types::{Address, PublicKey, U512, VmError, ExecutionError}; +use odra_types::{Address, ExecutionError, PublicKey, VmError, U512}; use odra_types::{CLTyped, FromBytes, OdraError, RuntimeArgs}; pub struct CasperVm { @@ -142,6 +142,10 @@ impl CasperVm { self.active_account = caller; } + pub fn get_caller(&self) -> Address { + self.active_account + } + pub fn get_account(&self, index: usize) -> Address { self.accounts[index] } @@ -164,19 +168,7 @@ impl CasperVm { .as_uref() .unwrap(); - let events_length: u32 = self - .context - .query( - None, - Key::Hash(contract_hash.value()), - &[String::from(consts::EVENTS_LENGTH)] - ) - .unwrap() - .as_cl_value() - .unwrap() - .clone() - .into_t() - .unwrap(); + let events_length = self.events_length(&contract_hash); let event_position = odra_utils::event_absolute_position(events_length as usize, index) .ok_or(EventError::IndexOutOfBounds)?; @@ -199,6 +191,13 @@ impl CasperVm { } } + pub fn get_events_count(&self, contract_address: &Address) -> u32 { + let contract_package_hash = contract_address.as_contract_package_hash().unwrap(); + let contract_hash: ContractHash = self.get_contract_package_hash(contract_package_hash); + + self.events_length(&contract_hash) + } + pub fn attach_value(&mut self, amount: U512) { self.attached_value = amount; } @@ -422,13 +421,37 @@ impl CasperVm { } } + pub fn last_call_gas_cost(&self) -> u64 { + self.last_call_contract_gas_cost().as_u64() + } +} + +impl CasperVm { + fn events_length(&self, contract_hash: &ContractHash) -> u32 { + self.context + .query( + None, + Key::Hash(contract_hash.value()), + &[String::from(consts::EVENTS_LENGTH)] + ) + .unwrap() + .as_cl_value() + .unwrap() + .clone() + .into_t() + .unwrap() + } + fn panic_with_error( &self, error: OdraError, entrypoint: &str, contract_package_hash: ContractPackageHash ) -> ! { - panic!("Revert: {:?} - {:?}::{}", error, contract_package_hash, entrypoint) + panic!( + "Revert: {:?} - {:?}::{}", + error, contract_package_hash, entrypoint + ) } } @@ -437,10 +460,10 @@ fn parse_error(err: engine_state::Error) -> OdraError { match exec_err { engine_state::ExecError::Revert(ApiError::MissingArgument) => { OdraError::VmError(VmError::MissingArg) - }, + } engine_state::ExecError::Revert(ApiError::Mint(0)) => { OdraError::VmError(VmError::BalanceExceeded) - }, + } engine_state::ExecError::Revert(ApiError::User(code)) => { if code == ExecutionError::NonPayable.code() { OdraError::ExecutionError(ExecutionError::NonPayable) @@ -449,10 +472,14 @@ fn parse_error(err: engine_state::Error) -> OdraError { } else { OdraError::ExecutionError(ExecutionError::User(code)) } - }, + } engine_state::ExecError::InvalidContext => OdraError::VmError(VmError::InvalidContext), - engine_state::ExecError::NoSuchMethod(name) => OdraError::VmError(VmError::NoSuchMethod(name)), - engine_state::ExecError::MissingArgument { name } => OdraError::VmError(VmError::MissingArg), + engine_state::ExecError::NoSuchMethod(name) => { + OdraError::VmError(VmError::NoSuchMethod(name)) + } + engine_state::ExecError::MissingArgument { name } => { + OdraError::VmError(VmError::MissingArg) + } _ => OdraError::VmError(VmError::Other(format!("Casper ExecError: {}", exec_err))) } } else { diff --git a/odra-core/src/call_result.rs b/odra-core/src/call_result.rs new file mode 100644 index 00000000..484a1a07 --- /dev/null +++ b/odra-core/src/call_result.rs @@ -0,0 +1,48 @@ +use alloc::collections::BTreeMap; +use alloc::vec::Vec; +use odra_types::{Address, Bytes, OdraError}; + +#[derive(Debug, Clone)] +pub struct CallResult { + pub contract_address: Address, + pub caller: Address, + pub gas_used: u64, + pub result: Result, + pub events: BTreeMap> +} + +impl CallResult { + pub fn get_events(&self, address: &Address) -> Vec { + self.events.get(address).cloned().unwrap_or_default() + } + + pub fn get_result(&self) -> Bytes { + match &self.result { + Ok(result) => result.clone(), + Err(error) => { + panic!("Last call result is an error: {:?}", error); + } + } + } + + pub fn get_error(&self) -> OdraError { + match &self.result { + Ok(_) => { + panic!("Last call result is not an error"); + } + Err(error) => error.clone() + } + } + + pub fn get_caller(&self) -> Address { + self.caller + } + + pub fn get_gas_used(&self) -> u64 { + self.gas_used + } + + pub fn get_contract_address(&self) -> Address { + self.contract_address + } +} diff --git a/odra-core/src/host_context.rs b/odra-core/src/host_context.rs index 91b17ff9..eb9ab918 100644 --- a/odra-core/src/host_context.rs +++ b/odra-core/src/host_context.rs @@ -1,18 +1,25 @@ use crate::entry_point_callback::EntryPointsCaller; use crate::event::EventError; use crate::{CallDef, ContractEnv}; -use odra_types::{Bytes, OdraError}; use odra_types::RuntimeArgs; use odra_types::{Address, U512}; +use odra_types::{Bytes, OdraError}; pub trait HostContext { fn set_caller(&self, caller: Address); + fn caller(&self) -> Address; fn get_account(&self, index: usize) -> Address; fn balance_of(&self, address: &Address) -> U512; fn advance_block_time(&self, time_diff: u64); /// Returns event bytes by contract address and index. fn get_event(&self, contract_address: &Address, index: i32) -> Result; - fn call_contract(&self, address: &Address, call_def: CallDef, use_proxy: bool) -> Result; + fn get_events_count(&self, contract_address: &Address) -> u32; + fn call_contract( + &self, + address: &Address, + call_def: CallDef, + use_proxy: bool + ) -> Result; fn new_contract( &self, name: &str, @@ -21,4 +28,5 @@ pub trait HostContext { ) -> Address; fn contract_env(&self) -> ContractEnv; fn print_gas_report(&self); + fn last_call_gas_cost(&self) -> u64; } diff --git a/odra-core/src/host_env.rs b/odra-core/src/host_env.rs index 595602ba..21aa115b 100644 --- a/odra-core/src/host_env.rs +++ b/odra-core/src/host_env.rs @@ -1,21 +1,31 @@ +use crate::call_result::CallResult; use crate::entry_point_callback::EntryPointsCaller; use crate::event::EventError; use crate::host_context::HostContext; use crate::prelude::*; use crate::{CallDef, ContractEnv}; +use alloc::collections::BTreeMap; use casper_event_standard::EventInstance; -use odra_types::{Address, U512, OdraError, VmError}; use odra_types::RuntimeArgs; +use odra_types::{Address, OdraError, VmError, U512}; use odra_types::{CLTyped, FromBytes}; #[derive(Clone)] pub struct HostEnv { - backend: Rc> + backend: Rc>, + last_call_result: Rc>>, + deployed_contracts: Rc>>, + events_count: Rc>> // contract_address -> events_count } impl HostEnv { pub fn new(backend: Rc>) -> HostEnv { - HostEnv { backend } + HostEnv { + backend, + last_call_result: RefCell::new(None).into(), + deployed_contracts: RefCell::new(vec![]).into(), + events_count: Rc::new(RefCell::new(Default::default())) + } } pub fn get_account(&self, index: usize) -> Address { @@ -40,17 +50,63 @@ impl HostEnv { entry_points_caller: Option ) -> Address { let backend = self.backend.borrow(); - backend.new_contract(name, init_args, entry_points_caller) + let deployed_contract = backend.new_contract(name, init_args, entry_points_caller); + self.deployed_contracts + .borrow_mut() + .push(deployed_contract.clone()); + self.events_count + .borrow_mut() + .insert(deployed_contract.clone(), 0); + deployed_contract } - pub fn call_contract(&self, address: &Address, call_def: CallDef) -> Result { + pub fn call_contract( + &self, + address: &Address, + call_def: CallDef + ) -> Result { let backend = self.backend.borrow(); + let use_proxy = T::cl_type() != <()>::cl_type() || !call_def.attached_value().is_zero(); let call_result = backend.call_contract(address, call_def, use_proxy); - call_result.map(|bytes| T::from_bytes(&bytes) - .map(|(obj, _)| obj) - .map_err(|_| OdraError::VmError(VmError::Deserialization)) - )? + + let mut events_map: BTreeMap> = BTreeMap::new(); + let mut binding = self.events_count.borrow_mut(); + + // Go through all contracts and collect their events + self.deployed_contracts + .borrow() + .iter() + .for_each(|contract_address| { + let events_count = binding.get_mut(contract_address).unwrap(); + let old_events_last_id = events_count.clone(); + let new_events_count = backend.get_events_count(contract_address); + let mut events = vec![]; + for event_id in old_events_last_id..new_events_count { + let event = backend + .get_event(contract_address, event_id as i32) + .unwrap(); + events.push(event); + } + + events_map.insert(contract_address.clone(), events); + + *events_count = new_events_count; + }); + + self.last_call_result.replace(Some(CallResult { + contract_address: address.clone(), + caller: backend.caller(), + gas_used: backend.last_call_gas_cost(), + result: call_result.clone(), + events: events_map + })); + + call_result.map(|bytes| { + T::from_bytes(&bytes) + .map(|(obj, _)| obj) + .map_err(|_| OdraError::VmError(VmError::Deserialization)) + })? } pub fn contract_env(&self) -> ContractEnv { @@ -85,6 +141,17 @@ impl HostEnv { } } + pub fn get_events_count(&self, contract_address: &Address) -> u32 { + let backend = self.backend.borrow(); + backend.get_events_count(contract_address) + } + + pub fn last_call_result(&self) -> CallResult { + self.last_call_result.borrow().clone().unwrap().clone() + } +} + +impl HostEnv { /// Returns the name of the passed event fn extract_event_name(bytes: &[u8]) -> Result { let name = FromBytes::from_bytes(bytes).map_err(|_| EventError::CouldntExtractName)?; diff --git a/odra-core/src/lib.rs b/odra-core/src/lib.rs index 2524c807..4e1f6beb 100644 --- a/odra-core/src/lib.rs +++ b/odra-core/src/lib.rs @@ -4,6 +4,7 @@ extern crate alloc; +pub mod call_result; mod contract_context; mod contract_env; pub mod entry_point_callback; diff --git a/odra-vm/src/odra_vm_host.rs b/odra-vm/src/odra_vm_host.rs index b892a945..a96f5570 100644 --- a/odra-vm/src/odra_vm_host.rs +++ b/odra-vm/src/odra_vm_host.rs @@ -4,7 +4,7 @@ use odra_core::entry_point_callback::EntryPointsCaller; use odra_core::event::EventError; use odra_core::prelude::{collections::*, *}; use odra_core::{CallDef, ContractContext, ContractEnv, HostContext, HostEnv}; -use odra_types::{Address, Bytes, EventData, RuntimeArgs, U512, OdraError, VmError}; +use odra_types::{Address, Bytes, EventData, OdraError, RuntimeArgs, VmError, U512}; pub struct OdraVmHost { vm: Rc>, @@ -16,6 +16,10 @@ impl HostContext for OdraVmHost { self.vm.borrow().set_caller(caller) } + fn caller(&self) -> Address { + *self.vm.borrow().callstack_tip().address() + } + fn get_account(&self, index: usize) -> Address { self.vm.borrow().get_account(index) } @@ -32,8 +36,16 @@ impl HostContext for OdraVmHost { self.vm.borrow().get_event(contract_address, index) } - // TODO: has the same logic as CasperHost::call_contract, try to share this logic in HostEnv - fn call_contract(&self, address: &Address, call_def: CallDef, _use_proxy: bool) -> Result { + fn get_events_count(&self, contract_address: &Address) -> u32 { + self.vm.borrow().get_events_count(contract_address) + } + + fn call_contract( + &self, + address: &Address, + call_def: CallDef, + _use_proxy: bool + ) -> Result { let mut opt_result: Option = None; let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { opt_result = Some(self.vm.borrow().call_contract(*address, call_def)); @@ -44,7 +56,7 @@ impl HostContext for OdraVmHost { None => { let error = self.vm.borrow().error(); Err(error.unwrap_or(OdraError::VmError(VmError::Panic))) - }, + } } } @@ -79,6 +91,11 @@ impl HostContext for OdraVmHost { // For OdraVM there is no gas, so nothing to report. println!("No gas report for OdraVM"); } + + fn last_call_gas_cost(&self) -> u64 { + // For OdraVM there is no gas, so nothing to report. + 0 + } } impl OdraVmHost { diff --git a/odra-vm/src/vm/odra_vm.rs b/odra-vm/src/vm/odra_vm.rs index 97eb3cdd..b4963ad0 100644 --- a/odra-vm/src/vm/odra_vm.rs +++ b/odra-vm/src/vm/odra_vm.rs @@ -211,6 +211,10 @@ impl OdraVm { self.state.read().unwrap().get_event(address, index) } + pub fn get_events_count(&self, address: &Address) -> u32 { + self.state.read().unwrap().get_events_count(address) + } + pub fn attach_value(&self, amount: U512) { self.state.write().unwrap().attach_value(amount); } diff --git a/odra-vm/src/vm/odra_vm_state.rs b/odra-vm/src/vm/odra_vm_state.rs index 835f2263..80ff02e9 100644 --- a/odra-vm/src/vm/odra_vm_state.rs +++ b/odra-vm/src/vm/odra_vm_state.rs @@ -94,6 +94,12 @@ impl OdraVmState { Ok(event.clone()) } + pub fn get_events_count(&self, address: &Address) -> u32 { + self.events + .get(address) + .map_or(0, |events| events.len() as u32) + } + pub fn attach_value(&mut self, amount: U512) { self.callstack.attach_value(amount); } diff --git a/odra2/src/lib.rs b/odra2/src/lib.rs index 612d751c..a01af258 100644 --- a/odra2/src/lib.rs +++ b/odra2/src/lib.rs @@ -7,8 +7,8 @@ pub use odra_core::casper_event_standard::Event; pub use odra_core::event; pub use odra_core::EntryPointsCaller; pub use odra_core::{ - mapping::Mapping, module, module::ModuleWrapper, prelude, variable::Variable, CallDef, - ContractEnv, HostEnv + call_result::CallResult, mapping::Mapping, module, module::ModuleWrapper, prelude, + variable::Variable, CallDef, ContractEnv, HostEnv }; pub use odra_types as types; diff --git a/types/src/error.rs b/types/src/error.rs index 93794640..7ddc4261 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -86,16 +86,16 @@ pub enum ExecutionError { MaxUserError = 32767, /// User error too high. The code should be in range 0..32767. UserErrorTooHigh = 32768, - User(u16), + User(u16) } impl ExecutionError { pub fn code(&self) -> u16 { - unsafe { + unsafe { match self { ExecutionError::User(code) => *code, ExecutionError::UserErrorTooHigh => 32768, - _ => ExecutionError::UserErrorTooHigh.code() + *(self as *const Self as *const u16) + _ => ExecutionError::UserErrorTooHigh.code() + *(self as *const Self as *const u16) } } }