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..b1b1d0ea 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,159 @@ 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.result.unwrap(),
+ vec![].into()
+ );
+ assert_eq!(
+ call_result.events.get(&erc20.address).unwrap(),
+ &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.events.get(&pobcoin.address).unwrap(),
+ &vec![Bytes::from(
+ Transfer {
+ from: Some(erc20.address),
+ to: Some(alice),
+ amount: 50.into()
+ }
+ .to_bytes()
+ .unwrap()
+ )]
+ );
+ assert_eq!(
+ call_result.events.get(&erc20.address).unwrap(),
+ &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..7cb9b352
--- /dev/null
+++ b/odra-core/src/call_result.rs
@@ -0,0 +1,12 @@
+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>
+}
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)
}
}
}