From 886415574a97bfca4a754022a841c2594c8c6078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Mon, 29 Jul 2024 20:33:01 +0200 Subject: [PATCH 01/22] Casper 2.0 1st batch --- .gitignore | 1 + Cargo.toml | 47 +- benchmark/Cargo.toml | 11 +- core/src/address.rs | 67 +- core/src/callstack.rs | 2 +- core/src/consts.rs | 5 +- core/src/error.rs | 4 +- core/src/host.rs | 3 +- core/src/list.rs | 365 +++++----- examples/Cargo.toml | 15 +- examples/bin/livenet_tests.rs | 39 +- examples/ourcoin/Cargo.toml | 7 - examples/ourcoin/rust-toolchain | 1 - examples/ourcoin/wasm/OurToken.wasm | Bin 230012 -> 252475 bytes examples/src/features/livenet.rs | 34 + examples/src/features/native_token.rs | 2 +- justfile | 12 +- modules/Cargo.toml | 28 +- modules/src/cep18_token.rs | 2 +- odra-casper/livenet-env/Cargo.toml | 2 +- odra-casper/livenet-env/src/livenet_host.rs | 29 +- odra-casper/proxy-caller/bin/proxy_caller.rs | 2 +- .../bin/proxy_caller_with_return.rs | 5 +- odra-casper/proxy-caller/src/lib.rs | 27 +- odra-casper/rpc-client/Cargo.toml | 12 +- .../resources/proxy_caller_with_return.wasm | Bin 35022 -> 0 bytes odra-casper/rpc-client/src/casper_client.rs | 662 ++++++++---------- .../src/casper_client/configuration.rs | 50 +- .../src/casper_node_port/account.rs | 38 - .../src/casper_node_port/approval.rs | 72 -- .../src/casper_node_port/block_hash.rs | 85 --- .../src/casper_node_port/contract.rs | 43 -- .../src/casper_node_port/contract_package.rs | 53 -- .../rpc-client/src/casper_node_port/deploy.rs | 277 -------- .../src/casper_node_port/deploy_hash.rs | 97 --- .../src/casper_node_port/deploy_header.rs | 162 ----- .../rpc-client/src/casper_node_port/error.rs | 131 ---- .../rpc-client/src/casper_node_port/mod.rs | 17 - .../src/casper_node_port/query_balance.rs | 48 -- .../rpc-client/src/casper_node_port/rpcs.rs | 247 ------- .../rpc-client/src/casper_node_port/utils.rs | 47 -- odra-casper/rpc-client/src/lib.rs | 5 +- odra-casper/rpc-client/src/utils.rs | 55 ++ odra-casper/test-vm/Cargo.toml | 7 +- odra-casper/test-vm/resources/chainspec.toml | 216 ++++-- .../test-vm/resources/proxy_caller.wasm | Bin 34392 -> 42817 bytes .../resources/proxy_caller_with_return.wasm | Bin 35763 -> 44418 bytes odra-casper/test-vm/src/casper_host.rs | 24 +- odra-casper/test-vm/src/vm/casper_vm.rs | 337 +++++---- odra-casper/utils/Cargo.toml | 11 + odra-casper/utils/src/lib.rs | 8 + odra-casper/wasm-env/Cargo.toml | 4 +- odra-casper/wasm-env/src/host_functions.rs | 86 +-- odra-casper/wasm-env/src/lib.rs | 14 +- odra-macros/src/ast/wasm_parts.rs | 47 +- odra-macros/src/lib.rs | 2 +- odra-macros/src/utils/expr.rs | 7 +- odra-macros/src/utils/ty.rs | 4 + odra-schema/Cargo.toml | 2 +- odra-schema/src/ty.rs | 3 +- odra-vm/src/vm/odra_vm_state.rs | 1 + odra-vm/src/vm/utils.rs | 2 +- rust-toolchain | 2 +- 63 files changed, 1299 insertions(+), 2289 deletions(-) delete mode 100644 examples/ourcoin/rust-toolchain delete mode 100755 odra-casper/rpc-client/resources/proxy_caller_with_return.wasm delete mode 100644 odra-casper/rpc-client/src/casper_node_port/account.rs delete mode 100644 odra-casper/rpc-client/src/casper_node_port/approval.rs delete mode 100644 odra-casper/rpc-client/src/casper_node_port/block_hash.rs delete mode 100644 odra-casper/rpc-client/src/casper_node_port/contract.rs delete mode 100644 odra-casper/rpc-client/src/casper_node_port/contract_package.rs delete mode 100644 odra-casper/rpc-client/src/casper_node_port/deploy.rs delete mode 100644 odra-casper/rpc-client/src/casper_node_port/deploy_hash.rs delete mode 100644 odra-casper/rpc-client/src/casper_node_port/deploy_header.rs delete mode 100644 odra-casper/rpc-client/src/casper_node_port/error.rs delete mode 100644 odra-casper/rpc-client/src/casper_node_port/mod.rs delete mode 100644 odra-casper/rpc-client/src/casper_node_port/query_balance.rs delete mode 100644 odra-casper/rpc-client/src/casper_node_port/rpcs.rs delete mode 100644 odra-casper/rpc-client/src/casper_node_port/utils.rs create mode 100644 odra-casper/rpc-client/src/utils.rs create mode 100644 odra-casper/utils/Cargo.toml create mode 100644 odra-casper/utils/src/lib.rs diff --git a/.gitignore b/.gitignore index db82c1e0..f2370838 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Generated by Cargo # will have compiled files and executables +global_state target .* *.env diff --git a/Cargo.toml b/Cargo.toml index b7997c9d..8886139b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,37 +3,47 @@ members = [ "odra", "odra-vm", "core", + "examples", + "examples/ourcoin", + "modules", "odra-macros", "odra-casper/rpc-client", "odra-casper/livenet-env", "odra-casper/wasm-env", "odra-casper/test-vm", + "odra-casper/utils", "odra-schema", "odra-test", - "odra-build" -] + "odra-build", + "odra-casper/utils", + "odra-casper/proxy-caller" + ] exclude = [ "examples", "modules", "benchmark", "odra-casper/proxy-caller", "templates/blank", "templates/full", "templates/workspace", "tests"] resolver = "2" [workspace.package] -version = "1.3.0" +version = "1.1.0" authors = ["Jakub Płaskonka ", "Krzysztof Pobiarżyn ", "Maciej Zieliński "] license = "MIT" homepage = "https://odra.dev/docs" repository = "https://github.com/odradev/odra" [workspace.dependencies] -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" -casper-event-standard = "0.5.0" +odra-core = { path = "core", version = "1.1.0" } +odra-macros = { path = "odra-macros", version = "1.1.0" } +odra-casper-test-vm = { path = "odra-casper/test-vm", version = "1.1.0" } +odra-casper-rpc-client = { path = "odra-casper/rpc-client", version = "1.1.0" } +odra-vm = { path = "odra-vm", version = "1.1.0" } +odra-casper-wasm-env = { path = "odra-casper/wasm-env", version = "1.1.0"} +odra-schema = { path = "odra-schema", version = "1.1.0" } +casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "rustSDK-feat-2.0", default-features = false } +casper-contract-schema = { path = "../casper-contract-schema"} +casper-types = "5.0.0" +casper-execution-engine = { git = "https://github.com/casper-network/casper-node.git", branch = "rustSDK-feat-2.0" } +casper-engine-test-support = { git = "https://github.com/casper-network/casper-node.git", branch = "rustSDK-feat-2.0" } +casper-storage = { git = "https://github.com/casper-network/casper-node.git", branch = "rustSDK-feat-2.0" } +casper-event-standard = { path = "../../casper-event-standard/casper-event-standard" } +casper-client = { git = "https://github.com/casper-ecosystem/casper-client-rs.git", branch = "rustSDK-feat-2.0" } blake2 = "0.10.6" log = "0.4.20" env_logger = "0.11.1" @@ -41,4 +51,13 @@ serde = { version = "1.0.195", default-features = false } serde_json = { version = "1.0.113", default-features = false } num-traits = { version = "0.2.14", default-features = false } mockall = { version = "0.12.1" } +sha3 = "0.10" +hex = "0.4" tokio = "1.38" +base16 = "0.2" +base64 = "0.22" +serde-json-wasm = "1.0" +casper-rust-wasm-sdk = { path = "../../rustSDK" } + +[patch.crates-io] +casper-types = { version = "5.0.0", git = "https://github.com/casper-network/casper-node", branch = "rustSDK-feat-2.0" } diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 73381e67..5822ffd6 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -7,10 +7,10 @@ edition = "2021" odra = { path = "../odra" } odra-test = { path = "../odra-test", optional = true } odra-modules = { path = "../modules" } -base64 = { version = "0.22.0", default-features = false, features = ["alloc"] } +base64 = { workspace = true, default-features = false, features = ["alloc"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -serde_json = "1.0.113" +serde_json = { workspace = true } [[bin]] name = "benchmark_build_contract" @@ -33,13 +33,6 @@ name = "evaluate_benchmark" path = "bin/evaluate_benchmark.rs" test = false -[profile.release] -codegen-units = 1 -lto = true - -[profile.dev.package."*"] -opt-level = 3 - [features] default = [] benchmark = ["odra-test"] diff --git a/core/src/address.rs b/core/src/address.rs index bfd331c2..acfd43af 100644 --- a/core/src/address.rs +++ b/core/src/address.rs @@ -1,14 +1,19 @@ //! Better address representation for Casper. -use crate::AddressError::ZeroAddress; -use crate::{prelude::*, ExecutionError, OdraResult}; -use crate::{AddressError, OdraError, VmError}; + +use core::hash::Hash; + use casper_types::{ account::AccountHash, bytesrepr::{self, FromBytes, ToBytes}, - CLType, CLTyped, ContractPackageHash, Key, PublicKey + contracts::ContractPackageHash, + AddressableEntityHash, CLType, CLTyped, EntityAddr, HashAddr, Key, PackageHash, PublicKey }; use serde::{Deserialize, Serialize}; +use crate::AddressError::ZeroAddress; +use crate::{prelude::*, ExecutionError, OdraResult}; +use crate::{AddressError, OdraError, VmError}; + /// The length of the hash part of an address. const ADDRESS_HASH_LENGTH: usize = 64; /// An address has format `hash-<64-byte-hash>`. @@ -80,10 +85,43 @@ impl Address { } } + /// Returns the inner contract hash if `self` is the `Contract` variant. + pub fn as_package_hash(&self) -> Option { + self.as_contract_package_hash().map(|cph| { + PackageHash::new(cph.value()) + }) + } + /// Returns true if the address is a contract address. pub fn is_contract(&self) -> bool { self.as_contract_package_hash().is_some() } + + /// Returns the [`HashAddr`] of the address. + pub fn value(&self) -> HashAddr { + match self { + Address::Account(account_hash) => account_hash.value(), + Address::Contract(contract_package_hash) => contract_package_hash.value() + } + } + + /// Returns the [`EntityAddr`] of the address. + pub fn to_entity_addr(&self) -> EntityAddr { + match self { + Address::Account(_) => EntityAddr::Account(self.value()), + Address::Contract(_) => EntityAddr::SmartContract(self.value()) + } + } + + /// Returns a formatted string representation of the address. + pub fn to_formatted_string(&self) -> String { + match self { + Address::Account(_) => self.to_entity_addr().to_formatted_string(), + Address::Contract(package_hash) => { + PackageHash::new(package_hash.value()).to_formatted_string() + } + } + } } impl TryFrom for Address { @@ -96,6 +134,12 @@ impl TryFrom for Address { } } +impl From for Address { + fn from(package_hash: PackageHash) -> Self { + let contract_package_hash = ContractPackageHash::new(package_hash.value()); + Self::Contract(contract_package_hash) + } +} impl TryFrom for Address { type Error = AddressError; fn try_from(account_hash: AccountHash) -> Result { @@ -210,6 +254,21 @@ impl ToString for Address { } } +impl From for Address { + fn from(value: AddressableEntityHash) -> Self { + Address::Contract(ContractPackageHash::new(value.value())) + } +} + +impl From
for AddressableEntityHash { + fn from(value: Address) -> Self { + match value { + Address::Account(account) => AddressableEntityHash::new(account.value()), + Address::Contract(contract) => AddressableEntityHash::new(contract.value()) + } + } +} + impl Serialize for Address { fn serialize(&self, serializer: S) -> Result { let s = self.to_string(); diff --git a/core/src/callstack.rs b/core/src/callstack.rs index ff3ff06b..0dd84c2a 100644 --- a/core/src/callstack.rs +++ b/core/src/callstack.rs @@ -113,7 +113,7 @@ impl Callstack { #[cfg(test)] mod tests { - use casper_types::{account::AccountHash, ContractPackageHash, RuntimeArgs}; + use casper_types::{account::AccountHash, contracts::ContractPackageHash, RuntimeArgs}; use super::*; diff --git a/core/src/consts.rs b/core/src/consts.rs index 9fd3185e..90346e89 100644 --- a/core/src/consts.rs +++ b/core/src/consts.rs @@ -25,7 +25,7 @@ pub const RESULT_KEY: &str = "__result"; pub const CARGO_PURSE_ARG: &str = "cargo_purse"; /// The arg name of the contract package hash. -pub const CONTRACT_PACKAGE_HASH_ARG: &str = "contract_package_hash"; +pub const PACKAGE_HASH_ARG: &str = "package_hash"; /// The arg name of the entry point. pub const ENTRY_POINT_ARG: &str = "entry_point"; @@ -59,3 +59,6 @@ pub const CONSTRUCTOR_GROUP_NAME: &str = "constructor_group"; /// Constructor name pub const CONSTRUCTOR_NAME: &str = "init"; + +/// Number of accounts created during spinning up the test environment. +pub const ACCOUNTS_NUMBER: u8 = 20; diff --git a/core/src/error.rs b/core/src/error.rs index 205e747f..176bc778 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -251,7 +251,9 @@ pub enum EventError { /// Could not extract event name. CouldntExtractName, /// Could not extract event data. - CouldntExtractEventData + CouldntExtractEventData, + /// Contract doesn't support CES events. + ContractDoesntSupportEvents } /// Represents the result of a contract call. diff --git a/core/src/host.rs b/core/src/host.rs index a1cc3c0b..7f01dffe 100644 --- a/core/src/host.rs +++ b/core/src/host.rs @@ -558,7 +558,8 @@ mod test { use super::*; use casper_event_standard::Event; - use casper_types::{account::AccountHash, ContractPackageHash}; + use casper_types::account::AccountHash; + use casper_types::contracts::ContractPackageHash; use mockall::{mock, predicate}; use std::sync::Mutex; diff --git a/core/src/list.rs b/core/src/list.rs index 5540ed39..304125fe 100644 --- a/core/src/list.rs +++ b/core/src/list.rs @@ -178,187 +178,184 @@ impl List { } } -#[cfg(all(feature = "mock-vm", test))] -mod tests { - use super::List; - use crate::{instance::StaticInstance, test_env}; - use odra_types::{ - casper_types::bytesrepr::{FromBytes, ToBytes}, - CollectionError - }; - - #[test] - fn test_getting_items() { - // Given an empty list - let mut list = List::::default(); - assert_eq!(list.len(), 0); - - // When push a first item - list.push(0u8); - // Then a value at index 0 is available - assert_eq!(list.get(0).unwrap(), 0); - - // When push next two items - list.push(1u8); - list.push(3u8); - - // Then these values are accessible at indexes 1 and 2 - assert_eq!(list.get(1).unwrap(), 1); - assert_eq!(list.get(2).unwrap(), 3); - - // When get a value under nonexistent index - let result = list.get(100); - // Then the value is None - assert_eq!(result, None); - } - - #[test] - fn test_replace() { - // Given a list with 5 items - let mut list = List::::default(); - for i in 0..5 { - list.push(i); - } - - // When replace last item - let result = list.replace(4, 10); - - // Then the previous value is returned - assert_eq!(result, 4); - // Then the value is updated - assert_eq!(list.get(4).unwrap(), 10); - - // When replaces nonexistent value then reverts - test_env::assert_exception(CollectionError::IndexOutOfBounds, || { - list.replace(100, 99); - }); - } - - #[test] - fn test_list_len() { - // Given an empty list - let mut list = List::::default(); - - // When push 3 elements - assert_eq!(list.len(), 0); - list.push(0u8); - list.push(1u8); - list.push(3u8); - - // Then the length should be 3 - assert_eq!(list.len(), 3); - } - - #[test] - fn test_list_is_empty() { - // Given an empty list - let mut list = List::::default(); - assert!(list.is_empty()); - - // When push an element - list.push(9u8); - - // Then the list should not be empty - assert!(!list.is_empty()); - } - - #[test] - fn test_pop() { - // Given list with 2 elements. - let mut list = List::::default(); - list.push(1u8); - list.push(2u8); - - // When pop an element - let result = list.pop(); - - // Then the result is the last element - assert_eq!(result, Some(2)); - // And the length is 1 - assert_eq!(list.len(), 1); - - // When pop another element - let result = list.pop(); - - // Then the result is the last element - assert_eq!(result, Some(1)); - // And the length is 0 - assert_eq!(list.len(), 0); - - // When pop another element - let result = list.pop(); - - // Then the result is None - assert_eq!(result, None); - } - - #[test] - fn test_iter() { - // Given a list with 5 items - let mut list = List::::default(); - for i in 0..5 { - list.push(i); - } - - let mut iter = list.iter(); - - assert_eq!(iter.next(), Some(0)); - assert_eq!(iter.next(), Some(1)); - assert_eq!(iter.next(), Some(2)); - assert_eq!(iter.next(), Some(3)); - assert_eq!(iter.next(), Some(4)); - assert_eq!(iter.next(), None); - } - - #[test] - fn test_fuse_iter() { - // Given a list with 3 items - let mut list = List::::default(); - for i in 0..3 { - list.push(i); - } - - // When iterate over all the elements - let iter = list.iter(); - let mut iter = iter.fuse(); - iter.next(); - iter.next(); - iter.next(); - - // Then all consecutive iter.next() calls return None - assert_eq!(iter.next(), None); - assert_eq!(iter.next(), None); - assert_eq!(iter.next(), None); - } - - #[test] - fn test_double_ended_iter() { - // Given a list with 10 items - let mut list = List::::default(); - for i in 0..10 { - list.push(i); - } - - let mut iter = list.iter(); - - // When iterate from the start - // Then first two iterations returns the first and the second item - assert_eq!(iter.next(), Some(0)); - assert_eq!(iter.next(), Some(1)); - // When iterate from the end - // Then two iterations returns 10th and 9th items - assert_eq!(iter.next_back(), Some(9)); - assert_eq!(iter.next_back(), Some(8)); - // When iterate from the start again - // Then the first iteration returns third element - assert_eq!(iter.next(), Some(2)); - // Then five items remaining - assert_eq!(iter.count(), 5); - } - - impl Default for List { - fn default() -> Self { - StaticInstance::instance(&["list_val", "list_idx"]).0 - } - } -} +// TODO: Rewrite using mock env. +// #[cfg(test)] +// mod tests { +// use super::List; +// +// +// #[test] +// fn test_getting_items() { +// // Given an empty list +// let mut list = List::::default(); +// assert_eq!(list.len(), 0); +// +// // When push a first item +// list.push(0u8); +// // Then a value at index 0 is available +// assert_eq!(list.get(0).unwrap(), 0); +// +// // When push next two items +// list.push(1u8); +// list.push(3u8); +// +// // Then these values are accessible at indexes 1 and 2 +// assert_eq!(list.get(1).unwrap(), 1); +// assert_eq!(list.get(2).unwrap(), 3); +// +// // When get a value under nonexistent index +// let result = list.get(100); +// // Then the value is None +// assert_eq!(result, None); +// } +// +// #[test] +// fn test_replace() { +// // Given a list with 5 items +// let mut list = List::::default(); +// for i in 0..5 { +// list.push(i); +// } +// +// // When replace last item +// let result = list.replace(4, 10); +// +// // Then the previous value is returned +// assert_eq!(result, 4); +// // Then the value is updated +// assert_eq!(list.get(4).unwrap(), 10); +// +// // When replaces nonexistent value then reverts +// test_env::assert_exception(CollectionError::IndexOutOfBounds, || { +// list.replace(100, 99); +// }); +// } +// +// #[test] +// fn test_list_len() { +// // Given an empty list +// let mut list = List::::default(); +// +// // When push 3 elements +// assert_eq!(list.len(), 0); +// list.push(0u8); +// list.push(1u8); +// list.push(3u8); +// +// // Then the length should be 3 +// assert_eq!(list.len(), 3); +// } +// +// #[test] +// fn test_list_is_empty() { +// // Given an empty list +// let mut list = List::::default(); +// assert!(list.is_empty()); +// +// // When push an element +// list.push(9u8); +// +// // Then the list should not be empty +// assert!(!list.is_empty()); +// } +// +// #[test] +// fn test_pop() { +// // Given list with 2 elements. +// let mut list = List::::default(); +// list.push(1u8); +// list.push(2u8); +// +// // When pop an element +// let result = list.pop(); +// +// // Then the result is the last element +// assert_eq!(result, Some(2)); +// // And the length is 1 +// assert_eq!(list.len(), 1); +// +// // When pop another element +// let result = list.pop(); +// +// // Then the result is the last element +// assert_eq!(result, Some(1)); +// // And the length is 0 +// assert_eq!(list.len(), 0); +// +// // When pop another element +// let result = list.pop(); +// +// // Then the result is None +// assert_eq!(result, None); +// } +// +// #[test] +// fn test_iter() { +// // Given a list with 5 items +// let mut list = List::::default(); +// for i in 0..5 { +// list.push(i); +// } +// +// let mut iter = list.iter(); +// +// assert_eq!(iter.next(), Some(0)); +// assert_eq!(iter.next(), Some(1)); +// assert_eq!(iter.next(), Some(2)); +// assert_eq!(iter.next(), Some(3)); +// assert_eq!(iter.next(), Some(4)); +// assert_eq!(iter.next(), None); +// } +// +// #[test] +// fn test_fuse_iter() { +// // Given a list with 3 items +// let mut list = List::::default(); +// for i in 0..3 { +// list.push(i); +// } +// +// // When iterate over all the elements +// let iter = list.iter(); +// let mut iter = iter.fuse(); +// iter.next(); +// iter.next(); +// iter.next(); +// +// // Then all consecutive iter.next() calls return None +// assert_eq!(iter.next(), None); +// assert_eq!(iter.next(), None); +// assert_eq!(iter.next(), None); +// } +// +// #[test] +// fn test_double_ended_iter() { +// // Given a list with 10 items +// let mut list = List::::default(); +// for i in 0..10 { +// list.push(i); +// } +// +// let mut iter = list.iter(); +// +// // When iterate from the start +// // Then first two iterations returns the first and the second item +// assert_eq!(iter.next(), Some(0)); +// assert_eq!(iter.next(), Some(1)); +// // When iterate from the end +// // Then two iterations returns 10th and 9th items +// assert_eq!(iter.next_back(), Some(9)); +// assert_eq!(iter.next_back(), Some(8)); +// // When iterate from the start again +// // Then the first iteration returns third element +// assert_eq!(iter.next(), Some(2)); +// // Then five items remaining +// assert_eq!(iter.count(), 5); +// } +// +// impl Default for List { +// fn default() -> Self { +// StaticInstance::instance(&["list_val", "list_idx"]).0 +// } +// } +// } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 782a42df..3b588c35 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -4,9 +4,9 @@ version = "1.3.0" edition = "2021" [dependencies] -odra = { path = "../odra", default-features = false } -odra-modules = { path = "../modules", default-features = false } -sha3 = { version = "0.10.6", default-features = false } +odra = { path = "../odra", features = [], default-features = false } +odra-modules = { path = "../modules", features = [], default-features = false } +sha3 = { workspace = true } odra-casper-livenet-env = { path = "../odra-casper/livenet-env", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -14,7 +14,7 @@ odra-build = { path = "../odra-build" } [dev-dependencies] odra-test = { path = "../odra-test" } -hex = "0.4.3" +hex = { workspace = true } [build-dependencies] odra-build = { path = "../odra-build" } @@ -63,12 +63,5 @@ path = "bin/livenet_tests.rs" required-features = ["livenet"] test = false -[profile.release] -codegen-units = 1 -lto = true - -[profile.dev.package."*"] -opt-level = 3 - [lints.rust] missing_docs = "warn" diff --git a/examples/bin/livenet_tests.rs b/examples/bin/livenet_tests.rs index 87b7e12b..9575f59e 100644 --- a/examples/bin/livenet_tests.rs +++ b/examples/bin/livenet_tests.rs @@ -1,40 +1,49 @@ //! This example demonstrates how to deploy and interact with a contract on the Livenet environment. -use odra::casper_types::U256; +use odra::casper_types::{U256, U512}; use odra::host::{Deployer, HostEnv, HostRef, HostRefLoader}; use odra::Address; -use odra::ExecutionError; -use odra_examples::features::livenet::{ - LivenetContract, LivenetContractHostRef, LivenetContractInitArgs -}; +use odra_examples::features::livenet::{LivenetContractHostRef, LivenetContractInitArgs}; use odra_modules::access::events::OwnershipTransferred; -use odra_modules::erc20::{Erc20, Erc20HostRef, Erc20InitArgs}; +use odra_modules::erc20::{Erc20HostRef, Erc20InitArgs}; fn main() { let env = odra_casper_livenet_env::env(); let owner = env.caller(); + // Funds can be transferred + let another_account = env.get_account(1); + let another_account_balance = env.balance_of(&another_account); + env.transfer(another_account.clone(), U512::from(10_000_000_000u64)) + .unwrap(); + assert_eq!( + env.balance_of(&another_account), + another_account_balance + U512::from(10_000_000_000u64) + ); + // // Contract can be deployed env.set_gas(30_000_000_000u64); let (contract, erc20) = deploy_new(&env); - println!("Contract address: {}", contract.address().to_string()); // Contract can be loaded let (mut contract, erc20) = load(&env, *contract.address(), *erc20.address()); // Errors can be handled - env.set_gas(1_000u64); - let result = contract.try_push_on_stack(1).unwrap_err(); - assert_eq!(result, ExecutionError::OutOfGas.into()); + // env.set_gas(1u64); + // TODO: Fix setting gas for contract calls + // let result = contract.try_push_on_stack(1).unwrap_err(); + // assert_eq!(result, ExecutionError::OutOfGas.into()); + contract.push_on_stack(1); // Set gas will be used for all subsequent calls env.set_gas(1_000_000_000u64); // There are three ways contract endpoints can be called in Livenet environment: // 1. If the endpoint is mutable and does not return anything, it can be called directly: - contract.push_on_stack(1); + assert_eq!(contract.get_stack_len(), 1); // 2. If the endpoint is mutable and returns something, it can be called through the proxy: + contract.push_on_stack(1); let value = contract.pop_from_stack(); assert_eq!(value, 1); @@ -70,7 +79,7 @@ fn deploy_new(env: &HostEnv) -> (LivenetContractHostRef, Erc20HostRef) { let init_args = LivenetContractInitArgs { erc20_address: *erc20_contract.address() }; - let livenet_contract = LivenetContract::deploy(env, init_args); + let livenet_contract = LivenetContractHostRef::deploy(env, init_args); erc20_contract.transfer(livenet_contract.address(), &1000.into()); (livenet_contract, erc20_contract) } @@ -81,8 +90,8 @@ fn load( erc20_address: Address ) -> (LivenetContractHostRef, Erc20HostRef) { ( - LivenetContract::load(env, contract_address), - Erc20::load(env, erc20_address) + LivenetContractHostRef::load(env, contract_address), + Erc20HostRef::load(env, erc20_address) ) } @@ -101,5 +110,5 @@ pub fn deploy_erc20(env: &HostEnv) -> Erc20HostRef { }; env.set_gas(100_000_000_000u64); - Erc20::deploy(env, init_args) + Erc20HostRef::deploy(env, init_args) } diff --git a/examples/ourcoin/Cargo.toml b/examples/ourcoin/Cargo.toml index 1ac141ef..be156743 100644 --- a/examples/ourcoin/Cargo.toml +++ b/examples/ourcoin/Cargo.toml @@ -32,10 +32,3 @@ test = false name = "our_token_livenet" path = "bin/our_token_livenet.rs" required-features = ["livenet"] - -[profile.release] -codegen-units = 1 -lto = true - -[profile.dev.package."*"] -opt-level = 3 diff --git a/examples/ourcoin/rust-toolchain b/examples/ourcoin/rust-toolchain deleted file mode 100644 index e02da0b2..00000000 --- a/examples/ourcoin/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly-2024-04-26 \ No newline at end of file diff --git a/examples/ourcoin/wasm/OurToken.wasm b/examples/ourcoin/wasm/OurToken.wasm index f8fa1b52c7dec6297e83d68ed4b1da8a51798889..02b7a20fcbb09033e531d61ad33ce8617b3e130a 100755 GIT binary patch literal 252475 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 Lm_xPzulWA}jX_cV literal 230012 zcmeFa3$SHZdFQtu=XKBH-g9r?zWtQ6&rt)nL5F;-$XIc~SmnWSr$P#+%B~Q{l_C=ow_h`O%9&Cno>;c=fD^_e*N`dA zjN%~I`Ti;I3JbZteBuV<8rMvG=A4~4e z9^;?{0!j=!)u(W`DbLAV2%S#r{2K9)9TTh0_<#ZYHS;_EZID-go)~4?etk`n`|5 z|NUn#B$=KrhNnMvcJuVPvmZ`+&BBKs@xok%YvIlN9;BUn&fIt3>4!JZ+;c8z>*1@z z!+S2AJ+pcC^gRzguzBIkJ)5T=;*B#ucJ}m{&C?$^^Y90fmR>#(UOw|a8j5dTJbU5c zdmnrtS#A!1IyNfnV|S?bz}XL;e&4FPvqNn@OUweT`-xICKBm z_nkg-;m1xteDB`~t*>tPJQ=F$Qh20!JaXae`yD9DRVVj9c=4>w+Lmt;%I6{b$iocz z$1Xhh$U{EJ>*IS`nE*^i72%sKs_WOxb`l?+t{bxvJ)A7*?FjH1*r$P_qsTVTz!tAp zPX$c5c;>!G0!)UX_YYpUck^swNYw<>d+&Sjo^zY`-hb91{HpNw4(oH~o_ijAlyd7k8Iy2XEa zyVatF@i=MQ|I`DetyaIk3r4~l^u)N*l!4I#1y;4)%3G_{?RWBS-s&Wsyrc1VG?Gr* z>FSE#lv4dK3;%1i(}&X6y{=7*%bk7sV`+Km(v@WJSW^D?PxzLEA%pMKw&%`>OZKJY%4?eysn zoIUdpEaBm^dA3ldNtq5_`y&thER6o=(#z?MhhF`f$@GW*+P`<^eP`eQ$i)wS=)-^G z$3OBnAH9_R?ewGRPoy79A5TA?ej}{-P$+R^}%H&A$g{MyLFO&7` zOFw;bniko{G%wOSM!CI6@o)e1$se?~zV)qK&$2s4Et(i#Jef?>lEOQ=Ps{Npm2=SI zBj-a4tuiU{W2#@&&9(+!N@v52>qfV2b(?9N$wa-k^>nuP_Dr{o)4OelZmor!4%&@w zy-?j!O1A<{#)$HwHNKSXAErFo2?)TBIc*t+0fCXujD|M10YXNV;KuvCj2Z7xY+W90)N6Qvvua5=zj2zAb-#zeeA}hEY3}{L*M}OO3QR( zYG}gzl4)M1n^P9D;P1`@tZa`rdUo~L!{rSZCoRV!Lj^kC3*ZW1a4b6rp3%`>?r5oX zEL-J)CZg7|a&=>TLy@g#Lt9iXdNg74)erSsAOf)b=%q`SK6LAdg>lp!3rEKXlEL`8 zL=6@jhmrx)IY{00Y_;V43ewV}cnV6wESangOW{nDJ*h5joOrkRvOHU`T}@)}yd-3^sx%8WzC10#1(C2miP| za7vs17d$$8ZVDbT=tHz83eU^@L~;{91?W&F6ERiRVO=P3EGztO)qjRsFaJ-ZG@iD# zI1Ei#n7kdL5naK6lQQ4jm~{AkYy;l>t#760ff8-=Jee@!NjIk1|AyIX`sWVT}O4(dw!-Zb+Gy?aj#oFZqC2 z_c)&3q~918LmSKt%~84<8hoK18fcam?V?M+j`K!QbaHUxNRk*>xiQ8dh^7vyNrz5` z{PhATHF9RSh*p!q%{l;YGSc&+UyL@UBN#-P-Uf8J5*AK6gEF1;)OqJvHs+`2gk!+0 zn{Bk&Q=1*y>ugLrReM}ndp)%`RC_ceW-x>k@NxmPrduHHyH;OuN~(8I9N+u}C8bv2;g z6X7kc_v-qa-H$!a^*Yyd!PPBKJpTA}>2uRDs>IfQIvd5fT-)YB9>%E#PwoA9F`yMS zyUkcL4dnIPJ!$i#SbF@_!R;O{Y!o9+GD2cudq^w^67@8PjcE?6X##Fdv%k|c7k8TG zKqC~S!!#Fno@Rf?X^zxLe}`%A5mF2dDXw|BhC9t;ZKrwc*5HJ>{8SJf=qApriM%7t zvKa0#;#C%A8+_ZPF1AI06+PJ&0hTw4!S>5K2EVo;kG+D{N`Myg8E*5=L3LMCVt<>l z?gMG`QpBy{_Jg=VE!DVnRT3I}Zra?MSEv z*KZ?&riiIKNBJ<3WQ6I7v6Xk1iGE!SZTBz2Erp5Z4^5ZPO;@CFwkADTt8vd<624u2 z9urh?g1}$Y`q|@#^18VVr zr>Q;VtxbeX=i-H}ufaCQWowgnTNlfv^IP8NOxi^cC8SemDhQ}$w1LG8|HFWZc8u3E z5&LEI_RAdJHR_ZZRkH%#ofIoJ4hA(lhaLk&)bO@}VY57Xe$tY0VlB+oW)`!xSG+bl zATn6j$eworLcvb+*`(u60KoQ>>j5S2KNBc7%eT-!%5dA|7i-cK9zA54G>9Ii9fO{m zy5BQ!mAiSrCJl4%X1Vtfwf*d;k$Q;p!HrlsZk?nO)l$)t@ewI(DDr5CWBdXC9H)G&$9A6g?EBT_MlFFSON%l@KSjIOCpc%S`bUmO$BhsCc zg` zyP&WZ){P?2K(Aqmu`oq`IBOlx5@Ac?3+oGpse?Ho1C{0ag*3@e`pVS*ngLQ?*jVi4 zt*qVYCMl*I@NI!3QgVeq$*?3uy;%R4K4ineLlOa6@JBMe%;Gb!2yB=)hu>~>h z%R_T0Y#nhu_N`Yoo5ej$B5yI7-a>yc94#)5msfVJ?%pHLFDmU#AzdaWQcI!V!U`u) z0Hq*)Kvf>WnV^WJYpw@2Taz!gUiK2}vA!>%GK!3oo%= z|0UKNyu^CLmsoFYhkE#KDtf@cc6yMj3&!T&9;1wGqa3M)n!LLeX zpypC!Y|SQHFFchk0A1dpt^6glwK5CY>?Jg@Ylq>O|4xvUC-xd~Wp%r@0?4B55Uclv zdtt7;TkWNhD-RYx#PK`FsQAB_)voq=XDjZJbdpOl;tOM2CL)cL2*ka1T}=CQ-2(li z(k*P4MbES?gTiFGMILnvgz6@hrAJA(K+y9RS_F!Lbc?!K(=8~KZh?c z#_9OGr)V)a439`2F+tbCQO|>)XrZwLN?H?LTnvy6bN! zCes@az3SDkkxz_OV9B`5!sV+6X!c(EssWyAzdST3+u`!YLD>nH(?Qt{myzH5!) zxLXL9Hw?;txV(N)4#MSigK`)y4-U#vxI8c@7sKWLLAewz_YKN%xZFD^m&4_rLAeqx z*MM}m+&w5)!{zFr+#N1=4a&7}xiTpCgv;eYxi?&n2j#wS$>Qw~my3h)K)4(Y%7fu@ zI4G|RmxDoheYoro${WJv!k{d|C9cm&xa@)y;SvYyjp4FAC=Z28HVt1DEB{_D1%E}2>s1Rz`OyQ)&J(!k#gK44$aoeTt{d2Gsy5AR` zU%hAB@cNGR3^{_vz}M!@k7x;IuDMO7d5J&H=SdN`b|P8l$D~?P#w}JkbwDC8zybB6 z6`-aLC@_>B_RN4s_N~{-Rk!Z@TjM`g>nDJE3q)B1B~@M*mG7cI5N5?2Ne8Z z1=)p&f`^k7;%&gJM&_)Xczv8C-|3Nup1ZGD&ukLDLUUet#d_|0&s$j4)v;snl4`O0 z`U=5!bj{<1vjhe&d1DN|qKU<8Y-R8_-FxhQQrHx(KA3>JflZ)SI*9IwLeWoeBEih7 z+4%U(OQ|Z`8}{}%=QUUMCHZ?n*4CGw9L?)PZYz}&v@7G?WoowFw7psW0Tzixt>Ih8 zE-9xb$I~KG0QOEToZ9JnN$svn=WN)KM6_B>{ zBbvA{K`XTa=|>$2Uc9#0ee~KbG6nBwg9R=mTSzw2PEwmev=^5eR!4u(@5{x9a8O7T z&lQMAv+iTZZ=03{ZdY@Xcid%;-?O7|2SyeIOb6FJaNZjQ4)f7{C4f3|%+>-3Koo2n z4cUBoWjgMMj=PN+o2NYkrMAqt<(mW;LYg>B#4cKxxHi?8O*L_R>$0>wbRB6Nz`@XB-?+(OQh%xKz{K-tB!4NO?lDKnGAkK)z1y6T0i+_{fA zzntwA3Py&JZbSS-c<;-@?$`h-GT)LyZo(H#YmBZa$R0cFw`Q$#*fA0#sUl-r6Sn%i zkWf;_jD$*ZN>pZ3kYT~rW)dT)WP7-!Hc50zXycT(sDSwXO; zxm$ramx8TE2l%#(j+qAVU(481jg6q*fx|PL{pb+v}V5P%MGC#43k}6DGS=VEHe38UDq1?;c#!$ z$waJP*!ti3m-vCF({t0*0tj#pJedL^em|dTxc(m{PZHjdmY+{I#w|QS^|)D5ko8Fb z!QiOYD-s3%TpDWw*cc~u^vdVc@f-Z*&nvRn!n_kTuZLeqvB(u{%;cpH$iJ%cUrwiO z+%eP6FP{3N z70-BL-t;-Q8O82Rw-&N8DKLP{b5=Er_t8qD`%M0vkC``|=Qnscos4LCtnPrx?fT}*e8EDPGai-fEj#^L@wZ6s!kP9_D*3bi* z+T#xzP3Zwm?ezzBQ@Ew_yN>6dhb`&y=!DfXzVGVpsL#4I9O3b@Qf;Vt0J4V3WD@_8&T)oT5c2s93Asg~p0K)g4q=?2Svu z;Moy`@eC222)>TwwI)ocW8|VG)=iNgKrrW{j+n0P;2}k%F^h?s>p8P6H_cYBiW~8V zbDTZE!kAc(A3UE3ZUv&7ar$&>+jSWG7L)bNHnC)4JwTp8TC6^jaGPU9u)4Bk$B4dl z`5*T05Odd>crdtl86bf%`f;LojwshFcCtwF6(?nVq=;A}g3rd^>vl2rvl(uZL4K?P zfyhL34&)k-6#dfGPR-FgvU%&dIdy9yNE<`Z2O%MYIM8-WJOZcs4cTL2M$fW4M%wde z9-`AxCR`HIavF!|I0UB&N%qvuil3{L&WzPHx}_CyJO<646uncw+D8t(&mN-FdRVtJ zhv-^#fQPo~)|%B!8;9uFgJAbK9-?bNMBI0$6Ln~rJ3_?#MvY^Ibg&IXa-VxOG~Qyy zHAFysooFZwAHZmd7We84&t9G*b^rAH_ia(Dq;Tng#Exng>eh3pTOHJ`PWcqpC*G^XRUK&ie*UwXOT7ALm_2YZGr{17>PrYHI-WODl{=9b49xD*>OYmI!AK7Ns zbkPT)LK<7LO|2v(S;&Z z=aydjMd*NAMlvXT5ZY>cD%FT?#|Gr)3+x4afJ;hx3};<$+W@_9?&bff`r@9^+UA|c zrWQVM9C85dpoxlc>aK@f#E*PUo$^^&Z_VgtQPeo6owB0;IlO+@Duzic!EB zJx&t-f|(%}03C8zVL}u_Qta~3loc7(D-P|1 zD=oL2nfwYKR%Gy@$iZesEV{kr3I|2f04BSM(SQx=KNTcqh!?xng(T^uk07LPgkc*Q zx_dIr{arg3M{q`w;q4`8D=2KV(pTNS@z%Z!-!AvLc$%qs0QPyc)Rn;Qn)%v~rlmd8 z^PT=A#bsI|6wR{l8eSFI&FQ}mc0Elz+maVz$-R0TQ6ZVA-)=u4aWz#P(OEyX8r`EHn^_o*#h!(v)+i6;%UOhEN z(JF62Et3~lSgK>9t#WwZM)|x)-mdw$%R_o>=hR!1k;dNz?tvxnPGQ5Td(L<_t2X zK$9KVnhVGFQYesM_hmj)wGd}GoSC7f$hiWPoWBGZV>iA-ac_ENH9ms`p%)2 zV4E<74A?3hZB>=CvLg4oMQI5V%rc;NDgJ~&C3E(lARh#@{1qftD>y%hv_F=8HSnru z;2wdgiJQqI@`p0=Kwv1F5412~{>{g&A z5mPjqmlo35DW5^`5`Jp?G>L3E&UAj2O&q4Mo_#sR%;5-NOBLC;(a6}fV+OFEwj%o_ zJ0!bVHCzBDWVyuF7gyR#XZysFSh!x^;79~zZRJRN4O@&O?cGYY+i;}4-wuwn$SmPF zFT|1dZskaO=Q)xvA6%_Dl2G>AI1(XuEPswSwh+Jq&WRCG6I(3FLetpq7 zlDtET!g_XlWE-pn_y;4K$U>7iXNPVVZWzw%6rG!sA0!U!g0>Q+h*R;Iv{*W)i@T?e8U`NNg`}N-t{PB>fn6@a!CI38 zF0weR4eb*z2uxlCvpg^;hA_*6Fo;oLn8P`SISdSQt!9`*g>}I&uPfG?3{%Uu<_z;X zXP9eZm}_8n&Y!%fFOp%dIR_ieFwE;He9;V3kTdA57Muv=@FFae)5bB<>K!A_i_h9J zoN|%{H*%iH1m^84z%0fXth-spIHEXOceId|uv6EA>Sm2Gx^-8K%#`WKeetZA;@4mq zAlboU)Z~m`7iWZ(eoen4MaG~;v2>?`!gvf8{l4Y!D8@X;V+xUE_mDW0~&OtrSjl?MqG{v-GB%|>6%3cb?~b7 zalESo)E*83^G~>EaZe;)48z5&ByK`NYvA-mlc}h?0p)nv4q{wR zS-1DGv{L%$8_2GOPBpe8EDma5?AK=u3n@kbjP=VkvXS;>s?mv})B0Ck$w0!|1J6&Zd=k(7dXQ!*qPM5?^ zjmDs}ZvLX#so)A)&+4D66uvliDv52Iba2gzCbN?@MY2k@e%7GKpi#1*FjYvPNs*w3 zQ)GR>0h(?qZd!b6R!;R9%`FmSMTcTfA~cbzvB{qm&B0_7aThK1XYgkd&X_tivZI>5a{~NLbX8O{L+H40q9+Q89UJFz6y=4qbbqm8>OAN({S*aV0$|y zGpNWrjgbG6uRg=w!l@xvOpA8{S?AO{@@Ovq9)xM$8%+Cx!LRaZ-khL9VU*Zb=TBZZJ%(i&?%2Os{M3&7kUn_6N@SL1Onk z9*9~lEcp6B2q>A?OY4eSF7KEP>x|lj$y1du!N8hf=(AGfFU+SSdN$aVDAH_@;gRHx z!T77Rh@A~fQ~$)Df?@|{V*v;egHGqVrNY1yU$tuRww2`)26d;>vt$N8e{JGqzLdxu_wBB_)R zx!$pP77$gKrR|d|)F!y)v)!*8 z3e^O)6fIj4#wiC~JD8M42JPs36Kpw0AKYvqC`UfW_V{K--Y#})EG!}-K$2R19j%cq zqpgs({oJdnpL(t0WEyQ&YxUx$LYq`zWjuzcAK53Hkzwl*RQzN*&C299VdbaNDI|K^ z`RST6ej(Hq*cv9G4!CH~^n9m3>eW$E!jQ01b=Vh85R1g%2yQVvlS7!WFOZ+g{2Rl*SSmfo(<_=` z#JN!$=(^Ah`4S$gDPfl8)suU8M3t_lrX>Z;nu{pXrbc9m-lj>=VHBM)_H3tVg?e3K zq2VtfBn;Mc&(D|}MvSOk9=%VJk>Qn>{_Yh^f@024El-YyMz6uS*_STcM)3d@gYG%W z(eG?inE17KuI#ESj)sn8PvC>-{tI4ErF4%_3 zxnKqcG)%MBxPr5jCbl{|A#!$LF?16sm3aq4G^#nKU|fkFPE-jP9xGFshGs$#J#a-z z(i*s73lr!l@NnZJP>1tI^Rn}nEWtoGnvu~>2$-z5RiK*?eaO;+Tov8m;M|#J47ucs zX>6-lbQD9V>4qkt8wSTVkmv%YAHpT8Z50ofluldG4dW17fcDX_X6Od|fo?2R(&)xQ zC6!*q=q5}p&`o3sjablOq?>DD2@Se&6;eJ)>^6A!Q|`oUmrh1epF~S&I5E+asQ_`Y zD%OA;7hNe3O($mig2l{J5f!s{PAybUOsSY0f*2cud=a6pryvRuNdz%@5*c?5@s`=? z!f$g2swT` zrxD^Sk$Xt0pnlC4#MUTxPXS(>!Ga~Dno=TnLWfXHNIiy-0`adYka7yDa|)L$&OU0p zTwZBnt0d*C6BAy_7QB?3cx}^%kKB?YOZ|8)iT;HXX>!Ab$>pP}%nQle7Tkw3F{m+5yT|*zrvmlDAA} z)R5aqIZ`1&x4@qZ(Mq`@oh)JSo^b4_T`Q*w8D^-q#5%!k$8d!T5ODRjMLYdwYDaQ; zmP$l6|0}?QWN{BU+#CPvbP_v>+vU(ui>PCaB3^5a->RP;8iG4e8=BNF0s zz$W|8sxi3u3IGZZSOq1)vX`LWbVD7$hvJ*Wz`&>e5@a#{UIkciek?<%0tq-KOXjE4 zSU{7=*OKAPfx23MhrXHI-eKd^)3>i6^CgSJ8@<5M5??EM!e9I-lP>JM5&7RL_X*+G!p6U5c zf7Dx$)M7u{ERS5;9z(b})yXzGp#^rUwUd}jCgpn6Vvr1w_ZE0hR)^eJJavmhYIlFK zAWVUq__kXnEQ$ev;)oXDkW16k4Ev2~^))SqWz$mXG~TvY1^NivZuV@aDTR7{O{*WK z)vwtwy_pU{lYrMaD-4$q4fOD8b6RY3c0Y!JD42+2#Zn$n2ctT~0L_WS0!Vf1yXv-} zLM2Hg@_UmAaaji1sJ0fQB5~kUj{dq?Q{5<)6A5@jr-*yYfua!ag|HEmiER&g1e2$k z7Vv;cZ z+_g4sg`02cq=!k8Vi-j0v3Ee`yy)FmNC3UCu`zVqS73vkbZnDgt~u;Ji7KxDAdZD3);|B1EcU z>+%DcBgxsi$hYf*b<-=db&IA5Qwuhhk)qgpH8q;982hylYHei+=EkVd2h|*=5}UPX zW@uohow=LPT!x?q&O(8^nug8Fpf8CfVcd0c4VSFP&LXy2k82rnEn>CC27aLqqp4%; z3!|zYKOh+a#f2Z%SJ){9Xf$YIUdFzJ2hERMo&4Ya;z9Gj^4!O6xP#>hoT6A;Y9ncX1+1!Z_KT83DqbIJ;S<|-;|GOa!FQ}c4lfw@ zV)7x+N~SVvYy5_pCUX!S_nT_bB8na6kIWLYG;}&sy-iz3l7gT^%m2c40&`G+h4N~8z3Q?JhmvAyc%nGp_%z#{SLaF9mh z(*zEKwzAps^1ILl{0bOwwk29yd3lAvk%d^>EOm=M=BSa9<>bneQ)qFyWQ~qN6Ff+@ zdSc8TrJLv=4Yr*bh=~r;#P%(fP;5Sddl%L^S|7%oxEM6==O48dORF4%jFEtBCmQs1 z3{p&x$Y^DiEAk`Bp#U*0wamb8Wxa;QU>FL1UI9N-eY+!yD`8EPyqQFand?b*lGbM> zZ!qqew@*@|gb)89Q9!8=-LvAc8OP|Mc*o2c z)7=VL>lw@43LA!dM+;2{GTggDO1erg8B-xT@-Qr{&S!x%(O}nc^CDEGmf&uI_B)y5 z9idKAbdM(~KLmyp{FphO3V%p!^_$&2GYg>e5zW6XXC+vkOPr7(O?7j5;^LsWXuK%% zS{(G^8MP?*J>?!{adKCS>}@Sh<@aQ9OrNJfYLQ7rPLM=gSBfZUHM9aW1d{j!U~yk* zM9RFuebk5Ic;jz&QvcD)grx@><35W|9>)|Lu^iz3;1NmG6h6ySsYEB?X!?mvlKGj- zjk4SAd=<@c{WdYNghs9;c&AHPn+Qe+o0S_xOwU`)rT<@{n{(qoPuab*Zp7lkR&Q1w zx4dvAXE(wWGXna6ue}OyP$^iDapJ2hroZx?Xq*Ok!@&zwPRqTgy%MRv@ zN3szP=$+#8yNfV7-7`WW2q$-`T6XJcF50XyvPwh-l-;qlp#B3qjp{pUZw3@VVgM7 z|IUX6>ooG7GiVmiYX;g9jUr2(Ms52Ab7a~vTs_J03pZYr`T#0bNDme$u@xTC!^vnF zFEoO?S;h-{Afi&VNl3UzD$v8cjY=hk0ZB!rlGNN+REpHl{Z3RGXCV&|2^F-(lN#Nk zI$*mn0Bm{&0EwCD6-KXp2u801bBx}qW&SRs*Qgo44;-rl|6RLURIb9vElW*oeh3HM;!&J@2b7XQXf z@%P8#U!N&H6^nmurnrp7I%Hmtn( zmcFi8`r26f+GgqDSbDfwx*ki{o29RbrLSq0zB-n^x>@?FSo*4F>7iJ9s9AbrEWNQ= zI*p~%X6YoBPMW3Y$-Y8Gv-E~odPB4H`dE5>v-G-HdR?>hU@Rr5xL!AQ&%Cz-&C>m` zbbqsSUo73%EZrMR_clxS#8UEm)V;06(zRyk?pV6JS-KiaSDU4~V(G4C=}IhJX_hX> z(&c99IF@oKtsdD@EQMKiQJX8yw%B;kX+9V=9CAh#o+G>KTaG03G z+x90y$Lf((8SG~GAaqx0E>cz z2%3FXBQx)ZLz`%H)uGLR-SP~8-NH)W3$qret*u(=eRZ>noQj7wm1dzxXEXZ$+Gr-1 zbo~`vHok?o_LLbff0o&1?07Y%!0svP>rTR{i;a+KDw&e>p(mV^K>5RA5 z3ce<*wda~^nZqOWwRX+5wCXI0^|kw&Ybjzl)>^&hTK1`&(ATbOu4UvKYOP#zt>DMA zc`aXaE&E7L=xcn6KdH3qHDq^OEwg0*}mm9)v{0H zgj$1Ygpg!~y;;Ow7_o@YX@2LGdg$R*i~2+pEK@3Y{rD)BNi5I>v!$V zHovITXtvvE)}lIIWV6@!ah=BMY{8M{$8{PbYd1z_X6%a`S@SzPjb>rW4oAfLT(QM4 z0p5ho!MJWO#Hj7-JApu?FxYpLys}m0`#tA83<5?EO5ADuzabz{Dg1mRPmvKKBIGO| z2ewEsqNeJP_!QL_(@mGTTB0lI3sSxC#hU8XlHil-1@vj&6I@B(IjH5(=bjlkG|)_L zC2A(Q)jOp0kYSbjTzJ$JHDLZ0UE;woAN9>NR0y|LX21QOm4#G>SN=Qp?~u>wWr+a$ zo!SrSI)%rba1Mg6DTNYw!vKc>?ynwaYE@fC@1VT+!mR7&e6nZw<{h;=ox(aX$Qu)MVuwgCGPK zwbV|L!}UFXK+Pr5@og+G`YV&MS>u~HTjg7oixNc@1yxb9!Tt)+sx78^pxZ+W`0ofU z?GoDgy*c;kEVQ!ipk?YNFl3+@0K#*WYTJZobwePbh|98i$+B=(V^2gSD9E#A@Cz@~ zuSja}dw=mgdX8CZgZ{t#soy$u2hkUCBYKwdO9)*gpK~gEyUcc&BriAde>68`oSAa) z5K~Miq`qDM(Fsy>@yJcvYwue>ueE;owXeI0{Rr%boIk!~P|8W%;jhadU;ii<`0Mk> zS3S!ie3dKY>YG1n-h8up^XGPh`~0Gu=i$>&uNU)? zSyS$jq~i^aPAv^)Klb}O;jO{j()6*=jvB+san8?H!eH10 zxYG*JhD@F)JpkOqV&3gJ=g^5E8~lD|*nmsjLuDf-FwILCj6qBh3RqqmyZxwhg%)BD z&FiDjm(mL-61M8N8(u8Gs=J}xb?g)M_IOn09?RUUL-G>pG~TKjiLDY9#v3aaiMS$3 zo*K3|>P$+3&saZZy)h^BUlrPnAt={>DPg;pxnm`X_L~Fn<{7{hZ#$cqdZQ*o^XCda z0nH5nO}7E4@~QCv4(YE5rgUMO8^BgI%P(KLf(RL}55AJxdSxf`lVvhVBcSVFo6F*! z{>=Z9v~PCrKtb^+CSv@_kY4H=@?B__<9BmR&z{%~(QUP!swnfSattqjH$lSnD{%jG zl2$Ewa2Z-kaT!veSWo@OSS~Y$#UyL|FNKrz%e&8)mmlL3fm)tExu$<$q*OFi z>6SNan7K%>F0nA>qvy*96xPu%Pu@NUk_ef1wc!5VZ$SWSB8)9rj$n<3Kf>mq-YTX?pqWz_|iv7^Ume>YDvNV>~_=lM% zYGMoIoact$v95J=$ws4bCXt2zE{Tc`i8 z0c(Eg^fffjr{b#-7NOKc<(2$DeX9dc!_Dx^xzkhhs!DgJ)i@C-CyqtMVRq83>>bzt- z3rwbMxh5?eUx+$odwQGin8WS^!^YjvWZ1BaptmCu@uST+g_PmJTxhl4E+<@rqgBn$ zd#|)L?CvY2=GA{@U-=0x*-KJCb2fvOw1u;ENHIT)v6vuo zb$$z*=@yLVj8z^K!tng2%MAf|extA95PIh~eQt=@^qWO)8oV$K#fnXC1o^IqV+J-| zX4AH_5I%bgTIbY(JySTSjv&|#)EZAvVBQ_hZ?R(5PM@_FvS#HWncbE^NO^;Zzy@!~ zYQuC15dyO5bxL1zM{2~>dHgm@ik>MJ1h;CZ!HZPt+cY9YzQ zqI*Iv{r$vkJX85iT$v9!auQ6H0V;f%a+I`XG$gVv26p-58;cn}O3o;Q$mFaDu7Q3r zAo|uPC_Rk{h3VSq5YZo{yv4^9s5bt01h^YC=3Z{l2Z5vb;2{>h4COZnr#Y9cbDf6; z*wsMYCY0x)3br+%uH%&Hh89pZc*Y72)dgR*f}84sXRY9ku|Gm@j8iYtAJH_@#gnkAidix8qJIFa*UXwE(=muaSJyx|ymijR;Le02S?5b_gwq$GjBx4) zvrOQn6HZoMGLR$AvH<-60!bc7s1gnHjErdZKvE8&C;7J6C17P=ph6&`NyDWgJgpRY zh>|8=76C8M3IYU2gE(HZ6lo|~+gM7yi!ik0F0=0<3qeZ>uF!&341#!wG>^y@31u>r z2(fg;*Dy+NTw=t}siIP)08k;Wqnipq6FWXlLtkUD+?FY?rqN@%94 z@D09mU}_c1D5d$$AQ1XHLS9OZZ4l73p#=ONf;DxEeI@O?t54F7m6tl95j1nP!#J%S z@>Ya)J`~GoYVMJxSXC9qJNqa!g|~FJ9I{9I(^4Z^6~v2Y3{@Vac6=?R$z-XDB-uw0 z3Hx1kHqS1h5PnD7NcPVX7(i&~9izU;XU$dLD8eq$=7$t_LBrTYRq)>C?UbB|c-QOmUxoiqCFg|NE=YKuI`E57gEJ zyqO&Qi;=U!yQkL`%dus#ttgCiZFUBk7_mMLv4wfeZ{$RRUV7VTu^0>G$A~nDk8xMF zARvJBm<#` zXlhToQydiIfmy3~E|Cu&mII^I@TE;KTJJ=fj50&Tp$n z8j{KbAFj&9hjB6RL?#Y=*xVsM&+On{d_FbesVRFDwHovtw6{)!##Ll$UO%9^>y9iT ziIQfDSiiJE@2be+^|sRd&!?5T9+W@RNNt3bepjDY-d(A9dn;9LU**i$Uny?~>bZ%d z{5*z%6h~c~N>}7)WbzQ9mFd$~RpMLxU*^w1B+VL?ZNx_KR4iI7zH0Q8bbd&sfb66M?j*bhnNkJjf=1cU0N`@-egZLb2E+rjBAd1SEfvhZ)QM!a`wxvq8rILDvJlZ~fH~M2Ev)9YJ(B{Quwx;?W`t4$^$x9DUA)h4C@0zm3rGL@yew7CwGoE&;*b92>rx_Zt1v*UQe>{!$-h5n z$Jk8{B*_2F%T?+ExyZQ3Pv)FeOx7BU_ntT-2f6g0Qc&C=MKC3HiMYOW;APi)8ONsU zvg^EzL(_HHK`&!Jq%J$)WgM8U%l3O2$EE8sJ{h5YIV@e5+2;#2r$*Txe~*LG^?OPw zPF;>k*Jb`8*OEihb=j)F#}VnejPFaTE(fIRGW*UfF{jP4Wq*&u(e-;{FGKUJ%j^p- zDdAd;vPFMyv00`s3(@bWS*9$Kl(C0cw`YoZN;=U-nWZR9Nut##v;D$U-}J2O+WA|p zd85pd7?O(fLZi%_FnA@JWfoGOmh24F?OC!F0yUducEp4z!e*I$nF7@JvNE;rZ-85# z3{;sy)F$N3Y#}#&$ga!^ZZKZJ&mz5*Z_z{G*cH-#lKuH(90M9WlXXn#2nq+P#!=HW z%GU(vU~=lof1Lb>s$_@1RSw(Alp&fRw>BZCg}riV+B)^zf0I92Bp>-49&@&y&zE0I z9w5Ro-7GKv1cY^S!iSq+A4DF)K0foSJNIrXhy{oR}Yy7mE?R9n_Z z2E+Uuo&F!n#uNrYb5r7wDR1;j680V2rxn_1m*j?>YXhTcW~>6J&r}u}ooF)-zNnqn zOVZwA{f+W9>_Crylu^qGehB4y@PD?N5u=vTM}R-b@JUOBQ-aSpgpst)D~!~lbFD#!ky1JmJvj9psO z()<3PE?2Rs_}NSLI@x*I_%yy_q6*R5$+tRORzRUwYGEKMkspZHe-Sm2wZ@<6WXwW> zT^7}QBq>O$YfUK;VX)k46X>JO#vqO#tvu^=ktStH>xxLv0A>jTz^DKLO^bMN zAQ_?_>LD)Rslh_tGDOL{2yEFV0yggn*tYDJGZjhMml}XMcLy<@aeZf-r1&v#(q0&x z3PJEJS7vMOItDP8y5QrG$j|K)G0J$T(-77>Pu2V2=lJGx$UHUwE8L-FCMqW~KfP(| z0+6T78Ca3YWgEiRwPM;^B?w`H!69^I$HCppT0Lw~ivUnMsPfu&pjpKuxMW6i4)S_$q+BUnpn?>tLl2PS?!i;(OA-QzGi*mT> zFXwYF=X#mvD}3nW(sRlA3yel2b1U2C@;~dl3OL`-6>xmzcGWXhhMg7IfK9}JplOh) z_y=rgcZ;+1%7^q5#uJ8S2Rj&wiet};G_HCNeM?JV{1PnsDWrG-Q1ZMp_C zyT}SlVKolBAcr<5KN!vx;LJaK=m8(uc{Evh0dIGB zSGliipH@I)2w5wR?Qm1lvM0J~S67|tsvGOEELD|W^?0GW>Q`5TPzATY4tGLz%m%Dw z(%QYEGbaj8`ZTJufg^I6O&f5L$EMY)6)YC>EE1F~CfkvW?PQBZQ$! zx-mBGP=`^>^V zI47gz@;!hnlRaRS^^RwU^b4_2fCKavm7@S0%Wl#Y$Z;Zh3s?Q)oQ+rIA(fBXujV#> zACOaFn2y~mHWSaRCZeZ!3(8Mv+_*PzSU4nbNf*rPMA9x$I5}NK9kZv4Wbn^ggL#$0 z@3l0_xvmugH~As$5x8vX1HT+(2%43t1tDQ1iBM4jz)v(mxRv9*;Xcqm$0937Jbd1T z7+1(l2Gu8E0R4j|RkvUGK`0(-vzeh2fGK9ljU_4{axN%vrJ^T%&CoOCcb~70w%n|) z*HEz3hCUrZh?(_9`u%WLkZ`a6_)|asbnB;3*0(<=F&Ml9D2JP8)4%B1@p=CxkE-y=%Iz z6c!mN>KuJsoPA_{cAf7IHsC$h?X7I(gQ$Fz9d#ZVru&1Hsp&fiO6k4~zoq|5o+E{;C{d(%c7(oFwbeoEE=nkSv4XDGQo;6Hp?S| zDH;=AKYWU0nT;K5m;cFhYMu$=1m2+-@Y$U!*iVS<&j2NICdT$d-WFhv&ZHRA@YUcD z>~8_*1p8C$IU4MA0F5hRL~#^#vld`ZsbVScnq+YnyfI#64#qK+z8T4?iK*)N-;V>Q zdCzjMg3GBeO`n&9YqI5}4_8fTnqUU)p6TQlhG47w>5l`a1LJ#PHNT|GH7;G#Xd9|} zFI_(2X5fi^FaWjf?T%3nO@y+f@9>p7Fup0$CP~bu&*ts6t<3y zG=)lm=xAii&M_?A2_Ebu)GIlheC87;mD3tzJ)C^nuUjSCxnE~Jg_O@~UAg}nj10t^ z)XT;_y90=3$6g3PUaPIcYn}nBhest^yD$~@IxmpRD{kO09?U}aaE+Ac33=l^q2n}q z3h~*BTB!HX=K!JM;d1qU;C;v&j7|GQ@;jO~_fLmkp$u>8 zdsF{JMPeH8hu^`OtFLf4>wp6rgOZen++a)?m?8wuGaQ%dE0P59k!or{wxB0V68B0z zogPczbYAmB@@|L!UEYFM9v@XlcS-t6+|kZ@_U^h9^H>;zJSM(bbjM3tv?DmZSZ?4K z`sGsvaA)3^7^BNag3DtM+@z981R|HNvh22P&`eez4_Sm0fZqTvaw0c zl_qhcglLaAeg7;6a$TS0aOM-gQ`9Q|E8uEjw$-r6x^7?6@ZsQzw!G{u#TFEne~%HJ zNOojHe9+Nny&W4;bp6XfXRBAGGj+V4{Iaua?MI5#exX5jpN(X&g0F5gOa(BjR>7d~_ZE-*-9JDq&?#krm}>&5byDWW?DI{Jf?}MESK) znM-CRF5{2YbM6f;`6{Jfo z`8q-J{hE^<()DoiKSUBl(tT2_9%bqY@p|OL`9`07jeqFR6j^NpN^$}dkc+ngi7LSh%wTMjY89qI$ zod)2JlLhcq->YW^l;c-f(U&A)gxqPJIsG8V+$u0)nk!O_fHpR`+<@(3iEg!*C426M zN`+E>az#|Bf+%>`R!?v-`aNMXVDdFMt*Bsk#rmKTl+SUpXmD<$ZyT2 zJ41eJ^W?YY1XYvYioy6=$ZzE(lAkNwH4)k_3W($B0lhhzVb^g39MBJ*RbZ<6!ybo zuxE8pEEmMIf{}LmkyAR5?R2lyAc^qIco_P~uO5TZ#$tTP5Mt1pWBwL|0RKVu05>?V-D9v^x+C?O2)(-iJ^2>BN>4uRvq%mbG>urLSGkMU*6pwUZe2wX+ta94$xZTx?bnT&017 z_6v=0vaBV<#KIso1D1e5Y{y_SvwrXK<-?YvTr3?U*6d@6PZ}tePtJC?UglOK^xhmjekhPmtI~CI74J!c*b|J~2EdfiA&h)tCVfiZdoDZ|kD54C5ToEDN~_3IihQ_y}Q)d(Rb| zt%@vhZ!xR{>0WEk1StWO$eOSkyaS$#A!<#FJyK-$n6ihTqx=&#VT_QnFPqNI7LJkC zSGW@=o~+8FWJ46QMgEfCS$uUDLmD%X1z73$%%#dukq>*1+zy$;fl!sD4nWd!pT?gGD5*3urG7@ zPb#4sr3_X;&;rDo8XCejD!KN15Y47Dz9x;fGBs(Rpn#v51-6<0R4QN+s z=k81&nLQ)bR6gdrlimS#Zr9y{cgLaN*3R8Y&p~7={>x^k_=RaN0*z4JU#utHzCWC3iDda)&6AFP zC$7MUUv~7QKQ0EMhH6iGBx3ClxPsmT({@nMB_*YZI#q!~Gs!z?VopIG zVf2mF%`OY8f^!TWK30qk=&a-ia6a+#=%ag9oSJ*|{YulSxOJE~Puz&PY?hz?u`q=lhwW2v?-Q#Q|6YIy$TS|*rsvE9-n z@Rif17O z7$b#4zqJ=Ba+r2W(gD*j-8^#I+7e8$i$T_kkWmtQ4HYGo#jMf?5v@Q&a1+}?w@iNu zt*!1G2M3vl_I;TkhvDu0h*L!3kK<=}K^aA(Z2J4QH5!Wg-rwXore zO^C(JZhplBfA`ZfJn$>JH7*GcJd*tLPg`VBz!lT8N(No1$r8{CWAv|)vUGrDg0&bG zJ`{qr@O4Badomrr!C#LHq|F&>;>aN$7x#e-*Wna~f&FSNPM6m?P%-?)QhAW9B&M8Y%oq*xUmAI z^u5sRjSa>LP>IJ}(22F(Vv^hW#F(9wL+4>q+2LRGW_~f7m7P&s*5-~}@~7`3>rSiu z92P&$vGOv%D-KL>1s~FGMP{3+zfcx;j1JLzaqH+1bL~hO9hV*x1VmrOa!ZLE;goK~ zcC2u-90R+AKNicO95osinHIoDLOBFHt;SuZZ;Jt7jh|AyloQfDgy>Az zC%JgiA5xWu2`Yql zXrAH~NA(E)67Ro9_xtRgQ8Tu~3Hc9;Er=#AVtUFROl;uH4j^;fftl=OVzsVo__?=% zU1BCP&gi#~_Ar&mOtgG3i;9`hmhSx&GJ~{qJ=-u7IgX}Sqk9d}c3>sJ?voRKj@|96 zM#u$i)v+77M7g!_u_ zsmC$Dp8TKx;Fq5_Y;yjRj~^6f)E;D|E)w49%#MjS@h9;br=jsmlS>_7h*sM*F>kUn zz8y8_gz_jonU1b2SIncc#h!ScW9YW}*U?B^_PL;h>t`0--o9Rtj0VN?oadce_3K!5 zc!UfcIEGf1M6~h;pjkp)MP$*2(t(!bPBT2N?kBqco`Aw}iE6($DybB@yCTo51>*Hg zMz+Kf79Pd!<)eYD|bwA#>QIdPg$gS z`MiBdhfFsb$ZXTQq%COMrV}iMPH3Y&`}^Nztr%TTG`)XNi<4c@hXq{-D-1J|BBglf zvG11oo1itMA0Dfo90tRYUVm>2e)v~ zb8ZV}K4syDC2-mCs>U6P%^CTX!MA}Vl z$Z+^qKfN60cyszQZB#u{!EklENGedz{jP+zIw zIo6`>4|1!qw47>DD)=S(gi$W4Yht~MMNb7!s7kSTu2?!SU1a`jS1A?zVn_vVn^$ii z?HbUdrE$<k{^*YIH&ThjirV`L>R7>K6mM+>(wQYye6No>kc< zqy)_9gqohFq&+X;0Jpr=$f~^E%eJgX(M?Cv??~ia9t{}^wch?x`izi$z!q5r#lTncXsOz-Xb0jw1s)K}(>kEq5WxNLsR|9&ZUz zM!`}yEgF!G$|^CbDcqy-rr0iIW?W!Q(@ICWWaz8>aBA=KVt!;CN6k(Y7NIX^=}nZ^ z4067WP&9q~0fVR~*1^DG^Y?A&RZmAMDkk-Y6|v6vuyio8@12DFgv{;ph`+{EB@LOb z<{a?ZB_vao({HAX>1xh1WlXtM(}vewSJ`@;wj3VJPPwV=lDS@yaWpIz4ov$t7f&qi z$lo^@EnO1{@D{0B1OVmh+?E8yIU6J(0C^tE`T@gjkaJA}rn3MS^LjpKUQa$i;Y>jm zlO9R4aS%2iurDEVR}UH>qy}DHC6v;G1{`^S8Kr#=J*XIs^|8)&aClHbbSp=CakSY7 zX`D8FMM&uQoUo5|y23ggB`5|bm;CWDQkDuFC-AY=Olt{vpB4P;d8 zCSDhb3TQ6ZdBG!K!pya;B^*=86#T>9eyBUoy zpaxe3H>u#M=!+CYIR9znkEySuwX<2k=f>9O&l|$ivg!euqX+&zAFBiUFPo zp%;Tf`!F5EiL+ZQ8sE}zgI?lEbhSDdogwG9OZ94)h#4^#2;DS%J zi4a1PN>h+#C2?rWt#FW#^Lwc@l~#QcRaX8&Y^2Kh?d`C$AKHmIztxm=Q9p_~ziDjl zk%puxE7vTes;iLkYh(+HJkv>I&-o3)Ixf~ZzemdXy#xVpGz3&OaZ1{IJMK&xpe^F` zY16?Fr@d`-u)gbH3yi21r{s(AjmV&b^~$H`bTGteZ;Lq9Stlc^HGnL!O2A(w2gEPO z73P7ywIGFI?xFumwCZD&m4{s)x2%M!hS`v)PUXw=77~1;5VpvhTS?S@kf=Bh-Zfgl z3uqalJ;8Us=(84;L|p>`B~f`bN>ruAmPGA{NRX&tP%(-U)p#Q*%!h#uL`guB?{UTP z#L<~uo#;Dqf0BL2Z!1K;Pr~n-RjMM)|Dd9IMq43~ve%yQr?0dc4t z<}1ST73=`$jnP+nK7gpxe47;l>KXAkelkr%PP9%_uKP;!J(3nlns5F^8zY!zSmiq& zZV;b`VGw4NI;~nzHQ1OyZ6;Nvlyiq zzpX0MTRKNbM>D(k&Wy58HLbj4rGCkK78D1w#CFFAFTnJt=J>7Iz6-$VTE*GTYvpos zHo3Qp4fKMUlhjZ|s8y&c3_XeXxeR#VnR{V1mKW(ISQgu24wz*_4nmd0DCAX5R>3hK7X zucD2>lj_vlQLF1W2pHuI`mK|A+dJrSqz0;p+!Z)%;ARQLixJesbt%DjS4wbDv!u=h zHER^AuU4M{SgTa8y_Kr8uUHE2@e!(K^LM*mCN?2Ws#ruf!e*C#r1&@2sF=7pD@Yf$ zMNo`j@|r5f`p{h$XhHD4YTw<0-Ga>yD`?p4RtZB*Hn`jDk{8Vk-Pr7W8A;1~S?fS5 z?uPB|hBNGoVz>@iVpu=`E~_u=cd=NxjqewX0<%N@;~?RE_yL)%)slFh2^?Af5u^b; zi)Lk1oI>wPU+XKZTR6!R)7K=Es4H@;^fgH(%4&U0l8J&^Uz2pApw`zUp(v>IHKr@( z>-o4jX`d+nolO+$UK3Rw5LTti$DLRV*$`2F*&{yv2LjxZh5Km_%e?k1++Jb(EZjw^ z&;3cg1~yF}0pI$IDm)xXzA1jlC1VqGoTIx3tdCPOqv9v9Z#kxr$P%)bskd zQsq-oF0#rFB4rm%hepUJMw{VApKq@0*4l6*Lph8)ypzxn3V{z_+ea1o*v`5IgJE%7 zF5LL#B1|8zAYfclYV^UHUa>Eh!vxF6fBH%yy=L%w@k;0;DfeiP5Y3A~j?MDxf7hQY zsS2^a>S=s)ximOVibB*jTwx~95~*J2abWOK?u_({%Bq=csL~{(G*dnrT#6fmv3#R+ z)3QV^KbRa z-u@>tf7^UDoL#{W?BTpiHU9=^=DJ@h*qfAUWsnWKMpz<(ET7o=J?RPND|Chb@53=OC?)$o5 zznOXS`_1g^4`4y7-^(hRm9WM-ffo=+Rc&uUs5B6ezSFP+IJcA3+t3!*QpE%i12z=;{Ybunp9&izf6~1t_8TAIHmaO3MR{r9b!3dI3E>bB1Ce+&h#WoQK18L}Nx-NX0jn)aI(ssE4#%&9 zk$WEpb>IW!757rA04ftaNe&rtii$8UAS6*}dl&li(A5tVeS#c%cDe5dd=Um;oPa^) ze?rMm!d%Ua+nc83yreUFQ1d|V>e165?!D)m^d8rGk6xnKKKb4&N|toHzLNAg4d&Q7 zSTx_R_>2EOk{=a%9ZBnN+NHbYt&7^Fy``>;+Vwx58e5eGe;^;F3pVa3KKNleN|m>Y z6Wmq&O1dRkND7bpL{}0G6u;1wME%9jszgcop7DjUGJW_gPKo5pwMW+};t*YK+>k*o z7VFwu&Zx_+VKE$(#d`DkC$FGf`0SFG-oxdNFN-8MiiX2U6-rCDWW_HUv;f`HskrB0 zGj8co+;YtjvAE_bkz6dBiLAshl0p)RK|6!npyM`H!)H%v5H%xt{F&g#D8;g=>tS-9 zM}e41sSTbJ<6sHYStu}Z%H>XHne0%eRHS1TYMx-N^^?%K{4xyOMWK@w^pf|)^?&0- zKw7oI>3?0<&4kcHU7Y&~@crrG9YR69a#!&wU1YaU{6Z*m^T69_9yAAKQE_zd)nO@Ei%_EEpI~`B@Zgrw~B%2kS z79zwopt0ojR#)vvvyCj!Y{OBUf`H==-RrU+m4U|9re7zBu8>s5>few;K|c4irvp_O z+M~!dD4mh|0{V=S< zR#>3--=Zs|cFbMT0{^m;I-RDgBzcP@OI1S#xX8X#HKd2D^L~=bF~h6*^XBV3^9~X+ z1Kw5KM;%b*1xQQF79r6Y^rgHq!B>}h!hVxng?hkJ{&Hlyc5DN=8%z1C+UUi~F{V`t z))Rc15BUw&PfP?9TTxB1YM520ihV>VKO=z+^;kPxcts;s%%Y`H4K3+#gS^JoD~elm zQ|hG)U;}O{w*qN7!lyavjt=#==?7szRgk*?Q>$`jCf@d!{$Ms%aL5q!b#K$g0R~5HY3U{gmuG{ypp|aZhRy~x zgG*veq8|pQh{e3DLja1wrDa`Ltr=XJBmj#J$24hhW3e|t8BCd^^)#s{LPMLCv+e|~ zaiD20h)XG2M>*cAyL1@W3<`U%>*rQRtx)L>4N2#d^%0cumoU$*swgoPpeKKu*3v|` zv83Th93(wSVs&1ImHKbH$gwS8&$JSk8_-REK`6Wm9Twolld5T6C%s&00(ld~vpAun zjS9v%*e~icMX5mtX)oVVEJ41TP;1cbVXz>sUI*`D*{aq0pV_|ICc<$*OtVhi5uP}G zJ7>tCnFoYRkPBFPxybDMu9~?U_U@F9S4iU5mU|C4zGmibG;_B)bGJ5gw?1>X(Yssi zH@4M#ptF4X&&D%%J2Q8?Gk1q(?)G|jqke@)dJjs3Z2#G@nY){2?rxsBJ3e!FOYd%_ zU*VtYJcG+{ z-RnvZcb7-H%cI@pvF`Gw?(*jD@_2W7OLzIY?()xdm*1vKTizJ81tar~rUJFLg4CKt zPmo&m=maV6OUTJ4stbKT;C}rVm;3qe--Tw+L1n`e#rQ$Ve^wWhrSXGv%-9j?tVbj) z(MtC&j`xrlp5tlNa3D(oKdH2JhYKzV``w!ktP)ppB@PzuQiOp^k;J_vcGQc4=lv8Y zOVT%qC<{DC&)&m5cI-zuNu~>wr3R#F4wUVxwHGA?%5p9iMj@U>|0VI-P+6JHSXW@+ z&@dz)mZfXhj&PiXr{n<cKIS( zCxcC)1pUD#>Fxl3Wx?=Z!G2|TMyb|dQ7)2ncMU}FSu|7J2u-faN;XiA?bU2D1FS+HRxkYT z(Kl{dAE9XDjODS`Mf-)f2&3c3DvcJ7IJYpi0^Qk&s-jdwt@tjwRdc?3^It@?M!f=Fpjl3H6JmK)+v8hZeXKddewjZO_tLLQ5EWCL)fW zVW{OTdbXaH6K%!)o?+63aJ3BDXG*_IF2NoO-^wH0qbG7F^`=qzB+X#lp|uEzU9QzZ zuN)FLA#C2e1b2ir3LMj+YP{Wpd!KqA{3@JkdZ*@aM%_9QC1mBx!mwe={qUaY1-Bk~aZfkaGp7 zIVzxBY_gyaYX0Pz;?26^tGkNx(csNYds3eyo!$`npgbR+TwdvkB`mef$mM)ih~{*5 zM!NZ!huD(L<~E*+mCsX;#SDkp2w-hqAHNOgGxbo#ET4q?Nzr1at8f(ZYfo&>$a6aH zfJyj&yNqk$N^8T5R$Rjz^8dv%#eW#cfBRj<+qr^S-7mGIdC@*K<*Q<`3nsliF#mp; zVDSvK33!-$>>eX0vq!I~!TFTT9@8dazu;$cs8O+CR5X6s~@)Srol= zX_ao57Q`$<6zwdEh1rhWXJH3s5i;w~q8S(SuUWK8d4J#s=$V*TFg~irFn)Vs z>r8Du$|4LP#+r5q{@Rd05-5d}YL{VMO5Sbm^-!h?v6}doIZ7q^fd0cuI_Nmc)uoHy zmUKzpB&wM8CLylT#e@|rnnbQn72hGhQ6)rhO7UzErD&Xr!b70jP=`!Iq0K(B8{B}$nOu4-u3qc?5 zgk3BJ`7hbJYrP`pm7z!1{0UwrqO;uK)k7k3r6a>YRRji37pi%2Y7s7QtU?8CyT zw`1Wl{Z7R6Yy1M%TWpJv-lFB@m1O0LDGsM?Ye@ud$AUITH=wK%Opui!4qYN4=<;PF@;@>Uq3ht2o-2CHKlVko@%M_FN+5*Hh-T; zwz@j56_SsZtSFJG56@tQADxNzG8PW3RqDVN=;qmXT|{(vT?SiGBR|T|%XZv8@=bIj zbpoOHDjWT85AF8!k6FKQzDruk^>zfy5Lt*LN&^;a8+1-N7lFcqzWC)W*yNy}d<0WI zLBa}hB(_F6fLymk6@&(Cx1lcKSIkV+e8Eq2nkI|3RjHGs=9mAM&)swnn+He4oUyQB z-OEyNmhrKPLIKQaInGRFYDTNJc1hs07QHMTv>eASDJxFJvI*sheWzw9sfG$HNQ>UC zS}%GjZI|QfjJKz3)gyv;RwnC}g3Y&dD&czLXfQ&%$Rf^vYYc6j^#TK(Ixh?fQ0|p! zY<3W9^#)1H)ln2{wmEczaDVk|Lw2{_8JmLkNHB;sieu&O8mPS94Hdbo)^;~aH3(L@ z*xiT=cM@O1oZ_gRbz;cYQw*8X@2&@DneGY=b{BSgr`~nsaRdPLmd(6x*wZxy0MJdr z9^0ao6B%^wvoF10?wyKD^H6ba-Y_e*v5Bo=pLGp7@z|3)+fUgLOPGw*|P}5T) zrz65z+}mYa>I!ElXjQ%)VfZ!cVb)B_e8P%PcNjvHF5+C_4mB@zmoIjgq{hrIyikhV z`gh}*yHUQ=D=)~+8N2K5I;u`x5+ve@_6A5zmNM-pz$2cZFK^5|0V?qX5wn}0D1_l@n7|b&t*8Spluh(3 zsv~M(*bqm=q_m&tp1Qiqxu?~x8{E^sw!^uJJ7XpLdmh!}k@l#V>Gq50^lda(49A^T zc^lG|k%*3|yp8K(aes2~c7vQgY8ULyF>FOB6HmD4uQT zQ^4#_DbbpZuRg(C9*(STU^5f>Mp(hH`C~W#2fd-H$DSd5)@xto9;(WF9Z>l-dT>8z zpN9Ip!AgSAcBn!ex1~yYZK-?_SXJ(}^#1H<0qfI35MJT$oh+BeZW zXsF#Bqw%}M5^R#XeY%S@Gi1=Pr#r7N4kH6??oe zIWs_dG-i?;ET_fMUX zv|;;P@dKMO)Co@w*j(^=Zy?ThU!1wq%ln#z{1_(NyL)-&?(4q0zy==0R6x`Bw_&b9 z@p+HuhB7i+>$7(9s#EPj$9Xg=Pt5}P#spzKEv;Sd}|q#ypq(or~tB3OyAXi5dsK|ghQ&vDd$ew(4M zP*ZgCNtCEJvy($12DFa7j_%^(IVO&Yn@uKC)^gy1JwmFMGLj2=?ZR503;c`sN_*aj z_)QdVYVqnvKdkqB^mo-fF4Kb)p-{4!MEcl5ZqUf}v;KH#du1jM^-3BKm{0e$p~eBD zbd_#qZO#O7gK?sy8^ff+D1#~-CmL3Wa(v9r+x4GZTIy6~){Y9L1i-Y2VI(tLbxgfb zpl}s|!h`06O!S}^DLhbYoH(IBZ5d${!z5hX^4utfCS2UI!5riMDAPb3jpv1Gk<$Z9 znfMGjo!tWd95I+%pE_0$G3^KLjk<-Dc0@vc;Qr_VP`3R`4-|9rDDsAHf!OIZeAk44 z77~bv+t+nZJzc%AK6S}`O{3}d9o0ThIc^y~Sm?$7o6Bv9Teg4e2bZ3H z7_^_)8+R2C@*2eU4t_pth5`t1z%cd?mq61@6mLo9KOZUe=vn^ib-Q95Dgm5Qs7Dzd zqEwx#AxrB_b)jmw8c7MPUjPGZ2a5Sb-WxpZ3Z5HZFsa137e!zVAAN)vC)VmLX<<%G zVV<`4YuZLL)5dlt^)d*RS!?G^h2ls&XVPYhZIK`NXpwNMnJY14Bzp9eG1Ho|)tEBo z_Y?~Y7J>usQ$Pb5X{rr=ZCo&e3d)W*On%X;M3QQO>hGw|Qwdli=$;ek$?;9AgXS{b z{nF(tfj|u={3lgfpBX$FAG}+)Jk?zC!(4Jf6K4jG)hnOiT66kWH;LNF>R*;|Xt1Ug zhM`%VXmCJSTeteu*Oz$!-NXiRWTKLY%akTXw(kaGhFZUXhfT zD`H~=0hQj9Bgp7Q#j9EHv<0k)2hIP8g~mY6BSLSHNs_Hm8mqKCZOsz>>SPTgv1aIb zV>pJfJVlzzzjmhh!~sT>w%UT65ap?YeTEAllG5|xHy1=?8Z<=sNSn43M0hp}5%v#A zv$WrMpa7!3>*;%a@zeaWG`&gONikyFg??7q}1RzjmiN)(?Pp4K zyVMSW0}0x@%-saT4of4A99m2j_`WSI^V~vzfdBc zSgaMAZDIj?EwTPev%Z5BNYp$WAEgGCO7gpTsp6Q9v^i{KzD}DTDHD%3)K!|^h$W5R zYrY2KYY<58|HmJ{;uNE?rU}YaXWhQook95kB?Otk2hI83>Y5{7BT|Rj_^+>X57J3c zxj)0`*MGV?tDDP(UfC&tO#bQ<^b2Fi;H>`1`o9kg@Fu>>yHu*?bAhBt@k+|GbaU-N z?2HsqjEHb?t6fGhg2KhEcBw5VgIn*Y?gKYNH}{L^<~CvTSB;>XV~wyG$e*Jp#sjpG ztSb9~d!sLqgjuKd1NTP{(08()IwbE)LHj_7SS>Qq$}_h1w?MscgJ8%955WB;k*0+D(#J^616NS@~eOETra zs3H<9Q~q>0+i()2$MhyfFD({R?A_uksjl|sWM@z`zK-7#FUP-E< z&8DKz$@Ov|i^mvmb$!l4JlSxDnsWT%j%ooDajS(5C$@7)q0)AkGzJvtp0GktMkq@J zwTY3jWhYn{atiA_9L~#pDM|Fp=^1;0##APLM!)lOM%N287Ji`0y~*^E*#vrh9z#|{ zP?3q_BhG^*u7Wb;-_sHRAHpIotc?rO`14G_!xaISLi6bHY{-e#L}Wb5M~ex}xdscz zP-UzFL_Oo1kW@e_On%TUN$Ix+OC|a!_HB89W#(3R;dlAUzF@hOF@BsWzf-{tRYSzr zKEE&oGoJsIYWiXHyixG3-bAr;WI|YIymv;+)mzh1aHZ`Ps6pfRXt*g1{|CZm@&BL_ z1EAuzve2@!OMh`&IWS<_^cT3XF7j4r(B(wja$x0|7;FEo|NML&C@C5(d@C{eE{Y8Fy+unI^mv)UyR{9G{^DbB_cE0TH;^SmHcG-NF0 zob77TM*2(Peo#0}D*%^vyXn9K2ty%Bc_~Z{;!;T+?W*mkie~E@*vS7Ll@7!_^V)a| z>si2`!sSn#tDl%S-dfwtum?X>FFpK;cRM2^8b8o73-s zX$hu5nTV7Q(*|dom!7y%G+JsPUO>0@mDvXntDXB8#hi`1(q{7kF-$gZszTWv_NRo2 zNZSI;2UrhFZ?S$Nc$Rq^KxKBr`}Q27?!a7x1@auG(BK?_OL2hNHHZbFxk(Z1s`d6q zN~H}d5;u>4H=LBa%t^$cyBtAu>ek|YXZvlsShnr6*}A~ExA-Al>lKL>(+Qn%0QiJ;A1aL*((exNBD%d zrc&c)qDGLY?V5_HQH|spRYcF!MCulAP<^4PmSz${j~ll4VHS+OVKSv*eeju*fe?}d z@77nCQ^cOXgZGg)+H*GkUcJ13f$+`ff7aLh3T;ct&lp0Ttgr<1viYB_p#N%^1>7C= zl`cg!t)>wpwe%CjTE!&F8C$&H>M?ZqBx?(IeUS7wzl;7>HdoxLT0)qkm|A~}8c^V( zP87Io+l(jfR03|+wBm`o6)%k{C!Nir%Er{o={6Zszn)%BM%0(nE#XkY-IvmBGM>Je zZt?Z&=@&nAR{g=zBe4#F&+pcblK&0B_BDbJ&U;vf7Qa&1v;SQLnXg5O>DoW=tq{%i z_~tG&+AKsJs9{Qpnw&W#@2t0RILD1z^qJs6d1;hLynk{n?+9TcthE-$J>H;qgi{X> zct`Bq0)z+zX!_jah$O6F63vwCF%*oV8*w+Pi)xsF@-D7VaY+}6HjuH*KYZ<*`3rD|SguHzP^oLlBPZn*}*xL$t?z0`~t!E|BN z(uI*q7e*&t7?E^g6w(D1#^=Gu>Ge|u zktVIZtJW$Vr4pHn7MDd>W?oR>UQiu9<2*meR5+*N=+MX?ktF^T(!Pxzk#CpeM7rbZ z*gGG_BEtZy<}q2Cu_M4{P>Cb2)p_YVN~grG7nB_MTG*7Fa*T%VRSQE`z-xEcOPA^@ zvSU$=7I#t2z0!bq;k;IbR6==VPSVn7A6q_gizE7}6(lZEoUF~G52JGgd@^~w$M@K5ABFYtDb^4q`2BBHz_M7lp50K zOA3}?a|NdH0dc-xHha2L`@s5Sy-;i5{ zH_>PrBc)2b1rnH}>NPNEwEo;)#BSQ-XTM)~|Ke|60f`?ykNS?JQXh!pHI&^TO`J9Z z2J6Our&L0P+hs^V6B|EMW{#s@j$A=B9fN2P)a(rX0dRZeFCTa4r5}ys(RjM5N2~ou zqj)r0@JN}!CHZ@gyk8okl1rNoBR_G*>ZNgeHEOS5;@_{eBP<0Pif-%Ok#>zBQ!s#A zbL9GRIbDmk*55{joOAkP*h%?$+b5huJTCS3`#{qZdT;QNxy0D4ce0`vl3@D0qx0KW=%~pQE;t`uoRp+zn&exzs@}p1xWquk} zO$jc={i(``CV_QCa3cHOlqin?L>*(xUOwCUh+|s+F$peU3jTdagcl$lfEfW>_De25 zMm+51+a{wPl-Zv7*pv;Sg7PTC_FT!E7d}A&upnd~%_XoJl|vDSrr*fWq#>Hjt+hxd@?ajq43$aHu--ct^2idmVLn$4<7_ z`QGa#SZ*l%v72Zh9-Iw&=9s+=gqJen`z}iMo9xzYJMnI6k?1pBH!@O^W2d=HC1jZv9!F*JLEGvpGb`4cS zZ<4d9t=0+;rBZmXRf1_YDLn`P(RUvZ9C2r~-Ks#nsv+VTe@mE$a6x6eg^j(*Oo1LE zYmhpC%I3GVDk6N?Hi~BV;fYRbo*a%sHIIox1KE-qL|<7kK6O?HT35}Q)ZXHEb<3;Mw<>d(d73i2cfc6rJZ9&ri!yr1-I*OfSl&W6syKYgK^*9B8p(}XS1>fBkC|#b* z@a`lYJ*gJI1~x})cLc8NlY7{t(5%vd&Wzb}QKoPZya5Oj( zn5T1_wx8la-v${!I<>%oJmN{&c-BGu!nl^a9J zP@R_M;n$Z`erQI4b0am}>ns{&%|8iJE9^-ca3jPo7-hx4GE0LJ<4wl`@v!v6$Xh&s zes~H%7;KLpf@x*6uE;d4N{XFA#0(2)E2J%1><<+AD67_M>k}5G|JHl;ZVXR~ujN>= z{>#;J->i$ux1kO(3k5DtUBPy>bjtrIkJS*1It_Bs;u1Aci%Z;6^NY*mFNkGhUR)-x zme2@Br`!&CaY+sEue`YIs_ic>QGdj1k*msT;f`Bm(*d@Y6@4{HgEHI+Jt}6sR|TSv zri)<3JriJ1SJl>i}G{uiF_bgAq^jqredv|UHeCzfvePq~==DotsGh>UKv2RK8-i|BT(YTLejgjQg*-34;I;N{r1we$&YFnHaR-|mdBejNRNqY>25vi>b zb%`ke;~^LR7ty^WAQqS1zCmMg*~emZ*o4L9Vl0l<0*lo{X>LL) zuo$$w1{Ry@1r%^7VXVaX_4A&{RLtmkB0KsnOr~ z#@`5ivfUpbrLTJ65LHrSH^B;2NA3@lr3z?^_S$ z=oQM^G4NBvPVgsBE^=f=rXU~;B8@O+YOid^x6slt#p&Rl=~}xu!|-J;r(;P1PqV~w3QXq0kfa794DefU0@427NDlShl1GF z;=Q>vC6g*`fRp5_AO8ubTt=A^9M!AV(O2O0;b~pb0_S8bQCx~EMh9h3Nyk3?0RF$C zjK;jIRLpcpezfnOV*6m@y26n(t@>`p;~GWwxaMrdQ3Sx2V&%Ug`^g0s5UZCoItgK_ zAca@kFG{FE`~G7+?RV&qgT&j;R2S44bO?#{E-56r13MEh$wSsz;nnQxx}#X-2fym* z91+e!il|9WLFKQQ*bE2s(?*A(%i4Jkz4Y!WQn*-+|Am;_kbO z6TC&dUL7w>zgVGz60HLZYS*FhVI9;11Z@9-;Z8BegH#OM2esV9!pG6anxmp2eabpx zd3>8jB8OqvV^UkGFM98G!4|sK^&6s91|c@;pkwuWzxduW#dq_Z-MFi2HsP>3FV)c> z^Y~Wx3Awk@pv$2POCV#jggp4KwPCpSsXm+*9eFG>Q9&N7i87W=8TR%;8zvg+t3@73 zWsOzEO7mhjMX37!x*SMVkFXVQFQV!VD2b&59+c+46ySz8KAv0v-qKj*HzjW z;-IyrVi5}1-;U9SHRJ?<1^mX@nQ zBR)rTjvqWaQPpof9#t`KbRxC1WIpDF*rcFUy31JVC5JuH>MghvhNR%66BAmL3u?Lf zNY^QOdjvr*;0t<2crT|AN}DIrSePYXqRnfk)64Wg+Y}k=siMBCc!RFU36TySIXM{J z!5&D;jAbNBH#BO4detYo$>(~3;yHf=p{8QF z`9KUD#U~~JDv2SdY1E>(9kPMSj zlF;fYQN-#uK~Epj2kW&C1^MCT=i_VB)AIl4^z@H_ z1Qr&55r$Zf|0?u@z9PMK0&W$PVRaU~#XTke;_2p}LzK8BQKHx0d>jXq^dKYSkn-`H zS@MBs@6^nKY!NXjab7lmFXnlvd0b1t^5UiOzi(2#dQ*42mMsY(FieT3CjZNdHvhDHV~W+EvwXx}1S^oa>Lvk+z$BO! zhRsh0BKuW|3=slGHciN|`I!KqAIi?yk+O2~1XH3cY!1v=@GLkObp0wgm1N4bIhFRD zilsKcnOJkj<-veVhghFp5nBEQ5UfK>G{kQnEitda?gJ~EF^1n8!^^9+buGO7=9{_> zV*d7M%O<<*%1Z?mZUugpOLP!H-7Ogh`=Er)uJ*7?ng&=7?r8bUPVrx;w>P_*2+_Tuo_ZT|!)&@UE z4`%H#;7?k0sZ~CpDBA$t5iJUG*o!_){!wiv@VZNV2Duyt#1(OW5)mw}fNE@E<7`Q-4Z6RdI5 z;;7Q%sQKs5AWXegZBNlreUArphDLlMTbGNbn2e(A)~Q?)alJofRjP2QS{`0p5lv~^ zsN2z^JHp6hM4&|(5vnjEM68y&7U5q&HH-*SVLKy&OIgfncQT&ygk3H*HYE#!k7Yu| zmjwYH!q!V!5MYeifviu0WY;zJi0ApN&Rt<4`wJVW~p z?dPMDgu=br*w@^)CZxR}3MoCgOkR&qvb@r^a)}J%b^#4yd8Lm*C3=G0g!>>7-Nsf+Q;T;xCVb{9+K}nOcQ{*`E+>G;H)gfKrbAo;@v`!c|$k|lDrc3sc3XpK@iYt z-Vkm=%RUv+D8L6Kk>p%{XwAd;KL=yvNB!fK`JFJaJPgVX1E>_+vMhZG8|cDqMobQNwQyc8>kDE-#QA*8gr0FO zg&eDnZJFO@)EUXsHlt)DCoU>g+c%PHo00Swut!=D*rSL?g2%ceF)B8fFE`4NS8K-? z!&&S4WQS_nBIYh}S^(UfN%7k<(yv8q3spoC{0Z?bDpYI>i%3N!uPL^Lk)AC6>H8hq zvWyv}DQ+u%_x)!HOrgn9vngLh1;W7Kw}|IJ1Z~8=#IY=)D>Zqu=xAcVs}1(}W&Wgl zv_1_1Sjh3|>{S(-27xTFUtK7Ug%cIDQd^BpgXk86E{4Ur$Dj*g3FTzav>_~7N2Q<@ zs}ZM)+KyI)5xgQAiSQMAzSl}Tdy_2mC?@mtB^BHv40-ur)QYwlRW4FbUp>KMNG9n`>(rO{+ zB><7Vl)>fTfGy|6QuYlDWTTpp61mY9a$Yi?*gfeQ#Q9*O8rn*ot2O7PRO1WID@ma| z!O#j_-9rV%Qf=)#2kQrT`?9!{1;0V@b)GxKVNO4gU;vN{SWe~JE6Ft{aLgr#lmj{u=HgvR?=Z5`b zB|>wNpAXFiNJa1$WY7H5>;(NTJO*a>?M4_81YKN^{0l8aNT1FP>fs3QCxQ^MWP+IA zD&7%9+A5w1qN~=SUx^^JRXp_h6%mBG9pPQF+Q;Iv3@+I->!7wuRe&KN4K>*?NkwQ` zl_69^X40NwgNH}Lkwu_w0=+0IioE9O9XPA?NK^n;8m4`Q6cX3 zCxgLRD1ysd^(U`wr$Z7j3et`bSb-IPdy`h`VU`^>c*mso zB2Ll6csiVNn0!5)tW{DG+0RQV*4xTu@@h{aDK_hh$yP`X{jK~tv3BUku&6JgWzYobV>Ep>0J-od1} zhGytnv~B4?+ISK7^{SRUw4-TPG`kc8t=Z8;bO|@(KPFV;)z%HN ze3hV^9kOs!7q*%eRjV}Q#HjG3)=7xmac&M0jC1P$xtpZPPh8uZ_n~ztBYQ#n&({5i{adI^c%^L z$IZrD>6&U5saz6Ske$phq)>!2*{!$qbBNx6xa*LNCT^@56JJ>|SfM1!T(DN}LArcA)qL4BQH_xwUvs^;7l9ltm6z8HnuGaZP9Z}@F5kK z)^%4yA)7+f31g%&l%gKe8c9vW@s{vZ%>bEAk9nu2iA!*l>mchl-4fFkSWAXHW9@ce z?bZy|TGlXh*}ns}El4edv<1WudN8#p0$A~&1?m>_X97U}0Nw{>f%NI84g5l@Smc%F zV!LfY1x(t`OaZe?8iwF?s)vKuWrbWrL748qVTj|}U^82uQIrNs>J=QnyOX{8(B!bV z`)%b=xo-5Y-kEM{CI^{8LAK43FtnU(!>i2KH#PlCgr^?}F%US{KtCwvR8o5J08Gu5 z)bZ9hDYBqDFJhPtMA!<}B>QbDs1J)nc;ns$9q*1%3C^)cxlm#pG{-Bfr!L_cO4zV9 z|K%&|z@||FkpmIaG^}E5&bU>p0}OZY2*>T2<(Ry9lOhr1;E})@?8G9#&3K?!Biu@aSLF+g?>5R z!bK{kfZ=?@E!LYCoA>1^nn$d-(@E8`m1}u4mr~KpgU8wjgP8~Fibh(^l!B4;MiT;0 z-9e&nY+|MrvIHVnnhk8v6_eiv{nlXeiZ$}(lKDPraWNb7ztTJpOWzSo*MbH~FvHT* z0Tbey9ZUDyTF|8ZSE?g6V5uarbgA=+r5n)?0a3B^t>BpK{t00J9JHcmOlXjTs5IkrII}+h*!9pY*u)MJ?x#NAj3oFs+ zfdhPGPg8(G!`S$VJDdp z?J&JNnph;UCU#p2l3)fSQpNOBY7C5M?3pD3T^>Bi1yvzN^OLOuPe6r@`Ii$5730mCahcd?N+dJSMi(ZMVNS!Am)NN4m}R4cLAG7P zX2S+R)-Folzz(8duF$=31hbjun#I5FFyt`Dis6H`aM)H|igFp7{8h2g3(Z>B!FH`H z%#p8^#7=)e{gy(}=?_8la-kN&QZ%GP7TYXHtSEERhs3CavN>AtDb}OvDoc5~D%&yw zC%QY@AjR0%$?MUa_MH3+O;7D8euWLQNEX%!)v1z+ovg*I7OYyms9upuS7Zs-P0o%! zkW5{?Y)LXsvK|@7%du?X?cTCQb<8eXKr4o=T$nn3$)XOmk^?OIL^R4J-dQIR^bgZ zj#n-3HTOh8=CE}+6~gQ4?4LHUu%~=b+xmQh&0yoeM6wQhQ67m zi1P2-EWMSJ50jU2DZG>tf*OInls0~b^@qHK7quM&LpOH$aM?}?hMRQBLuuqoa#ApBOauKG|9kq`@KW3R39{nw^5*I^`>lKNypbel$|tG z4PDz7EIOxKrN=U3kVKO80)eu3yJEftNJ4oSL5B#)ZL%6@B5e`I{8z{#9C9cuoXHM- z&`7-AJ3Ww*l$uMa{hEtn{5%{|b~t<|8a&q=#Dg|EkP51=`*Gc^aVa1FnYy+ z0$4@*Dh8VwlAJO9fkM@~X@&_SlLG~H3=Vh>m{JaUwwe_)pBY4-C#nc*U$l} zPm{)LrefHE~|*TMnB3I+ks7lXj`U#x#=eDgdaU*kRn&-Hfcc>G`t ziP;#Qv1t@PW@^SIbQef1pQzLvW=IDmxr~A3JDXzZ(@Jo7pzSwG8K%{Ap&IqOm* z2=3PKiNily0I}@PPY+RP zC}>p4EV-HDl{NFOw?J}NX+}$sdS}AP5QlUiQd3M!PwXp=lqz69nR498> z%@J6-yE(n99Zpn3k5j>Fe0+0#lZ2<-ay4SOAWsZ)f0zT_fFE!ZNn=YSj)oD@eQ(8R zM@FhGPya@}1={O5iV)hMwB%c*B`5UYf!3*q*oh)O%_nkxkWx_z?mxLo%SXsM22F8U*(So#rE#M{u~Me` z@MTKrnjSF&Jz^@U&IV}2D(WNCJ;C`1wIqK!gp4plDbq%>J<8?2J={EKBBDE@@&Qrx ztoy$Y8`BCGKX!3?lPZvM0Oa5u%tcfAC8lKwINWbq+J$?RIoh73c199o=QD>h2DC0Gn_7>wNRB zA9Y*cOX`Op{I0j~Jat&Mqoh1bHERdi>oDoCkDcum>&=5I=o|>kTLCtXZrO! z{&QS2E`eS;oL(MXvW@fRdRvrpbA7x%hF*v8u+T8ZVXXz7WY}s-8aM)PFoa+*fGW5Y zep0uPbR8kIPL@TY>ZG|pD{E>7CsT7LQAOEtRGf>pzPge&+@Uu_-$63Onu-l<- z-Lo#kl!k68BqfCDff{S#1(Y$7xvSTFDkYF`o&6)4JVmQ;%MCo;Q8cT_bFSC)lE`m8 z=1e&c*Duu@q&9sE^=mOgGjsBb`|JztR!_y8dYY~(jxMVT z`Zn-hRReRF)OHv@Y78Go_c)K}x}6gHa3C|CQG0GVHD$|oZdU`>q5uSfd5kP4Iif^t z6U9#^7(ze9HEd=i;kxW{D1_b%F3TCiRX`1W)Jlvx0J>_CTC17EoA7Z-pFc0@^W>p# zMn}~AdYeTuQLqm)OPayiENgJ4VRZ$cKsUgLq{wo{T31S$BwZY_QXy2ZNcwH*b>N(| zF4Zxck;sd=N(Qs1v~sg4LK-MeXQ`;7P(t5yGFyQn_BghYM^SDn9h{|q!m{{>3gD*_ z8%q;W7?KnrH3l1YVRU=wR_H`Y;a6z8%pts}RXEhh&zM*E+2Cz{boMzvL+aoMRZwEM zJ(H;51{N!sYTVLmjGtuj+_OO1&=qLPapj7ZPnvC@`R1k?SluW`k^r})d z+jfcb4oNrbZ6AJJ)Z3G>pHYVe@gXgLAvFa=0(h3c66*+5*7sBqZD9F=h93+6*6JnWA2qSPtq}Fg7C_%Bb2m4VCa2U|EEc5X zZU>JJesZDlY+e3Vh?8>qzmXq$5r^j+fL@v&k+{a=Umrn5GR6#>rS$9Ih9g)C zrfe$NVivgC6uSg2Ptsdd#*3(iQM3)VP1iFX49Mz|X@ImW$TMnNs zK3sAw%ZO5DgEF$`z#POM4oTALUBxd`z?j6Elr;nKS1HyL!0@7Ec-S(u%${zvFWPEi z;Yofgvblz~%VTiwaI3d&gcEZh%b%)KNEs%PnR+x*Z|Mgo%9^b*Z1b0PdMLHUX8M!W zS(Rxuf}GWI!o8J?0P*Hz5kB`=`!Cq-c#~Z8G{t-A=*W7^xTZIg21A$`eU0Tp z^)Zzy{3#6J+x5*V1!vyK1ak~F(_bRM29L0gqL=Gj0eR1rruV4kUT+!D%%5x}ED7Kd z?}O)s{d3qnkis(l38Dmk(#lKbp(Iu=agfd_Vhl;IcQ_ zDE{-h{0?7s?h6(&_?U#nYi7@0zp}#C;#Lw@JCMK$TQ;|?TPhX-5+FS#ycI@wR8#|a zEe<)ey96d!E{?`~@*F_j)#NzLEc5BirH?mP%%zXBnt(8`!lgA0aOvZ49&>5lu9}uh ztBzSNeO%_FYXg% z^v-W~sd#bt94f96g1nLz-KdKoxW(&>)3)(!O$Y~3pj;JduynSSGSG0w) z>x$ndT&_NnyCOgxVHhgW@`$c*sEfJ^pk})So(G^9kN`DA_8fB`jU|BsoOS+Il=C%S zhLVqXM7ogyl)sRKK3#`gg5uV7q^Q<9lS|@=dF^@J*7(P=k z-yi%Co?t1_tLw&!Y_EANOsxI>6N)KIj{K|W7P=G^6>%R>^x5qOn0E-6>498;PR2)G zZ$WDb5&%uGDM|2g#d$2jMmZ~+&ObK$;A6+KCe~Wwu3@dqnzo!pd9&*P3lcE38A%=U zMgcpaBwlon+G!_Mz8B{O8!Ub#h(aRhO+*K#^5@GWma$)+4Y7}~V1I`ploo+g>2Ro9 zxGb4RrmXu~@A|v4p7|~r=u>a*Nk;z5VpsWqI3ta*95RN2A zf7Tx;YhxoV>p`Zo&8<$;vzjLS%R(0+jl42U`rCypWlZ`5i;zWnwY3s)0`wixkS;MU zXxenS#$^Qc%Qa(lX`;9=Zyq^;MoEh&NPngF?(e2`{44Ikek_@g3pSV+RPh*#a)GhL zm~K|a6(J$;V3j?XIAz%o(h{OKls}eRw=#EHPuVc~O$_;!;km}h=!zL^$GE{bPoU`W zvyY~^Dr5WLAd*^T&Bk^@S7H@LD3T@Ubg0FCM%Td8l&!ne`VdvCho^kp!&3rW6xXOi zNitGI2~W5C#`bX&#&%-a3x)wH(A)xJJMT$kW9yC!4-MHjSG_>te=)jS{EbTXweO1PGso&M5 zdbu^WFQbjy*p5+)d^eaCmtj<#=psCYu-CM!0ea^<{){X_Nz3b}0K>?iPBY`CnGrj?31V>`@BdR`dY<1Dyq8QWR9W4;K}A8t5( z+&8w{XFbx~W0ev76cw*EUp8aWeH+Z$k>O4d0$?bUg3QnAVq-fGY%5L1c3!^}3HaB; z*q%Hu0E&7B^$}HwIZF`If32~7Snp{yo&-e}6A}Oz+kp(26bdYsiLAgP4J<2WH#R3< zlmrqqs8&K&xCSJR*twKiFf3Ku-`MOmJ{rs;SP-Bo+7Dy_HO;&Wq)Ee6z z0yl4LS6p2f+mFa}ByS~JEv8v);n(V>=uX z@WOhQWo*aHW3MsVx{dAY3<$d&u$Hl1cMu?v?&N^vZQ^BkTkp!)POUPwZ!x`);7~Io z7DJPglfks4ebP7Xv-LWsX%@2~gPE9@%*Sh&>hF<>z%QE1Axhn7{+!C<&Y-bWYZi7`wr>Mxrf}WwcU>Hs^Yjp#m?Pj ztm^K@alXaa&T2S}?chu5hafCtI~!uLbFw(rFyQ{V+ZqA)c^FI+I4)H#0X#7f?&#%u zYi!4jXx_o}x5jqHCD6;*u9t_|*nU`)(;3^3SZEmIW{mAat-iz94ub(y%r|@&4`*v^ zkG+iAsfJxiXe&9M&F&}~=TY3S>)y=|3y!mOQpJU4XBfvxV}xD9EKGV9B2Kzo!~uWsYof#^x)?0$y;eyS+EW%7QiF*Jp1b?f#BljTis?b^LHfjT<-_JP^b|nHX&?9Ekl?iolJU-$ zz>)|qqXV{NkrO*tObA_DHl8qUrfZ3*$lFcZ1g?IueHgh*vR4nwr>vjteqRzDr1VEK z`+W(tla%ry1}VBrOl+=bsNj20;UJ`|ZtDPxCKlRNw|xMlYr$MyAI#R|?L6p%7ore& zrE_rs)m_r>4^8cZH)x@oY7VsEF)J`7sqpc7JISRq))rT^;hs-2xOe3`p=7q_Y6~{px|I7cQ8wt$nkfMkn`B{xJ37Iv`Qt z=Cwf5qQ0O6?a0uE#43?k3g!)(WE3C~P2KS6gacD=dpaRUclq34pH66gb7$VF>DLu+gG6(nWH!_)4QU7e&1OUVMgqH`@GMG_ z{As;TZzP-5b-S!#dZ#Ggqd(;yOz35{S;dJ6dal*4QHO=)rdOr3@6rA^8`w+w^QSoC zc7(Z&nK8arP_<|kf}nITIIIXQBCOpwBD@NyhTkh=qOtZ{1mPB!z7_au5NrKALns3A zYfLC>ji*!`!Ru|choBZ^uU@0I8M>BX_Z1Nd+hT6uJ1_I5o9F5$KL&|yehlhT7#=;I z8V~T_=-NE43~mNL6xR}q#&8g{2fO%1c`JA(j^Jm;UYENLY0?R$*YvU*X~9xt%DNU{ zULn6)jcHK4(?QA_^(O*;)aQ?L6Y&bU6*idB_*YAmK4XLA2*WmY^?Ciuw}8a8n9;N$ z*q8wK%J-hC70(LhBA9zb-iQskpM6%e^sVWqMQ}pFw$Gp!I3QpmydMbu7~OWH&;goO zUteRIUJCfIy2-N$*7xUe_$`1BD5VZ$64l^6rzSutD<;6C*&Kja(4;@|#4mZ`(}?D{ zqDJ!?LlFCERv*-FcR?p#;Hi{5E#XB!6_DXCM4S+STX>>0sLH1|~V+Trf zmN}jZM6SbZ)l!UGa$h>}Ioh?)*3o-mpP=?oaA}T}X|M6nnB&Yo3@~GAMseTZ2aCGm zOBj#hOSEcLKTpf_@~;jYTatHNg?gNHOH}0(nmTm@6?8B-%Y{@>%^)_sc+EsuSPk{G zWwr~`lse4OdCU?dF`VK8ma&cN2zc@WMpaqKvuj;IKN!6>r{uXGV2)K`&(WV=TlV(I zOgNlrsCvx@CDaOp%Y%_i{vY-Uv zNs6k9{lThtiAa8?-$Pbf8Yts+PxUY_mOHr6yVP-vt#6|5OI_a? zTl5{Mr743`jepG$GQ#}ww-jL*Xb(|hWgUX)m7_~F{>*EGE~@BH30>w!`lo?3x%Yoc zNTcdSRz{j9mJaOS#grmrRtd8yWRAz;hcyscb^(BWqgpIIM^Iy*eAT38KFN`~Fq9-&ErW!WmvQcnQK3mZn>0yE@(yMZGqqEy4HPp>mI)ofB86)y44wT=`jjVs zL_eD;(@xK2MPA^u7&U)fK7?1c82?m>J8j0C+PwUQNULxE)rve>kz<{qg`e*6g^7l7 z*D)3Z3GDRGM518=FuFv;qJr5(!|JOP&4Hb-F#MTB!qrPn6^%qvUUYB7 z2=3j>po3WE9<|UY5}#Tm+LVo$fueu&^jMVPYFEGV zxm`5nM8kA22m{TQx$g)gg6|ETrA!#o5%R$Um6P})9l9(RY-Ul_A`A#>LxfRHN?;6< zKGG))?m1+~9zo|{3G$)zQ4s}%Am6fJm(C8sZAfM9i8U~mGW+; z4s98f{0&*42~a<)b@D5y=NmdrH69%1z{044m{!VW92WN={;^z(86SY6MGaNe3D&g5G zE5;}E*nogG#SkoS*X0P2D^qC)RH*6Prfi*ezn3_y^#lxdsCf!BS-@QYU_vxmjHd?x zrcRT^oZU@`;&u;$lK+A@=b>F1|0Y49+Gw{~>F@|*nuIS-^4O5F3E=nJOAt%#(Yla> zv`PYauRXdy+oHr|+v0D`v?x_1o{oQuj!7o@XGDsOLEx0XO;5GWhL=DIP<%lAk}!Ut zlS2!M3N}!V6$OD5c11cVn!bf4ke^yRF?B=t z@L2k$g28yxrMB~4Svi(D&w^jY{kMl$s1SQzF*vP?9DE1Ja%_k0ON+nFq6RoToxnv^`0}RMJo!B z1(9fl_K7tjWhU*@Ts@`}dZ1y~hm$YgQEg87=qDi1P5B97MG;{@_$%QS`c$|lGLz0M z)Z?1TN8Mlc?tXr2#3aEeVh?PeV@gq%xI&rp#KM!9zr^4%WGYffdI^%QdWikA#YF1? z0x^eFq!fjP6fa^?E)ju*N&-JBQo=&;i&)P{4(H`XL3q8XjR!wI`ydBQ3zzy|q}TgJ zjB27@hWnf??X1DQdA<4UPhKgSfA<>O>eL@Hw!~&rji0a_L1R*=+l$&FRbyhLCDQ^k zBZv(?5vEb}jL$2*ol_2ebhNvGau?|pQ0*{`TN_i0_T|Y6(-omO5HCc^7KPy3-YXrF zov3k9_Nr)@#2@VCaujAvGNGXs@}$)8FjR*&wXch=QwRwG{7i}HW^B4CJLMxql#t$0 zyM(NQ2rUB7cPVTY8W)1?czW(u^{Ln8@cIBrPo8W)Xd#lG0OLgYKxKMZ^%1$IP6-@L&p5!t zPW*rjyP?BP;+Mp(;pJ>?Wfh{xQ8`WE48Q-M!5N+^&5geNH@W^!U4Cr!>T-To%C{2# z8gd@*lDNT~oafMYl>$x4d2~tffWEj7Su8;h-mO{IYV`mGZ!me1+JIQ{lvKn}DKv^y zPKz4wB&~Un%4V4s$)o8ZQ}rIO>SVEGp?CsBy$6LyrAGm;1@9hpM-f>!>v6obPmvk~ zHS29lN$Svx+w-y*;h7{gO)i$jdm58RLa*yBwXn9-@(9>=%(O01+9RTA5`qYR~)w;t}Dh7n41{t!NJuP9og- zN)qA9))Snptv(U1o&+mvpCrOn8w`)V_d^C{MI&(&AtU=ddmaZPf@@rOx1NnyjefiT8plNuAjX^}kB$j2?tK(>@6Hs|TUZc1gqPCbTA%-?* zgE-6;+Tmh#=J!{2uCLDYSd$cs%#MWRwvuE{B>mQ_5bG!)viioh@8yY>yk3H2l^YHe zI?ASLu&1MBEnH%%)lpms{YzZ8I*JRSdnIzrBN4E56sM^64IIEnO!x*4Fm82}td)}k#w0Gz3eb~|vI&co2C~6wC^#!Q zm*_?4D7&Wb;2rFMp$#>?fdq{e6oS=7WC|qxIAI6#0mHUPCfOBGGPZ1WBDtmt=_sxu(Fy9J zYT3=TgpML5GAO%!5JaK}LD>0$kVB=RqvTT2iL6|G9c4|q13DdrWQSroNIakrI*M5z zz;AUFpdkDgAU5Y&TnQkIn!A#j8bWwQLJiO=2(vNxg4HclqyU6kGXyYONUu#SZoZPl z;wWwbk*ZHDPERr<(v!sEyyOD>>J$=Pv!hHi?v261Vm9k0>=hCp^c1$slrR5H_=gpc zcUQ}Ox(*7K>j7EbKagCyO_&sNP`4MBQ#>UYQyTND*3*;H|JX_@7c=CZj7nq|H4!fpk52lh{n{m|0QlGNs9OxNzlUnpP z6#04N(h>B!+UT5QK1FTQE~-uy@D+$u)(!QZi8C5EI(CaPyMNN)slX6w{?Fw8Nlu?M z?h>cY-)8P2rsls{J>ZFCVvTzP8cACpvPs8F!8n_ol_$Kz7|<5L31$lzh<91Vin4WM z_Rqf?KnG%unyi#SYmtY>`&QT&ba1b#w}v~0Nv42w2yZ&#k~jx>Xvj2>JT$JMw^y~R zw!c@E?kYM;ngvYGs{uRK@~i-t{;&*fStMo;Kp99`T-0T6HlOPu{R3DBx~D@pm$j1} zGoO4%>KPrAAGE0+mj8#HFY<%ZSvgD@j~u4Xu&@z#iZZs&Xy#$SvWcUP1~&ZDf_519~N(FY2s;KG(MZ`hkuK=thTL zpXd_2CIg+!w6XCJzMw-5nH{X+nu(3LLxJxB-Si|jB4aHu#48r#>ioSlr6)?|tbt)H z4UgTtOA2E_-z6n3bQRCA#Z%jW*+?R6EC03|JAzF+V<6=yzyp`}J319it-s8L(!#_w z9XvC5rmX$@zV!R<^!q>*za5+ z*Ft{tK6%9)~V5-!YKT_Ah8_IF;v zGjM)zHjjD84oJk57Echl7v;$CnDJ|?+R5R$$e(NFu_PP>0+BIdk;>jr=X(5n0T7hbJipyTUaFX7kxrU8YJTfq zfA*`(H;z+;9KKqdT3U!xnv7G$<1J3@7k})@xG|gpcZ5^#`Tuds;o%2x>exY?T1K}6 zr&8Mdj8oGkVmuaLm9#N)h?FTmaR@4z#VU&@DbymTuf1R7geC`v_{#gmA#yjE)q|W1 z#0I@Gt%K;8QiFd&$XDf`i+LBsPHl;QD&jbl^c%x58yODpt$7^l#sYaK{?%u{1YP8@ zyi&fkvXF1VL;&u4K4{>YZ~cU?uf?}wNa9;cD=dE8@+~_RR?fY*rJaL(>p7db&9sbK zSLM}+oL4($M9u=p|O>N&0_fX=-EK z%S+SMS$s1ct#rJ)8xLL~=m;pi%s4s)07A;c;f;1Jvw%w_Uf{Pi3Pv0X0EDzE1t36> z6@Y6H5;U+scuBTjz|63)omm6;$`a8BJbHOa#?v(jTeL)j(g9ZEK%~_kEkSp}B~$=q zou{tPdlx{ElG%obn_}$}{)rK9zW!#?Dxj~ztR(k~GL5Jd# zMm?+e1i!rMz)|>K?Z8p^77RQTzTfy@r||t+W}H_W_;u#nI$ufh81WKKrzUo_rvqLW z6rjgTOLtUv9>jo^;5Bo3A*UoCTO^Ox*4PxBNg z9pE*2I2g)rfY=_ zI*0?x-;8FX42S4c?Q-BM+=ioug4^(ZPta1yEVpqi>H4`ZeEB_J{l~xi2fy@x6*sU3 zfc@twp(n4Fqbx621Kd`8`u&{@xDuYVNjYvow4!8ueOg&tESYt{29g;> z4q8cqf=Xs-MQ8ib6>-e{;^n8jxCjfp36p z{I-U`a6<}XW!jJwj`3$Hd^IbR84W$}kfy62Y`45xmh@z1Zl~3kVAq z8`5YaWN?4!Y!;!h9z-dN;8#0lSjB=dhX`KCDxmU3W4@rl^5kBQBbghq8O27=czbZf zbMp6^gNE0?+Cjr877Y3tjN&STzE|-Ivh6OK=)^2uW+ABY2%R^2T7G60FR@IYXBKM> z2N5bW=`6F@Sj;R4&JeSJe1lmee5R6FX2G!>P*g+A4h-U{57FeAL$P+u; zb8O6LZ3NF&=2?)~X2&iz>y2O+?47tCyO^Ujvx~)+wx9VxHrMPIKlK4Kw6K9E8^K2_ zg8G5^bJcY7+Jx1OhZqVN1{*$}V;q?wZ?K*L#@avBW5|mktTU8=u-_KPfRx2CsA3kv z8-nmgqde}p^{&CqXmImJ8r;kc@^-I5s+euiAtjqm3v#f*p|FUo%h6!YyS{jY$qM4P z)gb$+f+vDQWAVh4V?DV+0*8Jsa0tckWbt=>4J3(R*oU2k6$HBGNE};1K3Wmp>4}@@ zAYh9C$4qS};^1T+Pw2Kbb5CJpe0Ct`2*Hiwq1%lQ95VsIH!G56Dnl$D zaQWC248R?sn($Q}CbU5Tp@+$Jf+y7vaUiGHv@6-0l>MC!Nn&YE+Y0u)J?*?9f5Mr; zX$rIrnKOp8xyETz6r_dtZEJmlpYO+WHVx72{>%&5avC4yK4?D2V@rd9N;HZu`2$-9 z+8UL!l_u|uD&-7KpMRML7Lxw_em=*4N_(N~x8IZ0M&Cb;Pf2~26E=+&Cu|%!K>XU7 z!J|CX@gc+$F}uAXy<3GRtINw#!H20JLFXBsn7JD~Pst3xbE@G}6e)r|O34eT#<7Ok`4xOgp5c$epVaMV^?Rqf4X%yib@3X7gA-P)IH@N zPvy_0a`nv~zov{F&dl1^&d^eN1ApLy4P=J#rS_Gc`?*`wh;!!I07b5M*d=r`{w-rs$< zI-B54`|djkHUn?U-FM16c++>gU!8T>$85Ooyz9R6d$qp%Gy2XR689b5y=?YKBw=B2!0VpY#v2+(&AzyjkTpd zUFzpN&FmmM2PBpGGAv1~?nV8=zrMt;D)^%B$w)JWt$!iC^!fDC=h91`(LK*UtzXU^ zsfW~fMK^H3Pw5vf^a8)C@sqw+jWqLodgYB@iqNIg)j4~8o%QE zRO3tOr7x$KUQ922N%z$FMg1P8#uaM(f^JX{KCfSB(r1xyH-BPc=Rk?>ri>D76Elc#u14dm!%K!O!KY#j~#uT0DEXy%e6kl&0WG z;kr~VOSQ2^tITJS(Q0ruDCpS=-O`6-6;VK5lawq?X#RBB=1$Y?CkcywqXk)f!H=Sbwi0(hXPuzkWmtwLJY;0~+EWKf+YSoOAg zfF_3IR^Z!|2suKZ3jRf>kfSu6(qB23;kTgK2PSNlCjb@z(8U;H&aL)Z5AnR3DcfdZ z28EhZrUgwhUc5%oJg;PVY3{xKqhWsc2k-Duk52}Xx{Gmlp3G|eU%M+XK|KZ*Qe9N4 z1I)x6b>IVaXpw{lPVzB>j%C+CIm-HnK2+(5v+shXa9|0~@7?^ayn4y2Gh$p-)!uGjYHfHB^ib{;2g#SFJ8wJVi5^J^%i#5;btf@b$%mimQhKK=i;_by;|T}Peo-ura7`rSt_ zTQ58NoXB=7maNvZWjhW!O6=GZLrgG?3EWRnq_(YxCA%d%&RmBmV8CG)J8|+ZnmSN=DHy6XE^TvB8mHH4|P_w6Pd_M-43uQDA}?1`qfB7vb?;!M8LIv?SNR7mT)xxsmqrtUDm&727+4g|HC|M6WSUd>tc9E7j0Xp`4$0~?!C{2f8U?_qmbBMcCP84h zCSQh069bc+xcMOq-4KWTCzuPR{ab~Sxx&t9a(q}T8}_SV)`!-*T&Hbu^J=Ilrnwr- z*J+h#V0~gb@BsoBd8%vW#!`^L_Gk=e=G6eaP|)y?N27%X3ziv5`h_=&wZx#8Fbrg+ zA!MaYk`mpwOj2v)p_%$FF3C zDv9RSGLexu;&LsijYM1&7xV=Y5T{&1wNoM@N<~C0LCPObqPexafmyoaiRQ+%<)Rx63TYl;M60s+7F4{H zWs~qgNEMBOtayJZ2gm>P^l|;WoqvGCOfX;X8Ob#BdbY&6CXO z4$sl^azLTCaex%w^6s_JEVW+G4@Z-gOR=w#WKL23oqg$m9DU>7Oo?(54OfaSFyO2Q z-zD%AtqYGqlFSXP<@kLT!X2?rS?GXxZ|+vb`V`m(-pmFF`ls+FL=}oj=HOlcjMK^u zmM9j=RqE^#n4#F6$Ph&-_K+JzZB%z-Csna`oHJ9gyNp%cB^RD0nNzeiYaMI}{Sbt! zN#^Y6zt-F^EIa+LHvAs&gWqCW0pLr-A7BiiUB5Y2S*RMASWGhKEP^o!?0)__du$1M{5HS4$?vq?&ohir`oWfdMr*|MIbPy*Bw`jwayUVhx}W#53~nhl1RPKTFjHW;3N zOdp*zJBCKAPCTok(thzepBV(kItDL9MOg4LxG0G{`sQV6J!a>I-n7PP>d zdpUG^@8xrap@hUAFLR56%EwX=N<6_-9iS=uOaq?v@(onYp3*)C&AP@&V`!1zfMzT9 z8QL^U*ULWCZ+w zHkwnG&udP3$|A&_&5ZI}5n#c50wXC6C@`CjG-GR>81G!T1`Lc(4hY8GTJUlbr#b*% zS}oXlM7NDbfmBMXjchcs+H^5Au}M*x+ixOgGwhdbLn(0_XI?s6je6U8GMbEPrj(bX zId%X3KCO?=%!;wST=t=EJ5*d1UM4#fz%-L!F|L=RJ;yK>&)Rf3=D+k960JIG@-j#@@@NT(Yi+hWT8PifN7knqSk&lbvS_z#RZ!Nb#@v3&YHdj`#)4)%Ytzmvf@l4}qkc|j_ z`dZ?|TqK+_vKlH?sTwSQ1P|jK!VIGY{JlbK=1W>b_KZ4kz956Z0$vwX;ct(#J=9rs zl;Lni&w*yY;3k8v*(~K%tBwSJ4vX!zP{kvBhr+@p0DT@2Sk{^=m#H$ifoOjE^0nSd z;DUXdnOJwjBp)9Lgy~nY_DmtNXAPy6kU`n9Pq1_24~ZqrhXb8{M$y(G8rg~SA&Wl` zs$?Ak?%6MBrrn@X1)RWEA*vR1y&NJFIbJuuq(w^2XFKaE3LFg?G$*Meb&!D ztLa)=D?ENDa_tc4rC}ACC+@K7U|%DCEn5Xe`BDnxXRkroJr2{c_gDT`?6br@FG^)Rmzg19+Afey6y&*viN=ixG|;(uqRA^v7E@Nll;w`S z7~z24GJvm9W}Q>)KZr@-yM}D9O0&)bnPYE<=#ka(2V;3wFaMcX-l&&96w3(+Tdn8A zu^db*$|1wfr+xw}`DzQZ`XOCjVwaULtJ+smO80iZZ9gPACfB|kK1atbF$$zZ9Cz?L z>*(wylsW2L&B7J72@RplDVX4hs>K_bZ*wec(18@~o5=jE$(t6kN#BCEA_D5q|? z^692^w_MR?v|Lq}ji9h)-aC6+3L92t+d#BsqbmlN2IRfsmu>)4OEzPtPcs=Wy8)FM zpa#?xh0o`V-&|d>eO0J?^x8k)muh>5W zFGdV690eMhTvu?yI_!m=$19A%uwdHt+9Qg84cNXKPv752ITlK z7;o?oWAas(C0$ppZ$7m&S{}Yr*LlD)+xXl?4=oTF~SJd+8AG&Ht<#uBamYt zP6!Ont%r^XFITShI#fNWRDqp_ONQ8k&EHOavh9LxK=YHRA(|PZBc{GIAR*wpKq5Ol z%o~tYX+!pCieif|3!>pkVVeL2ND8-2TDI-BO5*ZH6-dbQy0xw=$5{I-XgfPdB(Jd_vH8e0=#^D;kq=@9M7<4r0M z*4bjYz+s1mmb6B>q$x%=YyRg4`pY$6OMBuz`}88N^HTiTc9&XCDSKopNLtE4sCV*~ zCHOXtPoIaESn5nhgG{}GEK~qv@Cxdg$2pKF+lSGNs*zCPJj6A0ZV4jqRW(!&`-OzRArTUAfIkcqFwX$16QBY51n}EC0gTDifiM9*gRD=04b)Lp{Y&Ll zS0IQcKzf|8Crm(qgCmH+p$q(J&a2iozi)O_?N`DzBX|+D6cu;~a=SuPkNi#FKuK3A zM^}PSr#UtEX$Xp=q!G7)bqrb`f|PPrg|63iRB)Mncm-_K z(E3#I(;+0+sw+D?rd6ljL%F1(^JA})&Tnr^tEPIXvQ$wI%8RLy-iWD^uUmuQM2(m# zrzgc?F?{I`y=ITXIEO@+%*stn6F)cj&$a&ZTK}2ypY{H;%70+p3LXyYmgcTm*4%Q( zQTfiGM-~4Jmmxu+Qg;q`>1i&_1d-^Be~3`Ny(rswD26KM%zk(;FJhLjE6P@QKyq4C zNIbO-VJkf|&NS&AI8Lsc7_nsB<~fLCq=i&}s*ZcCEXo9*tyj-owPwAtJKf*!bD3? zHWC0NMHa-NjsYp3CR;2`XhIz;-Vw{9la|FDK0yw6oGa_N)qHz0a&KBnD_8{@CaoX? z3}W?!fs?6sEn-IJ?mKeuAJI*M3~cD{$eE%ruk^EV8WO?HR2;&2TS;X8?5OUY>s9?4QBV-zUWI&jXk=1ue z3|X}zfvmE0!fXSYK-)ShZ9fMzt}D}9^#kOulnt|rDu{&|poqO;tN@!r6OyMoHNdNa zV?n94d_QK(5^>7KpM~CZZ~H(Bi4kX@N%JyinC2cX%6#vVp{l%L*RtL9aDGq`hEn~n zK*kX1c`jsxtRw*w;YY|=u}CwyhK!Pp9r=WeA&G>1j-Mcb~?YY`3EJOsL<=wbY78N3`?qq4NE*rhkvaGWE-tA#jw0km`G_D7VMhMEE|@0bp^nY9siDa0pYv(VqeLf)5sUA2=9wnjRm&T zViCilp+(Gb$QChpyqRQK#(WWj(m)jvoP=zxUKH|u@6;%(3NeM)st7F@oz+{2i{LP) z_(aN?FRi-t71`u1JGH@*X}_XB@l~ktcUXph`xWs^Gazg}SH&-)234>(;#9z0BtGFT zKM{8)!CgE|3yjmy3eoBLi&tP4LR-WvT~;tFNbFGT9|z@WweV zUla->U3KoBO1?5iD0$T+5@=5QIl4vR^VC?#6cR1qbrLPO!yGG)xUO52kl~CaD`=jT zC2M}o)83Zm8I~uisPCZ$%`;Qt5b;(aN%JDRcfy9$|$ zA}_dY@IAm%w!v<6%K)V;Hn{RJ?O;mO32fa6u>>TeqXJ}12%N*-&S;sbl>AQ>J*oIo zN@098=D#7%qzlxi?Ux=$%>Ti}LB}Yd$Hw zVDE!}74p+d{iWvTGz124Yv&-AL}2N!;^>fcNQc6|NZ3n^>NS87-2`K=rzI*<3 zWSt5`ll2!9J-u%3#y52-9~|{0aW}}fOJ*=Cb3-wYH${dg}uUJVWqUJx8GnaRv}*nYRD5%>n^ZZ z0P#i>z5?UE$ym7n0%Sx@rcJ6g{@akVSsv;Sh;QHz$Tw%BW6v=ra>og74 zc~U$)*HM}{3GDyc`e#{5Zt!q6nXEBEGCe!|jl0F%k&&wgUJ@TBA| z$RM=OGVY@W6?#XlNU3xpFc~<B!LPx)AlcA^CI;{7jG%L1kOqXVl^DHcG&~; zcER-Cc89&)uq@hk+Hf&=E~qU)8=kCS`gJmdz#|!gA?yan7|L2?g!7aV5;~SbCVTFK zcPYz@9?_ABpt==HZ(~TTtsuJ7uZ|34}3dTQ+qe1?5+~%G*Q~!Tlpcpwy#?^ zMfk+|R;+a98y+<_=X?jbY#el97J^)c0Ds+>Vx@-prjq)6<4pquikV=;1+emo%{StO z&o?9JB{GE*?waq3q9vQ}C9_%w_%a8Q6V(~#s0BRbCu0g(u0QD4S`a}wxGIs88RtAG z7t)Sn2T>P-%w)(-?EMnusIBCxime|8D&Ad5F5FOOJh?E?Q%V7T*sFHB#7oJiP*Lb3 zt^R1B_lgV)C$hl*j*SEyGe|ZWgz6W$bzvhR(<=b9w-v$|kcl*TR$qO9p@bpj3)ztT zn5Ea`mg^t%>mKYpMIZAXeNt@zY1Y>^$u)p|JON=k(#o04Dj47ZuE{ zH^Z5yXUIULAVgT1QPlZH1#nqk6qu&oMFHil8;>}O6!1t;hN~4z!YTQX%$zw{-HxSt$k!rMEuJcX=sow08>^+1|Xz0ZpBh#-#rk``fOFCrGH4lz;jJV?6VQN!wb+|yr z%)-^d-oAhq=$2@eDrQ@Q@NyN<&m32Wb(DHatE^(mL`)w zcJ|B6q1RSuvWlpqrPLk}H5e%~ywVvNX+AVS%n|bC2(Q4)0?K~>3qe_Jr2BqDn}O3k zx0!Xz60i252QA+O#aA=*f*q~v*>M0eVFk*R1=Cd0S7%c0<`E?V^{=l1*`${BMN3>z zTM#9H@A^`rCmf{#`k4aIlgVHBwM!dfkJ^1m1v!baRm}~)EuPH$ucwEN0ERBcbWv~D zE-Jdlvo66y{i9U#zd8ATT;Kq}8|Nu#w>s zI#KE+CWc;8w8TYp0XjI*> z5UReW7gb+V%-)=+3U~@sjrOqDECN+8twGvQ_0pmxF5oj&R8UbjeOytyWfe- zK=)q1?vZq5vwJ?MTD3_)k;2n?752rj)J+TF>1DlmdRZ}bbK)tmDexyk*ZfSdj4x!j z-CP5+;pyh0B`&(~baRO&5!ivF)$ug4R1y*}&V^a(R4<-RRl87QsY*o<1{zuF&sSLL zhx~exEcL8*+hu{JPR);}TWSC_Jl#U?3p`!cgQrva!dCx>e$uwoq8azW$|lDtzxIuS zZVPRfdF_L9qOvP!I)qj3+IE(`1em*JAzMm170ms`a~E@WEQGl`dog!sF`08>F3>74Hws_)Aqe6N z^4#ef;0<%9i3>SCJkGYrE0BCPtPVWoM#d6Rk8|=cR=<8ql z;jjJL@BQJg{?7zIC6@0lE~-uY#R&w#qA(|eeL*roVYt7%il75sdgc{11hoiPS1@Y0 zgl|>MVYmk>1%^gN>xzJ(0}JftUReXMZTeqXG{r@iqIG47APXv55Q%NeUTIyxL_gQw zL9nF1wuYgmpS`x)g@%a4cOB&Z_S&)wji9guB56ps4n)$Y4Q2B@d98H;Pnfxk`N6b( zRn4@G5OdwwRn;y~ThR3|ZC_P(KnHay8_KfZ_YjH9Zh^mdd5DBkJVYX?^k{@28D(eJ z2uYn}#BtU9r1ZMlOxxS9V~hnUjrEYyRhoUqw;}oc3fKR1$bQa)I^VFt0N|p6;mu(B9rd?+ZNb z-IDI`#b12x;|n4SO@*Ew87yqyDs7?j_5t&T6?2 zpjz&AdbtBA49Od=J|3%7=TEEfsCKtf{;{kQ(TYPar-^;irw|cFn&I4`B2^CRpdOy| z6?beuiYV-8>xeKBj*HM0q!+cbJ*<{I<};5`!A`THSfBt=5_YEY|9 z8vgX1#Dgz|QEW=M*ZUmw*D-&>YSB)5O}jzLVTs8W`bj06`2wDDoj1V8QN_?H-Nov0 z3A@x{wdH*y|D|s@o_6E+2l(&Z`Y!}p2xt~3;Kd;s5PLpI=HLQ6^7$usrk#>hEGfJ7 zBeo5|1*+wyXL-0Ui*(9iXPph|lYaF^)HW8fSPqfj;r*p)-lrxTRpbKDL`xOk^>VXGE(bzfq51(lT5B_n@#c0?#Lw&3(Y|iOdcdM%m1HuK$ZpC?}WhT#EAQ;V$-CvgTtcWDP`LtI+Md^#t_E?JgbDdN6H%O6Z7`!{pgBPI-AG75f4T z5g)`kUkp3-Q%jWqBnWkpe~9IGN5BdMWGug-8V1XlISNPQEI;Y|GJ)}994xk}=nDZm zCW+$2VyW-!FcWLpT&Jc&8)!-4&1#&SqIaOK+VAclS>{8#<{+XlAv#sXvL&^vY+5X$ zyGURS?zoDUnG3iaW1$K`GC2eXZT>_mW99WYGkRr!&bF*A)KG)<#lnNp-YQ)DG*F*b zDuC1c8;+sHK2!&#UgFXyu>-(VgG#81m}{X&WLZ+I5;j)7tFZzSIY@@u;0o?>T_3)U z)xE!pv0|B{3cAWpbvahf#VvNM@%-sIH&CP`0n#Ux^NfmmS&ne`d?O5T4Vg__Lt8kG zy5bobnuV-lq1C!OphAq3(U2MZI29YF^yB2tPuc}TriHAkM(PKT2-awaTj}PXYw7K* z@`Il`So1W*ofC5KOCoaa&*&F~e8S2(qJ@N3c%6y1GWmr-S2{-NL|YR$ zqKM|)h5A-?i?68>&h`P#w&`~t zPK^p!maAWVHGYLHr2Oi3|4Mw#zWpg!*Bs0j-i zR1~r*$HHgD$;CUhF2_RHJGa)Vid_bB&d#c?&S+t;7PJ0aG0lZy^xWi^v+2e&LX%`e zW7Tv6j1i+XUB`I7GcK#R`SZo->IYg4d*)|hF&FuZWXb`sA^2I+xz`pe#)P>QsB$3? zCC!H!4J%#q4_Pv$OgztnxiMZ8YlGH@B5)v+P(7HdF^jF*dQtCM_rcsJvL0iB5?8qr z=4OLWuB~XOTDFTt7)Z;x2$akqq__x{1B1%-P&_Oyg7Y0M2DhpskZz_C$b(jqbRAN* zF!T12r!m}JyR<>iQ-?Dvm23X8;F0W&Lg5H)S}Qbf;% zR_Yq$X#&qeMid95qqH=sBIngnw5j8-#A1PbKEQQNeFR)DlpL0|J^>w%yHs{cQ%12b z9x0vHla}dxx1#~ zDN|sKy(F{XJSrYh*1F;lkNn;0W@R79_JF$X6@2>)DPf7X7cE9rb9nVST9UrSW8Rgf zaSkXkRUdrdG^e5d_QY^D%N0X0l@BehN;0b|W|VO8>4mlszK@51T16)ju3e^^67_qwhQn zw)zo$)8UMf;jq5s_BrEaTwo3-EDKDQcF7@z? zUeuS~bZ&W^vHzb_Hu=~&^^)Dmyfmu`TxRK_xMEmSvDw8au9((TY*sXiE5vnW(ET3aa2=?jq&(1z)em>kL#BMzK@0b)A}7zr28Qhlqb0pf_qKM zA3?6KY&W+USlc7b@9VVKI+0ZfOZN3;Y^Mksf@%mf8a`iDK@$=vS+rZg7`he6L~a0s9%;UjQ9UaFGG`# z!e35HGq4H`-T$gm_=HjKUza5^^!`aY-FQ@0!eq_>r8{3x%mA8NFSQ^&>6Gyo(!-t5Wy)?!ZHc-yzq zkc_8{#PE}k6wOfIWcIO!Pue7-ZV%-HhgJA!qpjr8-X2{T@%A31J#P-*0B;|BW17V5 zMyzGwaSK0yIF!+RI4t=j_*IhM!m4X@Z}}_R<8k|gNGJJt+_O}>pqW8#?8@}l1i`^8 z(~s#FA7ntN)XmCG156MKfRxY?51cUM;gIa3FzgAzi+wn=YXM-H00(m3HHpLa>Z%_o z@c^DP&EJI$_v|m=vKWZxd9GU;XxyCbS+TY+9Z6afydg1&AsStFo~rP%C+Fp345-4% zgvF2^QP;Y*$qM!{%m-D{t8yi#w#JoUEpu?Cz<(?VN>MFokBuNhBoyKr7F2Qu--0E5l{+oljtO(5w1KFFC~yo7ynBZ*uo!lh zjKzS%hE}|-;e;JRs;I}Ihqb_P^NBsDe}DZw@-m45#)rXp{<<*U_LhfAj30nl;x0Yo z6}l@LtvEzZ(s@I7`a^OXC}22X{D5Knu#dLHc=iP?y-h7S!kidCT%@;|1LMEx5=YSi zP#*@TP5*OeQ|U~@J^Y|4nCIZAf_66lZIwNE@GuDyVb>_K2_i(Bkl;?A=DZpl0xeWH zaf)$dHCa>@lXu68-Lut62=@-sf&C={_gM2Y@+Q0>8Tt49-3~zb!lCgzLWJ=?_ht%Q z>P0zPz91hswv=#c;9qiknd---N%~EW!|6XcQr?8u$b{0>^mR_TSlHM3auSzXFT~?a zsp9^st2j8nVOP-pEW~GCZ2P7grwHb(D9J-=;Qmyu0$S*-$tB}iCRr`DWSJ6M6d8(s z%9jpNFji$nG|XwIAdE)$x#6O#JZO)&7aYEL*2{tF%hX7$s8}N|cJ!=0Djz$9zEE7P zU%imWYwnfiyn2(Re>4wNlnX}n{qwty(l2)jRQP)zQEXceY%u^({hik3!|bYI_Jo0j-L`hrlsVDdFW|@K}Vkicv*R;FyF`1)|VW?BT)X zH`cwZW~f6*rUYzBGXx9usp5YUj{RVwRO6YiF7|dt#F>ZRGgt0r%_6>Io;Ti!?fQi-k7ORN{lFivbejX zBTa}=im+m;ekXksq>P+<%~%G~LR(>nm0u%$xSC~1>@HT1%=we*Pz~!g20B}M+C7r; z%Ew&oj)sNPX&a;rYMSppyP9Ld2Jc(jNOI!9CgiG;Pk1H2M@~SMQAye(<%x~EbpqFe z-t3A2$&G9!v6U`(0(X@1q8F#hs`3RhDGxEdBIOClAP!Ra_XvWE)BESx;g60KXW8M8 zjueaX#f#mkSZ(@WC|t%Wa7jA9g}l}Sofqty_zxZ&axv=x$Ho`xsrmAOqd?CfqfH$$ z=KzRk3gk5-fGBFC#F_iew3d2l#s7860?eyiqZZXSe^Ymln^ghCsM?n>SMW%M73K?a}7%rzpjCcnKCM<+&p@C+Yl*ds2-xpI`y1>u9MjCag_M z;0fJ}Nm$83X#HvkG?_xeVnP-5#RPW~0@qvQF7tZ^)*G^?Go?9ER1nrKl2m5qg06** zk$X=CnJiHxI6Tis$-BYNx)z$cXNDKkLkpr^X`4gcBf*Twl77tM`095%4!wVKAHuY z8bQd=4msjX7|$2<3pk$ANS<*}B%NPj`lF}geP#y#c%|f&m3*=+;nQCXch9Omkoe)u zOkB^X+AkS^sN79lUn*=*VBTc*bfZOdcLZUszuIW`yUFP(x&q+c*ijC=EcV5#_28T}ksKaE9cyc=@^*$dn6~<_X*{ zFj8Q43_SG_AEcqpqwmQa0v$2wAdeTr}aelmLAc~7C?xb9`Pt8Uluph6nkftEb;x}BszX~VwjhSh-Z zIie#&p(4}0kc1-Di)kQU+&D(W;FvLG>Yt<`!LhUfdqpbuhlro@ryoZFxvs*dAgG?FY- z5mk8Kq+_IQQqb_kq~pMMCcsHqk!)2~+H?<0KyU)e!ue<299l~z)|JN5pw!d^uddF!3C!MB&%to( zxwMw9XydSff?Lvkw3Y`W8p?%T8p;LIP}T+wB`2ZlDG5`+ zR(ef`9WDKfK>j{j3u0VP=mq1aX3JU|O@|V`*9Lx?bUuRi8qcR8j={EG<&{e^TkR?7 zvHi*C0Sx#^1~6=ATVflOsKQ5>Mz)``xDnv5KCYcj+Y9i`T*S$o4YGh8ktCx8Q|T| zq~UlvAo5Fa<&wyLXlF8StLh|-M;+Rbw+f@eqrilswfTlx5g%TVPZl4Z;Vs7ZnmjMw zQV}0sny;-D@!`h&g7~3~4Nq}+>`cSZ`65?x*0YuZJNO8*UG|$1<`>Bpn*dpM*Pg~m26Kv^lkyjEIjsWfWrjA}il zHf!xZJ78y<9qr596Jk^HIMB~0i!p7e3|O3+3NFsWhkWyRnqQD_4C7y$UmAe3K7UOF zlVEbDVYM@mYp~~Mt&4p=FZQ`?7m#li(~{>GQ-SyJHTn8bSqc3vpo5%r# zdT`og!S;pJ21|s2ZBCObQ^;9}^f)HI2kC;YHOr4O1XxCT5*6@tnxCJq%GXIt3#%qD zorFJUyYoqCZwOk@2r+aao2F;u z)LR6y7>DBWR1n~$amL6FrSqdvy|!9U_-0hE87r|HUMrc%MOQ$CjO*1`BwyC=C08U* zr#U=MyC6GxAFm4Ny@1QW70G*n17v3MIxHw{U8Z{&>t_8z(o^{Wo@Gn(&G)po zLJTzw!?dM{46OPr@vQS#k10)<%VH)NjJ8HyoRPqYJkYEdU*{f{2_a;TW6cIi2w9wo zuZb}$cv2N(l!^lJQYutMj8PJ~9x-M`U5r_-VYwfgce6;vTM=WDvP~(^ z;BVM#aa|qdnK|W{c+9z%gNb9%NCJ}!!J~#H{T6ZzssM0sFEV%qr1NG?vLXFttz{B4 zFUy2lkkvzq z;h%IBvvf%7dD@E;Iu4&pKFjJ*h?FpB6DcpzFCrx=!CGv9V6?ud5GmnBRgn^_gh?b2 zJ;t%PHnuNL6kN#4^=S!WYW z2$rN#JaxtIhg=$Um|W=cjH?nJs0)&z{8f#QyRTQKVb&zoiTbDb$rqK*m4Z`qh@#IqWt=oo(GCKH%djMxb(_COK74D)OVA0OB+}scahuOp=)4!z*gj6KXR#wO(7mQLz+# zQ%a)|Urxdgb&bF$yey=`_606pUH~CpNQbnbq2@!0HStn}8Bb7MIKOzAbUp`ZM&U8( z{1JEN-zEkp9>~8Mc%^@%>~aGHd4ieDNFPu3O(&lgU_Qw@Ub=yOiaV1&tuhfY5HA>8 zK7K_~LA&JRjcm6V=kr+<*hABz0L}0(4KzQ*B19ec(HVIfa1i5n)vrF=K#}dgXWK#{ z7;U@41BaR)CW4rD_WF_E`P%m=*xLxj@!^FqLHQ3gFXT}sug`#k2saq@0U)dsY0d54 zL2Bz{_DwSK$hPx~4UhS^Eh%bQ&0o$)ad-C3P!wOoeF+W1w&pi zNda-)cz0vr*0Rwg7ZgWf+oX`~zQV0V>SQUpY`l6*u|1ogPz4?=r`;z%R7{R=6X6L8 z03|NfduU3G|IAZK&><+PGaymCPQ}>P2HJ#HV?<)`t@Cq;u;J_^CSMJf9R_osVtUo2 z^V85iOSjqwCsKwJ+JM1@H>=FH=w0AA-5nI$sBoMG_BCw7wP=IpwV)EsNuv_i#d}wx zhFmfjP4oYfGoJU?xA6;z(QTlR-T^Ec<}YkfS5<0r0~YA$lCVKXmJr`%p0~}VLB=@H zX&v#MgHJLI+P-D`02hEmE%vCHiq=R2u&5_y!cf>RTv}79*h5gtP_!!!bK?OudBQw* zR>vpk3|Azl#S}CaAw>v^^ca&A3w{YmbXrt@#06FdMK!J)M>Lm>X8xe?fRm(d9AmL znxL1Yt66tfvpKrzoqa4*fJcP;e(60x`0=bgO42XeUjlJ~Z;SFx$0^@Q$082%-K7XLmARsM!W@3nfCqTI>1cFYH_6I%%3O_%|y*+jmylbtxE zJqGBzH36(fC|pu4Oc{<|mk~!suJOZIpv_r@jG1?+jH>TwF``x`np6m4XtJzTl5|G1 zZTTj%5_jHxJco`EBA0Zu?#%TO2-a22VoHEr*13m5oQiTEr=MXj3ZP|%mQ54jybi6Jn=Kc$*z%vZ%mN)ZxBhVPY_9mLv4tp z;R%9cb{er>ledX8hpiESj1kPe5hZ$V$?ijF4PT9*@J^3rFt#M^rVe98g8)P&A z87w}NNpBPlN1A07SP8k*GBV26+CI`EUl5el-@;r9p^dkO z8oNcmqFqrV43q^dv&}``T*4EK8G4fGva^Ksi+d5fNXF*iWP{plS5+h`A|4FWB+xAM z$9Gxh?tq{)H=uc^3*|)HGAl4kXrO02!hWI-@?KUri_v-z$tymeopd)Os7>WJjiu@s zhF2GhaT0Q|IQVzsR_IygwYEg}PMZfD50%k@@oq@I{^Pr+6Zs-?wu9T}bgM_uLNb}$ z`~Ka$Nq+`9=f8;=G)~2C-zs+md@f_g02(ug#e$^>NJ}6Hv=6z{!6@&@@XEdK&e8^g zk{BA$5j>Qd{&dPO%&mE&4p`XLX0;9ObU1cz3u)(!Ej7M^GOrc)ZfA>h)tfZls4}{t zw$&|_<-_K3s>b~EmUax|;-%K7X_G-ckpb7Z5k1KX_UItVYM36@8<@((3Hg{cF9%n8 zpu*D6%P>ct$%O*Qf^r&hG2_%D7gPbtb+93t&q`FRPPl~0a>A(@FKOnsIE&P*>p@Jm zWKktT?QYI|AwcB0&$b4&OKYVN-SNxbhJ>P3pJ(;6tspoCa|N^&l-@dtz(3kXMXg|p&q)5}@yly%8jMq-;ib5$t#Ft7$IY610v!x6EcRijQbi63)L+0WLR|#n~*WA)mY~b8XA2~ z$WXX}_7W2^hF`Fdu|z@!UFZ=q@WU37nEV0Z=Mpmbw-7RngobS(^no$BXB-IGC1gk? zr=un~Es>CM9xa;e6oxgZVOh?SlM*s2!#eNz2pNMCGO$0I>}%?>Sq6}NseFuTTXC@) z<)+Z|n_BsnlxBb?H-&+KrMXy-f(B)akOXtBIrXJT=q*iPi4?xhsuOv#Ir#%H`NV8a znrQ?wy^hW?6c?x^zCcOMz<%LKcV7cFllMSAmhA}z1L4?lSfF@|F$(Rv*9=Wodk9}$ zT0x|UvXU)OAr_5B#)=V6Nl+CIaS#MpEV+mZW@*t2L?O%$NDqw+nDoz(+9?V^F$&o{ z7_d6;2OgwkfF!hC83SiMCUn+ApXLV6KsTUzih>DdEY+OFYJFK()e{I6Stx%tlX*$9 zOqbc1aaz9^lKlQ=`ayn(a)u>moRh8VU9<)&(J)Y;i>+)hW$oy!dTbADlm zolrB_1DrRJS<;d1OIT!>q|geQS|9*1cND299swFg{%H4qbWiIC+9P}N`+h;u351Hj zU=m=^!Vm+m<-C5EHjR>_qf+qvj4vAH3PD%R37TkI*Fo(Km-)hy!3VZvQ0y|F)xg*B zU}{-yHB&N62hyjkZ7jVw#axk0GKNdF&YIu{HL@=tZF?h+u?(%(TPE^tun-w0)?&u# z>%0fnf{X>e_i1+_AI=AxJG29HHwzi=#j+j|q(U;+D7`E+NiRiheDrOEi7?IPZ^ zUVuB9t=A&j*IAE(-8{p{IKeHWGQX1w4`#+eP6R442*x8V&$uTMGep5bmMA#Lgj!Rv z;)7Uk?1uOtV@0E4{qp98a;n=dp;nRcw}5GP{PkEhAV=hME( z3LyRba64O{*wmsoVe-(Ti>RRxr>RaY1G+qY(^`xGvczDRvLWpp9DSHZE-cTnZI^5RLUo zjcat?eN!^Jway1WIf*#CZ_pgDZeylH(%7vlx{el z|EIcH5uSX>Z<-sEk>BlWICOx`NfM6%oXAU@0>nCG6lus74(Y(87{0Po2Y zplYX=z6hWOriYOK1k^m+l-w%kG&z;>59O73pcxi&n{R!?)AhRJccaAkkd|t)aOf6_ zXnMdC)u$#Jz)_!sb2c#n*+-Zza{_NGQcLwK0{%&fcKTqVNf7r?szdoUSKdEc2Gx)P zy+B%Ol(D6BGPKuDi&eC8qBV4)t*SICwrr`Ow5QWw)fe4Mgz57H=*0=M`*f|KY^Pa4 z4R^)}p|4_}3K%^02fpEXD$RsWAXC$D36Qa|&FT;FFf+}|jCh!&K0}gd(qr0^p`D3* zZ`jf|Utm+cJF8kkRpy>4^*n(>VmE7bhD9(E|HIS1&iR}_;?`zNWCNMJ#lXn0JF85C z5sLLW@C~IbmZ%Y+quh!ZF~KHBwpc^SgqWgf^M9nxEy>7bs#1{boNrZbO30XbqVqF9 zb=tPZ6@9e1nQk`^H8*!A&Q7R*t}(tssJ(>h8BL7Uf)uuE#IJ3{1wvi-dcNg7tK^~Pm(%7~gqM)7-P@XhZx(CDf9oHJD{3HitsLomD~D#x zqiZG5O`~h&sJj(qzH*?3`u+)&UK}l~ikxOl%gU7lb3s@+ME3N!SUH3@+!rf{7L|A^ z8kVG$BQ>@z{vHO1B(L2fL_lstsK7RiLlh0 z{x_ad?l-wsjD^jV;P%eI5Z>Hp$om=H-jNb<_WP=50H)p*N-W2J;!9?nFjh+!%f(0V zr{LkVozVY&R)!>cwQ|rbwUW$B%0a~CPitk}JuWhR)%jr{ClSuS^Cb}Ji2m5ZX^W6~ z+R+6y9>-jCxEiJ*LMxsR=w8%N462J-^eq*#wu=gBT*4PcVJrp|x&8Q}#*(Z>EpK2h z2+NxM3;(09U{xU~ia?pxb;&G@;RT{GMzVZ5!v(9N( z4<;oox(BF)et%Hm@>%ChNOq#Hc4hEJBZv4FkHS-Mv7|Ghc`vcfRK0qPRC0+$ zkreUmCh!S6Hf}83+^D^!kA(~;SO)z`KtsK76Pq@$B3VU+i~*r41_(;3!YAjJG2WF1 zf?!=R(GxkY;;xR$UWlGa=Pw}(8U3^^1|6Ul$h4>pu~hP3+z^`^p^_nYutR$>wwz4nBGH=Do{wkK0-9Zmz zwlVHYapHPnI{7S@ni&bq0<`)W55cL}^hJxYT`LY|RP`&oO?{0iB9zO_>*^ePLW6`2 z?qf^5hoag;MpHINX;kOPNZ~uGuF^Svs&ho1gu2@VsS6C$cU76e>y48;uIp1IQE29IrJdoxyHM{^)%*N69bhR*&Q;xjFgD`*vTMB=n%Kv+_;G zOK}P8B%SjLm%x58&XeS6(YZ0sTfyn#7Ds1XdjELz9nn@C_a$!PDhox&hyKM%$s-1guaqUK;F#_Tq0SaQ@Nv0qkQDot3H309!2l!%h z%JdEUG#g!+Rh=EQ&FSCSyz0?O6j{M8@4qtr7ojezemHo)3m-_cR2^fP5M!savyMOo|2@ibrFg4+^$<^-m_`7s0fgX=}bQX>nQ3sw# ziXJ^iTox<1hc0l-yQ-QGuhoZR`p`Zeyuo@gej}&+#`3}N!yDcg%BhcocGHcg#m7S^ z4CR}7O83M0$h$a9rVJkd;OWMpJz?1;ib1@5s<>3i6X=h<>I`*tgu!VQm}gUGgxIIG z#y`ROcy8-68lNxIUHZ&@wsv>vGaQO^IA|j&9A2@LCa&aE^qHTwH|zS$zm8=UedZTq zSw)}ul~~rR&pcD=Gam?3_O zqXk33=J(M<^`AY5<<0Lq)cL>XQFcaOI~rE3gX0Bs-7U`)ab#ogS;} zPLDHBK!%xllzoW9y7)|?I{_y6V$hu`V1nhE?u0{Lm+myjprt$6prh_IV7elak!adS z56kAIf61_-?&L%NP^H9&{!3+vhW-JZ*~nHJhW^3uaPdPAx>I%NL3i?@2i@s()SbZd zIdmr*G`dq|rnQ>*pk;G|;(G_pV_t&Z!~PkNf1=Qyu8}^k1Prb+Jz1zs8k@+|hRocc zGFhGsik{SrtKZ)A2h!$8bp&5B^uQajl2~!R+*2`>O})|9Sq^^0>3FftN+APT1{|qM zO%2IpbNU-bYxl_!seKN12_MpyjAz1FZr;VC)qa+Yh+lE{oG;H{NtNSjx59A_QJ>W_;tE>)lBAvY;3SX6=ilZ- zQqD&8-L-l!+Wj4CmqLm56GdQ1-_nS>I*Kkx8BOOyUFsQ0#*C>Cn{LJ{w5RROz&+sYl@x3y#Bn2cCdhAw{$_pu2i28kkEA4#4`&SZG=Tq#o)m2sO2+TN# zVn?u;Ud7H5h?R;RUNwtW>`-C7Tg&EF?5tOXVrOrw3j}5G9WWHTWm4=EjGDrr*y;31 zsn|VR-vK*WSL}Xt(TW{WpZoLWFHW&r{yY^s@kc2E-$IHVYXw@FDRwscZwbY&d(hut z#ST47Se7qcP_ff=yj+SMyLvBFvD4XQDR#|@Vh7_j#jcr)zw&|{PhQ!AOABBG*ZayA zTu|&dIEQCe&D;O)g6^H!?!1V_p>*M%ux{aD<%RR5UuUc z;OpK*7v}=Bs(eYu9W1_Z^TP#GhFye?xLjZ7G|7>>G%kkiO zxw+t|wTAWahQcA_4Q{WRH*P;>w_JRp zaM)x%9<{wtDZMfUgDnPDIbzeqqgl%|{YNs+*RutzEGWb8A}9bqY~N1kNOrkB8kKcF z_XXS{96Qm4!@twN+Aro{e?Dyc#pLg!wbO*2&4=El@zMVYACAliA8n1wh%uZ`V1=*( zwwQIxyY?kPYYDCMuCQ@Es$F94)n6GX{D-GRDNy-aEs}MCl(E}FO1CkD>F<1T9?z4? zn1wWo{72Fo&vFHpTbY?To{^0jv&Pf<9rwfxEU(&y#^Pb08SRv?-)w%dGIl6}wtE0u z^5N1iR*UcYrj;f!o?`x~jcbv@^ z&-b-e=sR~-==hLywJD#fP{~XJ_X)F7WvCO+@WkMv|_hto9*W-S;%59%q<m* ztD@OJkarcRlz2|Efk4Z_3)pIq0Rn`U0ivwdY#@}%0s$RI8^}o(L&fNLPQYN3``NN* zM9`yN9At^@?}1#qH@8Ee2sK{@imXcseg#?icI($?RlG@88mEo`caQVe*<+9X2|mu>td#gTf2S;wO!TY1 zNgu}f@4~~m$BA?n0*KhW-ruK<5u0BTA;hT9M8Q^@<_nkMQ1pOC@hrpi5%~VM2dQKK zn|d@b{uTd;?#g|!!RW3GA{i3nt{mEa02n_1wq+7|PHe((5|$W-MK^F)HYC&|Wo)z| z4dAX^9a?v=J_e_#g0hw1)Dnq7780|SEYHPxgRLY;ao@bPyXb(45}Fp1MbTD(J__vz zO-@BKd7)0s@hs3;RNHu9ehv@J9@W;j#tZvNzh&D2d&OFmJWpXJtM16j$omHeOH+zp z&1X7iFh!@ellxFfRBX|LIiN4FF z)Eu%x%R1x9nw5dFNQ(A$#+ntDO{(ZR+s`H;U(hnA`ismrbXb{hth||LmWBXZN|!=w zW|i{f>e9&Q*}zAzN#X`fxUPw!KkRF zI4~LBF%SyuE3`1;gK$(>M+LjJurgr`V~&Qd52f6MgTh9aVr13GP=pq!23G_V6E#hn zJXu8$4p)HA!VO-$q08;7^A753tGm-@`MHGpzZGX0*u`1XXT8ESx|xQZQm8VGscu^= zYfQAo;xiL%vGSp?WSQb1H%Kyhq_D+4U9-jB=(bpA8XuXDX~=PP0gsu0`lc2c(~x3f z`LV<_VyqkQP&{z#hA$Ac$MEGVh`8UTFHoTRDHV3pFY$5RG&=wv#0G@8otCT zf+nb{JI{HX4DlQOQv^(8EiO(LJ%=ORBh5!1dU6jK?*`%?dXz zg`52$tZ-Iv50*e~d6$6z#;lFR6V7Yolz^q@nz+^4|@qI4iK1 z4~T=a-?>z$fW5!$v8%L}O3_o`P6MUcIt_fdavAuV0ek=gTc?;03Yj1WBX&6cR26Z{ z8aV>14F+~8An+>^k<3F@fcH$rHuqpGtC;3a#WJ@=n7K}z&P-hOQyeNb3m3%|1DA?> z55hAw9F=d}=ci0`l!Ng^HojD{-!kl5{Pozk(7Ld1t*F_zq#gGY3%zdN5@vlLgZBIt zp1Z=#TZo3&_l7V=DVLOu39G?uS7dV5h+y85>5C^-7as+;KuDLRD26bWlTazAm(^M% zQf5WwEetglMFR@u3=0+@Y+?FEvwl1)v;}#c%Pd5iZOmKh7KbmQ)vX!_)p}LLLAAHl zg_Uk$ql#IqPPq7-=Y{G4xF&ISSZ6mF?1tnCckyxa*5>4?c&dG%Gv$X2%iK2-UndJbYShK52ETz2YRslhUwaWKd=H zi6=20=XB9w4e|?FJjJfy6GTkyfdP{V3ZQiqCoy=d95IIy<{GiaE8A?JHc{v|O^2b= zK{_i8#zJDOi*B5$cl1kuUcFBjzc9^8F0Lkjoz+kyE69hTDql^ZBrc1EZV2t2zCk2QGVHpj+ltH>~ zB*8dpMiLDOx;7)pid^LT{EQ?opM(@vz!0s^4I>2BdLar>j4po`1*o6VFY-(`J1aUV zP?$q3c>tFDrHZ^!$@-9tJdyQSK@4xOAy@zgx9le{faEYa6We`<sUD-&SB6aWD+ z@Y4rC3wZaBilP{MOPd{ig89HV+8-HGug_ zU!Z6oARcrR5sv}a5w9{(hqyjhq<9TR6e9%VtHa8MqTmqLg@80=U7ofjB26;#@iYWh ztWQQbsU0Vyiw`il@B#^mb#e%lFMMtb;SJM4JCUjw(R+UI-a*pT3OAJK28pI5U1m?{ zPbvd_)lG!GZ6zab4n0R?v6lEI+fCgpXoMU$__g~vFvl7Rs;CeAK#8Iw0edtN;)2A7 zweQjx8DX@dxaQxGWHw&^ZRPs8%JpA!{rU4*piZ7Vos4|!{3S_}jPlpV-ynZO{0;NB zFTeTN>}~r_oH%&&mi)x*zJn+7gSXv&WahS+qbK&AIC$)6K09;b&e@|g2l9PK^O@P% zW3zP2|CjQkI;(Y$aX-%A1b_STHyk{E`;mR`JZqKtokwTxzI|r@iJ1dO-q}KgJ=56_1`;Oc>b4A{y{WxBh zsbm>{%lXUozyCAe$Unmyhu@XIYjSdU`2U!?dunQG%hcAXZByH)c1-P@nx5J=N z(>r$UxP0f-&MiB)?%cL>`_3IZckZ0txohX;(^J!1rngRSo8CUXV|wTG^z^Rj%Xdxf z+Oliwu5G)v@7l3z=dS5pyLMfEIbFP*<}at}%lYhbiqdcL{{Zoe{9m@<{~BHY>(l=# zEx!5a{+ayNeaG|L_Z>aB|M1L#V+UsU?cIO#Eqia@xBu|ITW0q1@YcPDXWqH@=)T)# zil^Y#v3FSUvAe+Xg9pk-2aoT)^Y&Y2_Z`^x_9HWU_wC<5bNu+;6UPqE996qR!n7p0 zw$V(4`}_CJ-g0d3?RU-|pV_;2|FNTx)czBDZ`*h9s23iGs(3PU7xZJ9bX*x_sZ;_aB(K`QXtLGqXqc z9dVX$Mb2<$W}(hIkIv5Q+kdM-mG3`xU}oR(<1@3yM{eGC5c1{C1GmgvcI(W+TW&p( zUv*V}eCEi_@pe#w`< zd*}Y-$jr^p^NX^P*InhU^wtb3-G8eX>HfoeHPXEY58Mr_5!bn%2H!mU7WMRsD|9o- zvkm!Gugmog{nbEXuQv96VqbQDQ}hXD*4vw-Gw&#BtU~0wPu#rAxGLQ8-~rGZd^X-L z5MDET?6%^Cx4-km3^ND=7JL1!W4F!RdHmo%o5@8bH$yV{J8lK&u;%@D9$~#;5qbO0 z?YZN#aO9Sm*@fP{^X8j*K{Z}?)cTTt_l?)=0^d-&6XCIF`!)O>pE-H}1QXfp{dQGs zS9`QF+0}Q=oal&z_d);FYkzv((fy$1@tNY4o&taG4KrQu^%VGfH_q-mdYqou?*zE` zMA!cG^|$UjdJBEpf9LGM6Ys3w?K^PW!K24#_V0yacnLmn``h;&(HM@WN!r=-`ZrvC z%^R=1?w`Er`fvY^H{bA`Z~3kpzx#Xs=}q7JR;R&RZasMD@R8e&9=rXH+2befyz3oz zzw@7!bb9G#?GP;Tf3M@cZ*%&-;>y=v6~0Q+tkKs$uw-y(cw}^Je4@E@+42=D&pUtB zYJXwP+I1IP_^Q0se)al`F1}=P!)q?xxaqRZ>cS$MeR*Fqo^HTI7U|_fM-SxCF${V3 zM1JwbaIga+5m2}H-D5Z3e0=6a-nuHE(yQTizPF8>7vMg}XZK%5Gn3H%Rr!JAC;VoP zRxA8KT{Xtm`x;f{Fh(tB$KQF|+u@5u3kM-QGjxbMi`<9FVE`;m9vK6~u; zW2{Qz31Hm`7BVr)eYfe~{kI-FxPRsZ5PN4pMX=Hk)=+fsk%Pxi=%blgyF3anu!|GN z_$D+EFn8~tJGW3zj2nLT#r?OKhi@}AZ&tz%l>;u;sPB?HIn bg?{I&^W`qbGyA6y-d(^*l9P1T{`da_#hisw diff --git a/examples/src/features/livenet.rs b/examples/src/features/livenet.rs index b9dcd591..490f3de2 100644 --- a/examples/src/features/livenet.rs +++ b/examples/src/features/livenet.rs @@ -59,3 +59,37 @@ impl LivenetContract { .transfer(&self.env().caller(), &1.into()); } } + +#[cfg(test)] +mod tests { + use crate::features::livenet::{LivenetContractHostRef, LivenetContractInitArgs}; + use alloc::string::ToString; + use odra::host::{Deployer, HostRef}; + use odra_modules::erc20::{Erc20HostRef, Erc20InitArgs}; + + #[test] + fn livenet_contract_test() { + let test_env = odra_test::env(); + let mut erc20 = Erc20HostRef::deploy( + &test_env, + Erc20InitArgs { + name: "TestToken".to_string(), + symbol: "TT".to_string(), + decimals: 18, + initial_supply: Some(100_000.into()) + } + ); + let mut livenet_contract = LivenetContractHostRef::deploy( + &test_env, + LivenetContractInitArgs { + erc20_address: *erc20.address() + } + ); + + erc20.transfer(livenet_contract.address(), &1000.into()); + + livenet_contract.push_on_stack(1); + assert_eq!(livenet_contract.pop_from_stack(), 1); + livenet_contract.mutable_cross_call(); + } +} diff --git a/examples/src/features/native_token.rs b/examples/src/features/native_token.rs index 68f1f8bf..8a9703e0 100644 --- a/examples/src/features/native_token.rs +++ b/examples/src/features/native_token.rs @@ -32,7 +32,7 @@ mod tests { }; #[test] - fn test_modules() { + fn test_public_wallet() { let test_env = odra_test::env(); let mut my_contract = PublicWallet::deploy(&test_env, NoArgs); let original_contract_balance = test_env.balance_of(&my_contract); diff --git a/justfile b/justfile index cd3d2491..ef4b4ee3 100644 --- a/justfile +++ b/justfile @@ -6,16 +6,16 @@ BINARYEN_CHECKSUM := "c55b74f3109cdae97490faf089b0286d3bba926bb6ea5ed00c8c784fc5 default: just --list +# TODO: reenable this cd odra-casper/proxy-caller && cargo clippy --target=wasm32-unknown-unknown -- -D warnings -A clippy::single-component-path-imports clippy: cargo clippy --all-targets -- -D warnings - cd odra-casper/proxy-caller && cargo clippy --target=wasm32-unknown-unknown -- -D warnings -A clippy::single-component-path-imports cd examples && cargo clippy --all-targets -- -D warnings cd modules && cargo clippy --all-targets -- -D warnings cd benchmark && cargo clippy --all-targets -- -D warnings +# TODO: reenable this cd odra-casper/proxy-caller && cargo fmt lint: clippy cargo fmt - cd odra-casper/proxy-caller && cargo fmt cd examples && cargo fmt cd modules && cargo fmt cd benchmark && cargo fmt @@ -97,17 +97,17 @@ test-templates: just test-template cep78 run-nctl: - docker run --rm -it --name mynctl -d -p 11101:11101 -p 14101:14101 -p 18101:18101 makesoftware/casper-nctl:v155 + docker run --rm -it --name mynctl -d -p 11101:11101 -p 14101:14101 -p 18101:18101 casper-nctl:feat-2.0 test-livenet: set shell := bash mkdir -p examples/.node-keys cp modules/wasm/Erc20.wasm examples/wasm/ # Extract the secret keys from the local Casper node - docker exec mynctl /bin/bash -c "cat /home/casper/casper-node/utils/nctl/assets/net-1/users/user-1/secret_key.pem" > examples/.node-keys/secret_key.pem - docker exec mynctl /bin/bash -c "cat /home/casper/casper-node/utils/nctl/assets/net-1/users/user-2/secret_key.pem" > examples/.node-keys/secret_key_1.pem + docker exec mynctl /bin/bash -c "cat /home/casper/casper-nctl/assets/net-1/users/user-1/secret_key.pem" > examples/.node-keys/secret_key.pem + docker exec mynctl /bin/bash -c "cat /home/casper/casper-nctl/assets/net-1/users/user-2/secret_key.pem" > examples/.node-keys/secret_key_1.pem # Run the tests - cd examples && ODRA_CASPER_LIVENET_SECRET_KEY_PATH=.node-keys/secret_key.pem ODRA_CASPER_LIVENET_NODE_ADDRESS=http://localhost:11101 ODRA_CASPER_LIVENET_CHAIN_NAME=casper-net-1 ODRA_CASPER_LIVENET_KEY_1=.node-keys/secret_key_1.pem cargo run --bin livenet_tests --features=livenet + cd examples && ODRA_CASPER_LIVENET_SECRET_KEY_PATH=.node-keys/secret_key.pem ODRA_CASPER_LIVENET_NODE_ADDRESS=http://localhost:11101 ODRA_CASPER_LIVENET_EVENTS_URL=http://localhost:18101/events ODRA_CASPER_LIVENET_CHAIN_NAME=casper-net-1 ODRA_CASPER_LIVENET_KEY_1=.node-keys/secret_key_1.pem cargo run --bin livenet_tests --features=livenet rm -rf examples/.node-keys run-example-erc20-on-livenet: diff --git a/modules/Cargo.toml b/modules/Cargo.toml index 8eac1c98..6cd01350 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "odra-modules" -version = "1.3.0" +version = "1.1.0" edition = "2021" authors = ["Jakub Płaskonka ", "Krzysztof Pobiarżyn ", "Maciej Zieliński "] license = "MIT" @@ -9,23 +9,23 @@ description = "Collection of reusable Odra modules." keywords = ["wasm", "webassembly", "blockchain"] [dependencies] -odra = { path = "../odra", version = "1.3.0", default-features = false } -serde = { version = "1.0.80", default-features = false } -serde_json = { version = "1.0.59", default-features = false } -serde-json-wasm = { version = "1.0.1", default-features = false } -base16 = { version = "0.2.1", default-features = false } -base64 = { version = "0.22.0", default-features = false, features = ["alloc"] } +odra = { path = "../odra", version = "1.1.0", default-features = false } +serde = { workspace = true, default-features = false } +serde_json = { workspace = true, default-features = false } +serde-json-wasm = { workspace = true } +base16 = { workspace = true } +base64 = { workspace = true, features = ["alloc"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -odra-build = { path = "../odra-build", version = "1.3.0" } +odra-build = { path = "../odra-build", version = "1.1.0" } [dev-dependencies] -odra-test = { path = "../odra-test", version = "1.3.0" } +odra-test = { path = "../odra-test", version = "1.1.0" } once_cell = "1" blake2 = "0.10.6" [build-dependencies] -odra-build = { path = "../odra-build", version = "1.3.0" } +odra-build = { path = "../odra-build", version = "1.1.0" } [features] default = [] @@ -41,12 +41,6 @@ name = "odra_modules_build_schema" path = "bin/build_schema.rs" test = false -[profile.release] -codegen-units = 1 -lto = true - -[profile.dev.package."*"] -opt-level = 3 - [lints.rust] missing_docs = "warn" + diff --git a/modules/src/cep18_token.rs b/modules/src/cep18_token.rs index ae039846..e0b5ffd2 100644 --- a/modules/src/cep18_token.rs +++ b/modules/src/cep18_token.rs @@ -365,7 +365,7 @@ pub(crate) mod tests { use crate::cep18::utils::Cep18Modality; use odra::casper_types::account::AccountHash; - use odra::casper_types::ContractPackageHash; + use odra::casper_types::contracts::ContractPackageHash; use odra::host::{Deployer, HostEnv, HostRef}; use odra::Address; diff --git a/odra-casper/livenet-env/Cargo.toml b/odra-casper/livenet-env/Cargo.toml index 41f4d680..7122a68b 100644 --- a/odra-casper/livenet-env/Cargo.toml +++ b/odra-casper/livenet-env/Cargo.toml @@ -10,7 +10,7 @@ repository = { workspace = true } [dependencies] odra-core = { workspace = true } -odra-casper-rpc-client = { workspace = true } +odra-casper-rpc-client = { workspace = true, features = ["std"]} blake2 = { workspace = true } log = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread"]} diff --git a/odra-casper/livenet-env/src/livenet_host.rs b/odra-casper/livenet-env/src/livenet_host.rs index c5041f8f..0e01150d 100644 --- a/odra-casper/livenet-env/src/livenet_host.rs +++ b/odra-casper/livenet-env/src/livenet_host.rs @@ -1,4 +1,5 @@ //! Livenet implementation of HostContext for HostEnv. + use crate::livenet_contract_env::LivenetContractEnv; use odra_casper_rpc_client::casper_client::CasperClient; use odra_casper_rpc_client::log::info; @@ -13,6 +14,8 @@ use odra_core::{ }; use odra_core::{prelude::*, EventError, OdraResult}; use odra_core::{ContractContainer, ContractRegister}; +use std::fs; +use std::path::PathBuf; use std::sync::RwLock; use std::thread::sleep; use tokio::runtime::Runtime; @@ -153,10 +156,16 @@ impl HostContext for LivenetHost { entry_points_caller: EntryPointsCaller ) -> OdraResult

{ let timestamp = Timestamp::now(); + let wasm_path = find_wasm_file_path(name); + let wasm_bytes = fs::read(wasm_path).unwrap(); let address = { let mut client = self.casper_client.borrow_mut(); let rt = Runtime::new().unwrap(); - rt.block_on(async { client.deploy_wasm(name, init_args, timestamp).await })? + rt.block_on(async { + client + .deploy_wasm(name, init_args, timestamp, wasm_bytes) + .await + })? }; self.register_contract(address, name.to_string(), entry_points_caller); Ok(address) @@ -209,3 +218,21 @@ impl HostContext for LivenetHost { rt.block_on(async { client.transfer(to, amount, timestamp).await }) } } + +fn find_wasm_file_path(wasm_file_name: &str) -> PathBuf { + let mut path = PathBuf::from("wasm") + .join(wasm_file_name) + .with_extension("wasm"); + let mut checked_paths = vec![]; + for _ in 0..2 { + if path.exists() && path.is_file() { + info(format!("Found wasm under {:?}.", path)); + return path; + } else { + checked_paths.push(path.clone()); + path = path.parent().unwrap().to_path_buf(); + } + } + odra_casper_rpc_client::log::error(format!("Could not find wasm under {:?}.", checked_paths)); + panic!("Wasm not found"); +} diff --git a/odra-casper/proxy-caller/bin/proxy_caller.rs b/odra-casper/proxy-caller/bin/proxy_caller.rs index d7518628..e51a0211 100644 --- a/odra-casper/proxy-caller/bin/proxy_caller.rs +++ b/odra-casper/proxy-caller/bin/proxy_caller.rs @@ -15,7 +15,7 @@ use odra_casper_wasm_env::casper_contract::contract_api::runtime; fn call() { let proxy_call = ProxyCall::load_from_args(); let _: () = runtime::call_versioned_contract( - proxy_call.contract_package_hash, + proxy_call.package_hash, None, proxy_call.entry_point_name.as_str(), proxy_call.runtime_args diff --git a/odra-casper/proxy-caller/bin/proxy_caller_with_return.rs b/odra-casper/proxy-caller/bin/proxy_caller_with_return.rs index 8bad92c8..11c142cf 100644 --- a/odra-casper/proxy-caller/bin/proxy_caller_with_return.rs +++ b/odra-casper/proxy-caller/bin/proxy_caller_with_return.rs @@ -10,15 +10,18 @@ extern crate alloc; use odra_casper_proxy_caller::{ call_versioned_contract_ret_bytes, ensure_cargo_purse_is_empty, set_key, ProxyCall }; +use odra_casper_wasm_env::host_functions::revert; use odra_core::casper_types::bytesrepr::Bytes; +use odra_core::casper_types::contracts::ContractPackageHash; use odra_core::consts::RESULT_KEY; use odra_core::prelude::*; #[no_mangle] fn call() { let proxy_call = ProxyCall::load_from_args(); + let cph = ContractPackageHash::new(proxy_call.package_hash.value()); let result: Vec = call_versioned_contract_ret_bytes( - proxy_call.contract_package_hash, + cph, proxy_call.entry_point_name.as_str(), proxy_call.runtime_args ); diff --git a/odra-casper/proxy-caller/src/lib.rs b/odra-casper/proxy-caller/src/lib.rs index 2ef76248..9a499445 100644 --- a/odra-casper/proxy-caller/src/lib.rs +++ b/odra-casper/proxy-caller/src/lib.rs @@ -2,7 +2,6 @@ #![doc = "It allows calling other contracts and saving the return values to the named key"] #![doc = "of the Proxy Caller."] #![no_std] - extern crate alloc; use core::mem::MaybeUninit; @@ -16,21 +15,22 @@ use odra_casper_wasm_env::casper_contract::{ ext_ffi, unwrap_or_revert::UnwrapOrRevert }; +use odra_core::casper_types::contracts::{ContractPackageHash, ContractVersion}; use odra_core::casper_types::{ api_error, bytesrepr::{Bytes, FromBytes, ToBytes}, - ApiError, CLTyped, ContractPackageHash, ContractVersion, RuntimeArgs, URef, U512 + ApiError, CLTyped, PackageHash, RuntimeArgs, URef, U512 }; use odra_core::consts::{ - ARGS_ARG, ATTACHED_VALUE_ARG, CARGO_PURSE_ARG, CARGO_PURSE_KEY, CONTRACT_PACKAGE_HASH_ARG, - ENTRY_POINT_ARG + ARGS_ARG, ATTACHED_VALUE_ARG, CARGO_PURSE_ARG, CARGO_PURSE_KEY, ENTRY_POINT_ARG, + PACKAGE_HASH_ARG }; use odra_core::prelude::*; /// Contract call definition. pub struct ProxyCall { /// Contract package hash. - pub contract_package_hash: ContractPackageHash, + pub package_hash: PackageHash, /// Entry point name. pub entry_point_name: String, /// Runtime arguments. @@ -42,7 +42,7 @@ pub struct ProxyCall { impl ProxyCall { /// Load proxy call arguments from the runtime. pub fn load_from_args() -> ProxyCall { - let contract_package_hash = get_named_arg(CONTRACT_PACKAGE_HASH_ARG); + let package_hash = get_named_arg(PACKAGE_HASH_ARG); let entry_point_name = get_named_arg(ENTRY_POINT_ARG); let runtime_args: Bytes = get_named_arg(ARGS_ARG); let (mut runtime_args, bytes) = RuntimeArgs::from_bytes(&runtime_args).unwrap_or_revert(); @@ -60,7 +60,7 @@ impl ProxyCall { } ProxyCall { - contract_package_hash, + package_hash, entry_point_name, runtime_args, attached_value @@ -85,12 +85,11 @@ pub fn set_key(name: &str, value: T) { /// Customized version of `call_versioned_contract` from `casper_contract::contract_api::runtime`. /// It returns raw bytes instead of deserialized value. pub fn call_versioned_contract_ret_bytes( - contract_package_hash: ContractPackageHash, + package_hash: ContractPackageHash, entry_point_name: &str, runtime_args: RuntimeArgs ) -> Vec { - let (contract_package_hash_ptr, contract_package_hash_size, _bytes) = - to_ptr(contract_package_hash); + let (contract_package_hash_ptr, contract_package_hash_size, _bytes) = to_ptr(package_hash); let (contract_version_ptr, contract_version_size, _bytes) = to_ptr(None::); let (entry_point_name_ptr, entry_point_name_size, _bytes) = to_ptr(entry_point_name); let (runtime_args_ptr, runtime_args_size, _bytes) = to_ptr(runtime_args); @@ -185,3 +184,11 @@ fn to_ptr(t: T) -> (*const u8, usize, Vec) { let size = bytes.len(); (ptr, size, bytes) } + +/// Panic handler for the WASM target architecture. +#[cfg(target_arch = "wasm32")] +#[panic_handler] +#[no_mangle] +pub fn panic(_info: &core::panic::PanicInfo) -> ! { + core::intrinsics::abort(); +} diff --git a/odra-casper/rpc-client/Cargo.toml b/odra-casper/rpc-client/Cargo.toml index 42c86526..8229ca07 100644 --- a/odra-casper/rpc-client/Cargo.toml +++ b/odra-casper/rpc-client/Cargo.toml @@ -12,7 +12,9 @@ repository = { workspace = true } odra-core = { workspace = true } odra-schema = { workspace = true } casper-execution-engine = { workspace = true } -casper-hashing = "3.0.0" +casper-types = { workspace = true } +casper-client = { workspace = true } + jsonrpc-lite = "0.6.0" serde_json = { version = "1.0", features = ["raw_value"] } serde = "1.0" @@ -20,7 +22,6 @@ log = "0.4.20" hex = "0.4.3" reqwest = { version = "0.11.16", features = ["blocking", "json"] } datasize = "0.2.14" -schemars = "0.8.5" itertools = "0.10.5" blake2 = { version = "0.9.1", default-features = false } dotenv = "0.15.0" @@ -29,4 +30,9 @@ thiserror = "1.0.40" bytes = "1.5.0" anyhow = "1.0.86" humantime = "2.1.0" -tokio = { workspace = true } \ No newline at end of file +tokio = { workspace = true, features = ["rt-multi-thread"]} +base16 = { workspace = true } + +[features] +default = [] +std = ["casper-types/std-fs-io"] \ No newline at end of file diff --git a/odra-casper/rpc-client/resources/proxy_caller_with_return.wasm b/odra-casper/rpc-client/resources/proxy_caller_with_return.wasm deleted file mode 100755 index 05ee8ce2d97d35ffcbd72df74b63cc80817cc4d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35022 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 Cc>75J diff --git a/odra-casper/rpc-client/src/casper_client.rs b/odra-casper/rpc-client/src/casper_client.rs index e9727174..48b944bb 100644 --- a/odra-casper/rpc-client/src/casper_client.rs +++ b/odra-casper/rpc-client/src/casper_client.rs @@ -1,11 +1,9 @@ //! Client for interacting with Casper node. + use std::collections::BTreeMap; +use std::time::Duration; use std::time::SystemTime; -use std::{fs, path::PathBuf, str::from_utf8_unchecked, time::Duration}; -use anyhow::Context; -use casper_execution_engine::core::engine_state::ExecutableDeployItem; -use casper_hashing::Digest; use itertools::Itertools; use jsonrpc_lite::JsonRpc; use odra_core::OdraResult; @@ -13,26 +11,26 @@ use serde::de::DeserializeOwned; use serde_json::{json, Value}; use crate::casper_client::configuration::CasperClientConfiguration; -use crate::casper_node_port::query_balance::{ - PurseIdentifier, QueryBalanceParams, QueryBalanceResult, QUERY_BALANCE_METHOD -}; -use crate::casper_node_port::rpcs::StoredValue::*; -use crate::casper_node_port::{ - rpcs::{ - DictionaryIdentifier, GetDeployParams, GetDeployResult, GetDictionaryItemParams, - GetDictionaryItemResult, GetStateRootHashResult, GlobalStateIdentifier, PutDeployResult, - QueryGlobalStateParams, QueryGlobalStateResult - }, - Deploy, DeployHash -}; + use crate::log; -use anyhow::Result; +use anyhow::{Context, Result}; +use casper_client::cli::{ + get_balance, get_deploy, get_dictionary_item, get_entity, get_state_root_hash, + query_global_state, DictionaryItemStrParams +}; +use casper_client::rpcs::results::{GetDeployResult, GetDictionaryItemResult, PutDeployResult}; +use casper_client::rpcs::DictionaryItemIdentifier; +use casper_client::Verbosity; +use casper_types::bytesrepr::deserialize_from_slice; +use casper_types::execution::ExecutionResultV1::{Failure, Success}; +use casper_types::StoredValue::CLValue; +use casper_types::{execution::ExecutionResult, EntityAddr}; +use casper_types::{Deploy, DeployHash, ExecutableDeployItem, StoredValue, TimeDiff, Timestamp}; use odra_core::casper_types::{sign, URef}; use odra_core::{ casper_types::{ bytesrepr::{Bytes, FromBytes, ToBytes}, - runtime_args, CLTyped, ContractHash, ContractPackageHash, ExecutionResult, - Key as CasperKey, PublicKey, RuntimeArgs, SecretKey, TimeDiff, Timestamp, U512 + runtime_args, CLTyped, PublicKey, RuntimeArgs, SecretKey, U512 }, consts::*, Address, CallDef, ExecutionError, OdraError @@ -46,6 +44,8 @@ mod error; pub const ENV_SECRET_KEY: &str = "ODRA_CASPER_LIVENET_SECRET_KEY_PATH"; /// Environment variable holding an address of the casper node exposing RPC API. pub const ENV_NODE_ADDRESS: &str = "ODRA_CASPER_LIVENET_NODE_ADDRESS"; +/// Environment variable holding the URL of the events stream. +pub const ENV_EVENTS_ADDRESS: &str = "ODRA_CASPER_LIVENET_EVENTS_URL"; /// Environment variable holding a name of the chain. pub const ENV_CHAIN_NAME: &str = "ODRA_CASPER_LIVENET_CHAIN_NAME"; /// Environment variable holding a filename prefix for additional accounts. @@ -81,14 +81,52 @@ impl CasperClient { /// Gets a value from the storage pub async fn get_value(&self, address: &Address, key: &[u8]) -> Result { - self.query_state_dictionary(address, unsafe { from_utf8_unchecked(key) }) + Ok(self + .get_dictionary_value(address, "state", key) .await + .unwrap()) } /// Gets a value from a named key pub async fn get_named_value(&self, address: &Address, name: &str) -> Option { - let uref = self.query_contract_named_key(address, name).await.unwrap(); - self.query_uref_bytes(uref).await.ok() + let entity_hash = self + .query_global_state_for_entity_addr(address) + .await + .unwrap(); + let stored_value = self + .query_global_state(&entity_hash.to_formatted_string(), Some(name.to_string())) + .await; + match stored_value.clone() { + CLValue(value) => Some(Bytes::from(value.inner_bytes().as_slice())), + _ => { + panic!( + "Couldn't get {} from {:?}", + name, + address.to_formatted_string() + ) + } + } + } + + /// Gets a value from a result key + pub async fn get_proxy_result(&self) -> Bytes { + let stored_value = self + .query_global_state( + &self + .caller() + .as_account_hash() + .unwrap() + .to_formatted_string(), + Some(RESULT_KEY.to_string()) + ) + .await; + match stored_value { + CLValue(value) => { + let bytes: Bytes = value.into_t().unwrap(); + bytes + } + _ => panic!("Value stored in result key is not a CLValue") + } } /// Gets a value from a named dictionary @@ -99,7 +137,7 @@ impl CasperClient { key: &[u8] ) -> Option { let key = String::from_utf8(key.to_vec()).unwrap(); - self.query_dict_bytes(address, dictionary_name.to_string(), key) + self.query_dict(address, dictionary_name.to_string(), key) .await .ok() } @@ -109,11 +147,16 @@ impl CasperClient { self.gas = gas.into(); } - /// Node address. + /// Node rpc address. pub fn node_address_rpc(&self) -> String { format!("{}/rpc", self.configuration.node_address) } + /// Node address. + pub fn node_address(&self) -> &str { + &self.configuration.node_address + } + /// Chain name. pub fn chain_name(&self) -> &str { &self.configuration.chain_name @@ -174,26 +217,33 @@ impl CasperClient { /// Returns the balance of the account. pub async fn get_balance(&self, address: &Address) -> U512 { - let query_balance_params = QueryBalanceParams::new( - Some(GlobalStateIdentifier::StateRootHash( - self.get_state_root_hash().await - )), - PurseIdentifier::MainPurseUnderAccountHash( - *address - .as_account_hash() - .unwrap_or_else(|| panic!("Address {:?} is not an account address", address)) - ) - ); - let request = json!( - { - "jsonrpc": "2.0", - "method": QUERY_BALANCE_METHOD, - "params": query_balance_params, - "id": 1, + // TODO: Use rpc when it will be public to do this in one call + let main_purse = self.get_main_purse(address).await.to_formatted_string(); + get_balance( + // TODO: set rpc id to a random number + "", + &self.configuration.node_address, + self.configuration.verbosity(), + &self.get_state_root_hash().await, + &main_purse + ) + .await + .unwrap() + .result + .balance_value + } + + pub async fn get_main_purse(&self, address: &Address) -> URef { + let purse_uref = self + .query_global_state(&address.to_formatted_string(), None) + .await; + match purse_uref { + CLValue(value) => { + value.into_t().unwrap() } - ); - let result: QueryBalanceResult = self.post_request(request).await; - result.balance + StoredValue::AddressableEntity(entity) => entity.main_purse(), + _ => panic!("Not an addressable entity") + } } pub async fn transfer( @@ -214,7 +264,7 @@ impl CasperClient { let response: PutDeployResult = self.post_request(request).await; let deploy_hash = response.deploy_hash; // TODO: wait_for_deploy_hash should return a result not panic, then this function can return a result - self.wait_for_deploy_hash(deploy_hash).await; + self.wait_for_deploy(deploy_hash).await; Ok(()) } @@ -251,209 +301,109 @@ impl CasperClient { /// Get the events count from storage pub async fn events_count(&self, contract_address: &Address) -> Option { - match self - .query_contract_named_key(contract_address, "__events_length") + self.get_named_value(contract_address, "__events_length") .await - { - Ok(uref) => self.query_uref(uref).await.ok(), - Err(_) => None - } + .map(|bytes| deserialize_from_slice(&bytes).unwrap()) } /// Query the node for the current state root hash. - async fn get_state_root_hash(&self) -> Digest { - let request = json!( - { - "jsonrpc": "2.0", - "method": "chain_get_state_root_hash", - "id": 1, - } - ); - let result: GetStateRootHashResult = self.post_request(request).await; - result.state_root_hash.unwrap() + pub async fn get_state_root_hash(&self) -> String { + let digest = get_state_root_hash( + "", + &self.configuration.node_address, + Verbosity::Low as u64, + "" + ) + .await + .unwrap() + .result + .state_root_hash + .unwrap(); + + base16::encode_lower(&digest) } - /// Query the node for the dictionary item of a contract. + /// Query the node for the dictionary item of a contract or an account. async fn query_dict( &self, - contract_address: &Address, + address: &Address, dictionary_name: String, dictionary_item_key: String ) -> OdraResult { - let state_root_hash = self.get_state_root_hash().await; - let contract_hash = self - .query_global_state_for_contract_hash(contract_address) + let entity_addr = self + .query_global_state_for_entity_addr(address) .await - .map_err(|_| OdraError::ExecutionError(ExecutionError::TypeMismatch))?; - let contract_hash = contract_hash - .to_formatted_string() - .replace("contract-", "hash-"); - let params = GetDictionaryItemParams { - state_root_hash, - dictionary_identifier: DictionaryIdentifier::ContractNamedKey { - key: contract_hash, - dictionary_name, - dictionary_item_key - } + .unwrap(); + let params = DictionaryItemStrParams::EntityNamedKey { + entity_addr: &entity_addr.to_formatted_string(), + dictionary_name: &dictionary_name, + dictionary_item_key: &dictionary_item_key }; + let r = get_dictionary_item( + "", + &self.configuration.node_address, + Verbosity::Low as u64, + &self.get_state_root_hash().await, + params + ) + .await; - let request = json!( - { - "jsonrpc": "2.0", - "method": "state_get_dictionary_item", - "params": params, - "id": 1, - } - ); - let result: GetDictionaryItemResult = self.post_request(request).await; - match result.stored_value { - CLValue(value) => value - .into_t() - .map_err(|_| OdraError::ExecutionError(ExecutionError::UnwrapError)), - _ => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) - } - } - - async fn query_dict_bytes( - &self, - contract_address: &Address, - dictionary_name: String, - dictionary_item_key: String - ) -> OdraResult { - let state_root_hash = self.get_state_root_hash().await; - let contract_hash = self - .query_global_state_for_contract_hash(contract_address) - .await - .map_err(|_| OdraError::ExecutionError(ExecutionError::TypeMismatch))?; - let contract_hash = contract_hash - .to_formatted_string() - .replace("contract-", "hash-"); - let params = GetDictionaryItemParams { - state_root_hash, - dictionary_identifier: DictionaryIdentifier::ContractNamedKey { - key: contract_hash, - dictionary_name, - dictionary_item_key - } - }; - - let request = json!( - { - "jsonrpc": "2.0", - "method": "state_get_dictionary_item", - "params": params, - "id": 1, - } - ); - let result = self - .safe_post_request(request) - .await - .get_result() - .and_then(|result| { - serde_json::from_value::(result.clone()).ok() - }) - .ok_or_else(|| OdraError::ExecutionError(ExecutionError::KeyNotFound))?; - match result.stored_value { - CLValue(value) => Ok(value.inner_bytes().as_slice().into()), - _ => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) - } - } - - /// Query the contract for the direct value of a named key - pub async fn query_contract_named_key>( - &self, - contract_address: &Address, - key: T - ) -> Result { - let contract_state = self - .query_global_state_path(contract_address, key.as_ref().to_string()) - .await?; - let uref_str = match contract_state.stored_value.clone() { - Contract(contract) => contract.named_keys().find_map(|named_key| { - if named_key.name == key.as_ref() { - Some(named_key.key.clone()) - } else { - None - } - }), - _ => panic!("Not a contract") - } - .ok_or_else(|| { - anyhow::anyhow!( - "Couldn't get named key {} from contract state at address {:?}", - key.as_ref(), - contract_address - ) - })?; - URef::from_formatted_str(&uref_str).map_err(|_| anyhow::anyhow!("Invalid URef format")) + r.unwrap() + .result + .stored_value + .into_cl_value() + .unwrap() + .into_t() + .map_err(|_| OdraError::ExecutionError(ExecutionError::TypeMismatch)) } - /// Query the node for the deploy state. + /// Query the node for the transaction state. pub async fn get_deploy(&self, deploy_hash: DeployHash) -> GetDeployResult { - let params = GetDeployParams { - deploy_hash, - finalized_approvals: false - }; + let t = get_deploy( + "", + &self.configuration.node_address.clone(), + Verbosity::Low as u64, + &deploy_hash.to_hex_string(), + true + ) + .await; - let request = json!( - { - "jsonrpc": "2.0", - "method": "info_get_deploy", - "params": params, - "id": 1, - } - ); - self.post_request(request).await + t.unwrap().result } /// Discover the contract address by name. async fn get_contract_address(&self, key_name: &str) -> Address { let key_name = format!("{}_package_hash", key_name); - let account_hash = self.public_key().to_account_hash(); + + // TODO: set rpc id to a random number + let result = get_entity( + "", + &self.configuration.node_address, + Verbosity::Low as u64, + "", + &self.public_key().to_hex_string() + ) + .await + .unwrap() + .result; + let account = result.entity_result.addressable_entity().unwrap(); + + let key = account.named_keys.get(&key_name).unwrap(); + + Address::from(key.into_package_hash().unwrap()) + } + + /// Find the entity addr in global state for an address + async fn query_global_state_for_entity_addr(&self, address: &Address) -> Result { let result = self - .query_global_state(&CasperKey::Account(account_hash)) + .query_global_state(&address.to_formatted_string(), None) .await; - let key = match result.stored_value { - Account(account) => account - .named_keys() - .find(|named_key| named_key.name == key_name) - .map_or_else( - || { - panic!( - "Couldn't get contract address from account state at key {}", - key_name - ) - }, - |named_key| named_key.key.clone() - ), - _ => panic!( - "Couldn't get contract address from account state at key {}", - key_name - ) + match result { + StoredValue::Package(package) => Ok(EntityAddr::SmartContract( + package.current_entity_hash().unwrap().value() + )), + _ => Err(anyhow::anyhow!("Not a package")) } - .as_str() - .replace("hash-", "contract-package-wasm"); - let contract_hash = ContractPackageHash::from_formatted_str(&key).unwrap(); - Address::try_from(contract_hash).unwrap() - } - - /// Find the contract hash by the contract package hash. - async fn query_global_state_for_contract_hash( - &self, - address: &Address - ) -> Result { - let contract_package_hash = address - .as_contract_package_hash() - .context("Not a contract package hash")?; - let key = CasperKey::Hash(contract_package_hash.value()); - let result = self.query_global_state(&key).await; - let result_as_json = serde_json::to_value(result).context("Couldn't parse result")?; - let contract_hash: &str = result_as_json["stored_value"]["ContractPackage"]["versions"][0] - ["contract_hash"] - .as_str() - .context("Couldn't get contract hash")?; - ContractHash::from_formatted_str(contract_hash) - .map_err(|_| anyhow::anyhow!("Invalid contract hash format")) } /// Deploy the contract. @@ -461,11 +411,10 @@ impl CasperClient { &mut self, contract_name: &str, args: RuntimeArgs, - timestamp: Timestamp + timestamp: Timestamp, + wasm_bytes: Vec ) -> OdraResult
{ log::info(format!("Deploying \"{}\".", contract_name)); - let wasm_path = find_wasm_file_path(contract_name); - let wasm_bytes = fs::read(wasm_path).unwrap(); let session = ExecutableDeployItem::ModuleBytes { module_bytes: Bytes::from(wasm_bytes), args @@ -474,15 +423,19 @@ impl CasperClient { let request = put_deploy_request(deploy); let response: PutDeployResult = self.post_request(request).await; let deploy_hash = response.deploy_hash; - let result = self.wait_for_deploy_hash(deploy_hash).await; - self.process_result( + let result = self.wait_for_deploy(deploy_hash).await; + self.process_execution( result, ContractId::Name(contract_name.to_string()), deploy_hash )?; let address = self.get_contract_address(contract_name).await; - log::info(format!("Contract {:?} deployed.", &address.to_string())); + log::info(format!( + "Contract {:?} deployed.", + &address.to_formatted_string() + )); + Ok(address) } @@ -505,26 +458,32 @@ impl CasperClient { /// in under the key RESULT_KEY. pub async fn deploy_entrypoint_call_with_proxy( &self, - addr: Address, + address: Address, call_def: CallDef, timestamp: Timestamp ) -> OdraResult { log::info(format!( "Calling {:?} with entrypoint \"{}\" through proxy.", - addr.to_string(), + address.to_formatted_string(), call_def.entry_point() )); + let hash = address.as_package_hash().unwrap(); + let args_bytes: Vec = call_def + .args() + .to_bytes() + .expect("Should serialize to bytes"); + let entry_point = call_def.entry_point(); let args = runtime_args! { - CONTRACT_PACKAGE_HASH_ARG => *addr.as_contract_package_hash().unwrap(), - ENTRY_POINT_ARG => call_def.entry_point(), - ARGS_ARG => Bytes::from(call_def.args().to_bytes().unwrap()), + PACKAGE_HASH_ARG => hash, + ENTRY_POINT_ARG => entry_point, + ARGS_ARG => Bytes::from(args_bytes), ATTACHED_VALUE_ARG => call_def.amount(), AMOUNT_ARG => call_def.amount(), }; let session = ExecutableDeployItem::ModuleBytes { - module_bytes: include_bytes!("../resources/proxy_caller_with_return.wasm") + module_bytes: include_bytes!("../../test-vm/resources/proxy_caller_with_return.wasm") .to_vec() .into(), args @@ -534,27 +493,9 @@ impl CasperClient { let request = put_deploy_request(deploy); let response: PutDeployResult = self.post_request(request).await; let deploy_hash = response.deploy_hash; - let result = self.wait_for_deploy_hash(deploy_hash).await; - self.process_result(result, ContractId::Address(addr), deploy_hash)?; - - let r = self - .query_global_state(&CasperKey::Account(self.public_key().to_account_hash())) - .await; - match r.stored_value { - Account(account) => { - let result = account - .named_keys() - .find(|named_key| named_key.name == RESULT_KEY); - match result { - Some(result) => { - self.query_uref(URef::from_formatted_str(&result.key).unwrap()) - .await - } - None => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) - } - } - _ => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) - } + let result = self.wait_for_deploy(deploy_hash).await; + self.process_execution(result, ContractId::Address(address), deploy_hash)?; + Ok(self.get_proxy_result().await) } /// Deploy the entrypoint call. @@ -565,12 +506,12 @@ impl CasperClient { timestamp: Timestamp ) -> OdraResult { log::info(format!( - "Calling {:?} with entrypoint \"{}\".", - addr.to_string(), + "Calling {:?} directly with entrypoint \"{}\".", + addr.to_formatted_string(), call_def.entry_point() )); let session = ExecutableDeployItem::StoredVersionedContractByHash { - hash: *addr.as_contract_package_hash().unwrap(), + hash: addr.as_package_hash().unwrap(), version: None, entry_point: call_def.entry_point().to_string(), args: call_def.args().clone() @@ -579,69 +520,40 @@ impl CasperClient { let request = put_deploy_request(deploy); let response: PutDeployResult = self.post_request(request).await; let deploy_hash = response.deploy_hash; - let result = self.wait_for_deploy_hash(deploy_hash).await; + let result = self.wait_for_deploy(deploy_hash).await; - self.process_result(result, ContractId::Address(addr), deploy_hash) + self.process_execution(result, ContractId::Address(addr), deploy_hash) .map(|_| ().to_bytes().expect("Couldn't serialize (). This shouldn't happen.").into()) } - async fn query_global_state_path( - &self, - address: &Address, - _path: String - ) -> Result { - let hash = self.query_global_state_for_contract_hash(address).await?; - let key = CasperKey::Hash(hash.value()); - let state_root_hash = self.get_state_root_hash().await; - let params = QueryGlobalStateParams { - state_identifier: GlobalStateIdentifier::StateRootHash(state_root_hash), - key: key.to_formatted_string(), - path: vec![] - }; - let request = json!( - { - "jsonrpc": "2.0", - "method": "query_global_state", - "params": params, - "id": 1, - } - ); - Ok(self.post_request(request).await) - } - - async fn query_global_state(&self, key: &CasperKey) -> QueryGlobalStateResult { - let state_root_hash = self.get_state_root_hash().await; - let params = QueryGlobalStateParams { - state_identifier: GlobalStateIdentifier::StateRootHash(state_root_hash), - key: key.to_formatted_string(), - path: Vec::new() - }; - let request = json!( - { - "jsonrpc": "2.0", - "method": "query_global_state", - "params": params, - "id": 1, - } - ); - self.post_request(request).await + async fn query_global_state(&self, key: &str, path: Option) -> StoredValue { + // Todo: set rpc id to a random number + query_global_state( + "", + &self.configuration.node_address, + Verbosity::Low as u64, + "", + &self.get_state_root_hash().await, + key, + &path.unwrap_or_default() + ) + .await + .unwrap() + .result + .stored_value } - async fn query_state_dictionary(&self, address: &Address, key: &str) -> Result { - let state_root_hash = self.get_state_root_hash().await; - let contract_hash = self.query_global_state_for_contract_hash(address).await?; + async fn _query_state_dictionary(&self, address: &Address, key: &str) -> Result { + let contract_hash = self.query_global_state_for_entity_addr(address).await?; let contract_hash = contract_hash .to_formatted_string() .replace("contract-", "hash-"); - let params = GetDictionaryItemParams { - state_root_hash, - dictionary_identifier: DictionaryIdentifier::ContractNamedKey { - key: contract_hash, - dictionary_name: String::from("state"), - dictionary_item_key: String::from(key) - } + let params = DictionaryItemIdentifier::ContractNamedKey { + key: contract_hash, + dictionary_name: "state".to_string(), + dictionary_item_key: key.to_string() }; - + // TODO: set rpc id to a random number let request = json!( { "jsonrpc": "2.0", @@ -652,7 +564,7 @@ impl CasperClient { ); let result = self - .safe_post_request(request) + .safe_post_request(request.clone()) .await .get_result() .and_then(|result| { @@ -674,26 +586,28 @@ impl CasperClient { }) } - async fn query_uref(&self, uref: URef) -> OdraResult { - let result = self.query_global_state(&CasperKey::URef(uref)).await; - match result.stored_value { - CLValue(value) => value - .into_t() - .map_err(|_| OdraError::ExecutionError(ExecutionError::UnwrapError)), - _ => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) - } + async fn _query_uref(&self, _uref: URef) -> OdraResult { + todo!() + // let result = self.query_global_state(&CasperKey::URef(uref)).await; + // match result.stored_value { + // CLValue(value) => value + // .into_t() + // .map_err(|_| OdraError::ExecutionError(ExecutionError::UnwrapError)), + // _ => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) + // } } - async fn query_uref_bytes(&self, uref: URef) -> OdraResult { - let key = CasperKey::URef(uref); - let result = self.query_global_state(&key).await; - match result.stored_value { - CLValue(value) => Ok(value.inner_bytes().as_slice().into()), - _ => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) - } + async fn _query_uref_bytes(&self, _uref: URef) -> OdraResult { + todo!() + // let key = CasperKey::URef(uref); + // let result = self.query_global_state(&key).await; + // match result.stored_value { + // CLValue(value) => Ok(value.inner_bytes().as_slice().into()), + // _ => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) + // } } - async fn wait_for_deploy_hash(&self, deploy_hash: DeployHash) -> ExecutionResult { + async fn wait_for_deploy(&self, deploy_hash: DeployHash) -> ExecutionResult { let deploy_hash_str = format!("{:?}", deploy_hash.inner()); let time_diff = Duration::from_secs(15); let final_result; @@ -704,16 +618,16 @@ impl CasperClient { &time_diff, &deploy_hash_str )); sleep(time_diff).await; - let result: GetDeployResult = self.get_deploy(deploy_hash).await; - if !result.execution_results.is_empty() { - final_result = result; + let result = self.get_deploy(deploy_hash).await.execution_info.unwrap(); + if result.execution_result.is_some() { + final_result = result.execution_result.unwrap(); break; } } - final_result.execution_results[0].result.clone() + final_result.clone() } - fn process_result( + fn process_execution( &self, result: ExecutionResult, called_contract_id: ContractId, @@ -721,27 +635,45 @@ impl CasperClient { ) -> OdraResult<()> { let deploy_hash_str = format!("{:?}", deploy_hash.inner()); match result { - ExecutionResult::Failure { error_message, .. } => { - let (error_msg, odra_error) = - match self.find_error(called_contract_id, &error_message) { - Some((contract_error, odra_error)) => (contract_error, odra_error), - None => ( - error_message, - OdraError::ExecutionError(ExecutionError::UnexpectedError) - ) - }; - log::error(format!( - "Deploy {:?} failed with error: {:?}.", - deploy_hash_str, error_msg - )); - Err(odra_error) - } - ExecutionResult::Success { .. } => { - log::info(format!( - "Deploy {:?} successfully executed.", - deploy_hash_str - )); - Ok(()) + ExecutionResult::V1(r) => match r { + Failure { error_message, .. } => { + let (error_msg, odra_error) = + match self.find_error(called_contract_id, &error_message) { + Some((contract_error, odra_error)) => (contract_error, odra_error), + None => ( + error_message, + OdraError::ExecutionError(ExecutionError::UnexpectedError) + ) + }; + log::error(format!( + "Deploy V1 {:?} failed with error: {:?}.", + deploy_hash_str, error_msg + )); + Err(odra_error) + } + Success { .. } => { + log::info(format!( + "Deploy {:?} successfully executed.", + deploy_hash_str + )); + Ok(()) + } + }, + ExecutionResult::V2(r) => match r.error_message { + None => { + log::info(format!( + "Deploy {:?} successfully executed.", + deploy_hash_str + )); + Ok(()) + } + Some(e) => { + log::error(format!( + "Deploy V2 {:?} failed with error: {:?}.", + deploy_hash_str, e + )); + Err(OdraError::ExecutionError(ExecutionError::UnexpectedError)) + } } } } @@ -795,7 +727,7 @@ impl CasperClient { } async fn post_request(&self, request: Value) -> T { - let json = self.safe_post_request(request).await; + let json = self.safe_post_request(request.clone()).await; json.get_result() .map(|result| { serde_json::from_value::(result.clone()).unwrap_or_else(|e| { @@ -804,7 +736,7 @@ impl CasperClient { }) }) .unwrap_or_else(|| { - log::error(format!("Couldn't get result: {:?}", json)); + log::error(format!("Couldn't get result: {:?} - {:?}", json, request)); panic!("Couldn't get result") }) } @@ -825,31 +757,13 @@ impl CasperClient { } } +#[cfg(feature = "std")] impl Default for CasperClient { fn default() -> Self { Self::new(CasperClientConfiguration::from_env()) } } -/// Search for the wasm file in the current directory and in the parent directory. -fn find_wasm_file_path(wasm_file_name: &str) -> PathBuf { - let mut path = PathBuf::from("wasm") - .join(wasm_file_name) - .with_extension("wasm"); - let mut checked_paths = vec![]; - for _ in 0..2 { - if path.exists() && path.is_file() { - log::info(format!("Found wasm under {:?}.", path)); - return path; - } else { - checked_paths.push(path.clone()); - path = path.parent().unwrap().to_path_buf(); - } - } - log::error(format!("Could not find wasm under {:?}.", checked_paths)); - panic!("Wasm not found"); -} - fn put_deploy_request(deploy: Deploy) -> Value { let request = json!( { diff --git a/odra-casper/rpc-client/src/casper_client/configuration.rs b/odra-casper/rpc-client/src/casper_client/configuration.rs index 92382496..e521fbc5 100644 --- a/odra-casper/rpc-client/src/casper_client/configuration.rs +++ b/odra-casper/rpc-client/src/casper_client/configuration.rs @@ -1,13 +1,17 @@ +#[cfg(feature = "std")] use crate::casper_client::{ - ENV_ACCOUNT_PREFIX, ENV_CHAIN_NAME, ENV_CSPR_CLOUD_AUTH_TOKEN, ENV_LIVENET_ENV_FILE, - ENV_NODE_ADDRESS, ENV_SECRET_KEY + ENV_ACCOUNT_PREFIX, ENV_CHAIN_NAME, ENV_CSPR_CLOUD_AUTH_TOKEN, ENV_EVENTS_ADDRESS, + ENV_LIVENET_ENV_FILE, ENV_NODE_ADDRESS, ENV_SECRET_KEY }; +use casper_client::Verbosity; use odra_core::casper_types::SecretKey; +#[cfg(feature = "std")] use std::path::PathBuf; #[derive(Debug)] pub struct CasperClientConfiguration { pub node_address: String, + pub events_url: String, pub rpc_id: String, pub chain_name: String, pub secret_keys: Vec, @@ -15,6 +19,7 @@ pub struct CasperClientConfiguration { pub cspr_cloud_auth_token: Option } +#[cfg(feature = "std")] impl CasperClientConfiguration { pub fn from_env() -> Self { // Check for additional .env file @@ -28,8 +33,10 @@ impl CasperClientConfiguration { // Load .env dotenv::dotenv().ok(); - let node_address = get_env_variable(ENV_NODE_ADDRESS); - let chain_name = get_env_variable(ENV_CHAIN_NAME); + let node_address = Self::get_env_variable(ENV_NODE_ADDRESS); + let chain_name = Self::get_env_variable(ENV_CHAIN_NAME); + let events_url = Self::get_env_variable(ENV_EVENTS_ADDRESS); + let (secret_keys, secret_key_paths) = Self::secret_keys_from_env(); CasperClientConfiguration { node_address, @@ -37,7 +44,8 @@ impl CasperClientConfiguration { chain_name, secret_keys, secret_key_paths, - cspr_cloud_auth_token: get_optional_env_variable(ENV_CSPR_CLOUD_AUTH_TOKEN) + cspr_cloud_auth_token: Self::get_optional_env_variable(ENV_CSPR_CLOUD_AUTH_TOKEN), + events_url } } @@ -47,11 +55,11 @@ impl CasperClientConfiguration { fn secret_keys_from_env() -> (Vec, Vec) { let mut secret_keys = vec![]; let mut secret_key_paths = vec![]; - let file_name = get_env_variable(ENV_SECRET_KEY); + let file_name = Self::get_env_variable(ENV_SECRET_KEY); secret_keys.push(SecretKey::from_file(file_name.clone()).unwrap_or_else(|_| { panic!( "Couldn't load secret key from file {:?}", - get_env_variable(ENV_SECRET_KEY) + Self::get_env_variable(ENV_SECRET_KEY) ) })); secret_key_paths.push(file_name); @@ -66,18 +74,22 @@ impl CasperClientConfiguration { } (secret_keys, secret_key_paths) } -} -fn get_env_variable(name: &str) -> String { - std::env::var(name).unwrap_or_else(|err| { - crate::log::error(format!( - "{} must be set. Have you setup your .env file?", - name - )); - panic!("{}", err) - }) -} + fn get_env_variable(name: &str) -> String { + std::env::var(name).unwrap_or_else(|err| { + crate::log::error(format!( + "{} must be set. Have you setup your .env file?", + name + )); + panic!("{}", err) + }) + } -fn get_optional_env_variable(name: &str) -> Option { - std::env::var(name).ok() + fn get_optional_env_variable(name: &str) -> Option { + std::env::var(name).ok() + } + + pub fn verbosity(&self) -> u64 { + Verbosity::Low as u64 + } } diff --git a/odra-casper/rpc-client/src/casper_node_port/account.rs b/odra-casper/rpc-client/src/casper_node_port/account.rs deleted file mode 100644 index 4c8f0419..00000000 --- a/odra-casper/rpc-client/src/casper_node_port/account.rs +++ /dev/null @@ -1,38 +0,0 @@ -use datasize::DataSize; -use odra_core::casper_types::{account::AccountHash, NamedKey, URef}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// Structure representing a user's account, stored in global state. -#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, DataSize, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct Account { - account_hash: AccountHash, - #[data_size(skip)] - named_keys: Vec, - #[data_size(skip)] - main_purse: URef, - associated_keys: Vec, - action_thresholds: ActionThresholds -} - -impl Account { - pub fn named_keys(&self) -> impl Iterator { - self.named_keys.iter() - } -} - -#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, DataSize, JsonSchema)] -#[serde(deny_unknown_fields)] -struct AssociatedKey { - account_hash: AccountHash, - weight: u8 -} - -/// Thresholds that have to be met when executing an action of a certain type. -#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, DataSize, JsonSchema)] -#[serde(deny_unknown_fields)] -struct ActionThresholds { - deployment: u8, - key_management: u8 -} diff --git a/odra-casper/rpc-client/src/casper_node_port/approval.rs b/odra-casper/rpc-client/src/casper_node_port/approval.rs deleted file mode 100644 index 0a915cbe..00000000 --- a/odra-casper/rpc-client/src/casper_node_port/approval.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::fmt; - -use datasize::DataSize; -use odra_core::casper_types::{ - bytesrepr::{self, FromBytes, ToBytes}, - crypto, PublicKey, SecretKey, Signature -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use super::deploy_hash::DeployHash; - -/// A struct containing a signature of a deploy hash and the public key of the signer. -#[derive( - Clone, DataSize, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug, JsonSchema, -)] -#[serde(deny_unknown_fields)] -pub struct Approval { - signer: PublicKey, - signature: Signature -} - -impl Approval { - /// Creates an approval for the given deploy hash using the given secret key. - pub fn create(hash: &DeployHash, secret_key: &SecretKey) -> Self { - let signer = PublicKey::from(secret_key); - let signature = crypto::sign(hash, secret_key, &signer); - Self { signer, signature } - } - - /// Returns the public key of the approval's signer. - pub fn signer(&self) -> &PublicKey { - &self.signer - } - - /// Returns the approval signature. - pub fn signature(&self) -> &Signature { - &self.signature - } -} - -impl fmt::Display for Approval { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "approval({})", self.signer) - } -} - -impl ToBytes for Approval { - fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { - self.signer.write_bytes(writer)?; - self.signature.write_bytes(writer) - } - - fn to_bytes(&self) -> Result, bytesrepr::Error> { - let mut buffer = bytesrepr::allocate_buffer(self)?; - self.write_bytes(&mut buffer)?; - Ok(buffer) - } - - fn serialized_length(&self) -> usize { - self.signer.serialized_length() + self.signature.serialized_length() - } -} - -impl FromBytes for Approval { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (signer, remainder) = PublicKey::from_bytes(bytes)?; - let (signature, remainder) = Signature::from_bytes(remainder)?; - let approval = Approval { signer, signature }; - Ok((approval, remainder)) - } -} diff --git a/odra-casper/rpc-client/src/casper_node_port/block_hash.rs b/odra-casper/rpc-client/src/casper_node_port/block_hash.rs deleted file mode 100644 index 1870a93d..00000000 --- a/odra-casper/rpc-client/src/casper_node_port/block_hash.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::fmt; - -use casper_hashing::Digest; -use datasize::DataSize; -use odra_core::casper_types::bytesrepr::{self, FromBytes, ToBytes}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// A cryptographic hash identifying a [`Block`](struct.Block.html). -#[derive( - Copy, - Clone, - DataSize, - Default, - Ord, - PartialOrd, - Eq, - PartialEq, - Hash, - Serialize, - Deserialize, - Debug, - JsonSchema, -)] -#[serde(deny_unknown_fields)] -pub struct BlockHash(Digest); - -impl fmt::Display for BlockHash { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "block hash {}", self.0) - } -} - -impl From for BlockHash { - fn from(digest: Digest) -> Self { - Self(digest) - } -} - -impl AsRef<[u8]> for BlockHash { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl ToBytes for BlockHash { - fn to_bytes(&self) -> Result, bytesrepr::Error> { - self.0.to_bytes() - } - - fn serialized_length(&self) -> usize { - self.0.serialized_length() - } -} - -impl FromBytes for BlockHash { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (hash, remainder) = Digest::from_bytes(bytes)?; - let block_hash = BlockHash(hash); - Ok((block_hash, remainder)) - } -} - -/// Describes a block's hash and height. -#[derive( - Clone, Copy, DataSize, Default, Eq, JsonSchema, Serialize, Deserialize, Debug, PartialEq, -)] -pub struct BlockHashAndHeight { - /// The hash of the block. - #[schemars(description = "The hash of this deploy's block.")] - pub block_hash: BlockHash, - /// The height of the block. - #[schemars(description = "The height of this deploy's block.")] - pub block_height: u64 -} - -impl fmt::Display for BlockHashAndHeight { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - formatter, - "{}, height {} ", - self.block_hash, self.block_height - ) - } -} diff --git a/odra-casper/rpc-client/src/casper_node_port/contract.rs b/odra-casper/rpc-client/src/casper_node_port/contract.rs deleted file mode 100644 index e6034f2f..00000000 --- a/odra-casper/rpc-client/src/casper_node_port/contract.rs +++ /dev/null @@ -1,43 +0,0 @@ -use odra_core::casper_types::{ - ContractPackageHash, ContractWasmHash, EntryPoint, NamedKey, ProtocolVersion -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// Details of a smart contract. -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct Contract { - contract_package_hash: ContractPackageHash, - contract_wasm_hash: ContractWasmHash, - named_keys: Vec, - entry_points: Vec, - protocol_version: ProtocolVersion -} - -impl Contract { - /// Returns the contract package hash of the contract. - pub fn contract_package_hash(&self) -> &ContractPackageHash { - &self.contract_package_hash - } - - /// Returns the contract Wasm hash of the contract. - pub fn contract_wasm_hash(&self) -> &ContractWasmHash { - &self.contract_wasm_hash - } - - /// Returns the named keys of the contract. - pub fn named_keys(&self) -> impl Iterator { - self.named_keys.iter() - } - - /// Returns the entry-points of the contract. - pub fn entry_points(&self) -> impl Iterator { - self.entry_points.iter() - } - - /// Returns the protocol version of the contract. - pub fn protocol_version(&self) -> &ProtocolVersion { - &self.protocol_version - } -} diff --git a/odra-casper/rpc-client/src/casper_node_port/contract_package.rs b/odra-casper/rpc-client/src/casper_node_port/contract_package.rs deleted file mode 100644 index d317cd17..00000000 --- a/odra-casper/rpc-client/src/casper_node_port/contract_package.rs +++ /dev/null @@ -1,53 +0,0 @@ -use datasize::DataSize; -use odra_core::casper_types::{ContractHash, URef}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// Contract definition, metadata, and security container. -#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, DataSize, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct ContractPackage { - #[data_size(skip)] - access_key: URef, - versions: Vec, - disabled_versions: Vec, - groups: Vec, - lock_status: ContractPackageStatus -} - -#[derive( - Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, DataSize, JsonSchema, -)] -pub struct ContractVersion { - protocol_version_major: u32, - contract_version: u32, - contract_hash: ContractHash -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DataSize, JsonSchema)] -pub struct DisabledVersion { - protocol_version_major: u32, - contract_version: u32 -} - -#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, DataSize, JsonSchema)] -pub struct Groups { - group: String, - #[data_size(skip)] - keys: Vec -} - -/// A enum to determine the lock status of the contract package. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DataSize, JsonSchema)] -pub enum ContractPackageStatus { - /// The package is locked and cannot be versioned. - Locked, - /// The package is unlocked and can be versioned. - Unlocked -} - -impl Default for ContractPackageStatus { - fn default() -> Self { - Self::Unlocked - } -} diff --git a/odra-casper/rpc-client/src/casper_node_port/deploy.rs b/odra-casper/rpc-client/src/casper_node_port/deploy.rs deleted file mode 100644 index 3d531b1f..00000000 --- a/odra-casper/rpc-client/src/casper_node_port/deploy.rs +++ /dev/null @@ -1,277 +0,0 @@ -#![allow(clippy::field_reassign_with_default)] - -use std::{cell::OnceCell, cmp, collections::BTreeSet, fmt, hash}; - -use casper_execution_engine::core::engine_state::{DeployItem, ExecutableDeployItem}; -use casper_hashing::Digest; -use datasize::DataSize; -use itertools::Itertools; -use odra_core::casper_types::{ - self, - bytesrepr::{self, FromBytes, ToBytes}, - PublicKey, SecretKey, TimeDiff, Timestamp -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::casper_node_port::utils::DisplayIter; - -use super::{ - approval::Approval, deploy_hash::DeployHash, deploy_header::DeployHeader, - error::DeployConfigurationFailure, utils::ds -}; - -/// A deploy; an item containing a smart contract along with the requester's signature(s). -#[derive(Clone, DataSize, Eq, Serialize, Deserialize, Debug, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct Deploy { - hash: DeployHash, - header: DeployHeader, - payment: ExecutableDeployItem, - session: ExecutableDeployItem, - approvals: BTreeSet, - #[serde(skip)] - #[data_size(with = ds::once_cell)] - is_valid: OnceCell> -} - -impl Deploy { - /// Constructs a new signed `Deploy`. - #[allow(clippy::too_many_arguments)] - pub fn new( - timestamp: Timestamp, - ttl: TimeDiff, - gas_price: u64, - dependencies: Vec, - chain_name: String, - payment: ExecutableDeployItem, - session: ExecutableDeployItem, - secret_key: &SecretKey, - account: Option - ) -> Deploy { - let serialized_body = serialize_body(&payment, &session); - let body_hash = Digest::hash(serialized_body); - - let account = account.unwrap_or_else(|| PublicKey::from(secret_key)); - - // Remove duplicates. - let dependencies = dependencies.into_iter().unique().collect(); - let header = DeployHeader::new( - account, - timestamp, - ttl, - gas_price, - body_hash, - dependencies, - chain_name - ); - let serialized_header = serialize_header(&header); - let hash = DeployHash::new(Digest::hash(serialized_header)); - - let mut deploy = Deploy { - hash, - header, - payment, - session, - approvals: BTreeSet::new(), - is_valid: OnceCell::new() - }; - - deploy.sign(secret_key); - deploy - } - - /// Adds a signature of this deploy's hash to its approvals. - pub fn sign(&mut self, secret_key: &SecretKey) { - let approval = Approval::create(&self.hash, secret_key); - self.approvals.insert(approval); - } - - /// Returns the `DeployHash` identifying this `Deploy`. - pub fn hash(&self) -> &DeployHash { - &self.hash - } - - /// Returns a reference to the `DeployHeader` of this `Deploy`. - pub fn header(&self) -> &DeployHeader { - &self.header - } - - /// Returns the `DeployHeader` of this `Deploy`. - pub fn take_header(self) -> DeployHeader { - self.header - } - - /// Returns the `ExecutableDeployItem` for payment code. - pub fn payment(&self) -> &ExecutableDeployItem { - &self.payment - } - - /// Returns the `ExecutableDeployItem` for session code. - pub fn session(&self) -> &ExecutableDeployItem { - &self.session - } - - /// Returns the `Approval`s for this deploy. - pub fn approvals(&self) -> &BTreeSet { - &self.approvals - } -} - -impl hash::Hash for Deploy { - fn hash(&self, state: &mut H) { - // Destructure to make sure we don't accidentally omit fields. - let Deploy { - hash, - header, - payment, - session, - approvals, - is_valid: _ - } = self; - hash.hash(state); - header.hash(state); - payment.hash(state); - session.hash(state); - approvals.hash(state); - } -} - -impl PartialEq for Deploy { - fn eq(&self, other: &Deploy) -> bool { - // Destructure to make sure we don't accidentally omit fields. - let Deploy { - hash, - header, - payment, - session, - approvals, - is_valid: _ - } = self; - *hash == other.hash - && *header == other.header - && *payment == other.payment - && *session == other.session - && *approvals == other.approvals - } -} - -impl Ord for Deploy { - fn cmp(&self, other: &Deploy) -> cmp::Ordering { - // Destructure to make sure we don't accidentally omit fields. - let Deploy { - hash, - header, - payment, - session, - approvals, - is_valid: _ - } = self; - hash.cmp(&other.hash) - .then_with(|| header.cmp(&other.header)) - .then_with(|| payment.cmp(&other.payment)) - .then_with(|| session.cmp(&other.session)) - .then_with(|| approvals.cmp(&other.approvals)) - } -} - -impl PartialOrd for Deploy { - fn partial_cmp(&self, other: &Deploy) -> Option { - Some(self.cmp(other)) - } -} - -impl ToBytes for Deploy { - fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { - self.header.write_bytes(writer)?; - self.hash.write_bytes(writer)?; - self.payment.write_bytes(writer)?; - self.session.write_bytes(writer)?; - self.approvals.write_bytes(writer) - } - - fn to_bytes(&self) -> Result, bytesrepr::Error> { - let mut buffer = bytesrepr::allocate_buffer(self)?; - self.write_bytes(&mut buffer)?; - Ok(buffer) - } - - fn serialized_length(&self) -> usize { - self.header.serialized_length() - + self.hash.serialized_length() - + self.payment.serialized_length() - + self.session.serialized_length() - + self.approvals.serialized_length() - } -} - -impl FromBytes for Deploy { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (header, remainder) = DeployHeader::from_bytes(bytes)?; - let (hash, remainder) = DeployHash::from_bytes(remainder)?; - let (payment, remainder) = ExecutableDeployItem::from_bytes(remainder)?; - let (session, remainder) = ExecutableDeployItem::from_bytes(remainder)?; - let (approvals, remainder) = BTreeSet::::from_bytes(remainder)?; - let maybe_valid_deploy = Deploy { - header, - hash, - payment, - session, - approvals, - is_valid: OnceCell::new() - }; - Ok((maybe_valid_deploy, remainder)) - } -} - -impl fmt::Display for Deploy { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!( - formatter, - "deploy[{}, {}, payment_code: {}, session_code: {}, approvals: {}]", - self.hash, - self.header, - self.payment, - self.session, - DisplayIter::new(self.approvals.iter()) - ) - } -} - -impl From for DeployItem { - fn from(deploy: Deploy) -> Self { - let address = deploy.header().account().to_account_hash(); - let authorization_keys = deploy - .approvals() - .iter() - .map(|approval| approval.signer().to_account_hash()) - .collect(); - - DeployItem::new( - address, - deploy.session().clone(), - deploy.payment().clone(), - deploy.header().gas_price(), - authorization_keys, - casper_types::DeployHash::new(deploy.hash().inner().value()) - ) - } -} - -fn serialize_header(header: &DeployHeader) -> Vec { - header - .to_bytes() - .unwrap_or_else(|error| panic!("should serialize deploy header: {}", error)) -} - -fn serialize_body(payment: &ExecutableDeployItem, session: &ExecutableDeployItem) -> Vec { - let mut buffer = payment - .to_bytes() - .unwrap_or_else(|error| panic!("should serialize payment code: {}", error)); - buffer.extend( - session - .to_bytes() - .unwrap_or_else(|error| panic!("should serialize session code: {}", error)) - ); - buffer -} diff --git a/odra-casper/rpc-client/src/casper_node_port/deploy_hash.rs b/odra-casper/rpc-client/src/casper_node_port/deploy_hash.rs deleted file mode 100644 index 63275627..00000000 --- a/odra-casper/rpc-client/src/casper_node_port/deploy_hash.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::fmt; - -use casper_hashing::Digest; -use datasize::DataSize; -use odra_core::casper_types::{ - self, - bytesrepr::{self, FromBytes, ToBytes} -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive( - Copy, - Clone, - DataSize, - Ord, - PartialOrd, - Eq, - PartialEq, - Hash, - Serialize, - Deserialize, - Debug, - Default, - JsonSchema, -)] -#[serde(deny_unknown_fields)] -#[schemars(with = "String", description = "Hex-encoded deploy hash.")] -pub struct DeployHash(#[schemars(skip)] Digest); - -impl DeployHash { - /// Constructs a new `DeployHash`. - pub fn new(hash: Digest) -> Self { - DeployHash(hash) - } - - /// Returns the wrapped inner hash. - pub fn inner(&self) -> &Digest { - &self.0 - } -} - -impl From for casper_types::DeployHash { - fn from(deploy_hash: DeployHash) -> casper_types::DeployHash { - casper_types::DeployHash::new(deploy_hash.inner().value()) - } -} - -impl From for DeployHash { - fn from(deploy_hash: casper_types::DeployHash) -> DeployHash { - DeployHash::new(deploy_hash.value().into()) - } -} - -impl From for Digest { - fn from(deploy_hash: DeployHash) -> Self { - deploy_hash.0 - } -} - -impl fmt::Display for DeployHash { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "deploy-hash({})", self.0,) - } -} - -impl From for DeployHash { - fn from(digest: Digest) -> Self { - Self(digest) - } -} - -impl AsRef<[u8]> for DeployHash { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl ToBytes for DeployHash { - fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { - self.0.write_bytes(writer) - } - - fn to_bytes(&self) -> Result, bytesrepr::Error> { - self.0.to_bytes() - } - - fn serialized_length(&self) -> usize { - self.0.serialized_length() - } -} - -impl FromBytes for DeployHash { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - Digest::from_bytes(bytes).map(|(inner, remainder)| (DeployHash(inner), remainder)) - } -} diff --git a/odra-casper/rpc-client/src/casper_node_port/deploy_header.rs b/odra-casper/rpc-client/src/casper_node_port/deploy_header.rs deleted file mode 100644 index 946876f3..00000000 --- a/odra-casper/rpc-client/src/casper_node_port/deploy_header.rs +++ /dev/null @@ -1,162 +0,0 @@ -use std::fmt::{self, Display, Formatter}; - -use datasize::DataSize; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use casper_hashing::Digest; -use odra_core::casper_types::{ - bytesrepr::{self, FromBytes, ToBytes}, - PublicKey, TimeDiff, Timestamp -}; - -use super::deploy_hash::DeployHash; -use super::utils::DisplayIter; - -/// The header portion of a [`Deploy`](super::Deploy). -#[derive( - Clone, DataSize, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug, JsonSchema, -)] -#[serde(deny_unknown_fields)] -pub struct DeployHeader { - account: PublicKey, - timestamp: Timestamp, - ttl: TimeDiff, - gas_price: u64, - body_hash: Digest, - dependencies: Vec, - chain_name: String -} - -impl DeployHeader { - pub(super) fn new( - account: PublicKey, - timestamp: Timestamp, - ttl: TimeDiff, - gas_price: u64, - body_hash: Digest, - dependencies: Vec, - chain_name: String - ) -> Self { - DeployHeader { - account, - timestamp, - ttl, - gas_price, - body_hash, - dependencies, - chain_name - } - } - - /// The account within which the deploy will be run. - pub fn account(&self) -> &PublicKey { - &self.account - } - - /// When the deploy was created. - pub fn timestamp(&self) -> Timestamp { - self.timestamp - } - - /// How long the deploy will stay valid. - pub fn ttl(&self) -> TimeDiff { - self.ttl - } - - /// Has this deploy expired? - pub fn expired(&self, current_instant: Timestamp) -> bool { - self.expires() < current_instant - } - - /// Price per gas unit for this deploy. - pub fn gas_price(&self) -> u64 { - self.gas_price - } - - /// Hash of the Wasm code. - pub fn body_hash(&self) -> &Digest { - &self.body_hash - } - - /// Other deploys that have to be run before this one. - pub fn dependencies(&self) -> &Vec { - &self.dependencies - } - - /// Which chain the deploy is supposed to be run on. - pub fn chain_name(&self) -> &str { - &self.chain_name - } - - /// Returns the timestamp of when the deploy expires, i.e. `self.timestamp + self.ttl`. - pub fn expires(&self) -> Timestamp { - self.timestamp.saturating_add(self.ttl) - } -} - -impl ToBytes for DeployHeader { - fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { - self.account.write_bytes(writer)?; - self.timestamp.write_bytes(writer)?; - self.ttl.write_bytes(writer)?; - self.gas_price.write_bytes(writer)?; - self.body_hash.write_bytes(writer)?; - self.dependencies.write_bytes(writer)?; - self.chain_name.write_bytes(writer) - } - - fn to_bytes(&self) -> Result, bytesrepr::Error> { - let mut buffer = bytesrepr::allocate_buffer(self)?; - self.write_bytes(&mut buffer)?; - Ok(buffer) - } - - fn serialized_length(&self) -> usize { - self.account.serialized_length() - + self.timestamp.serialized_length() - + self.ttl.serialized_length() - + self.gas_price.serialized_length() - + self.body_hash.serialized_length() - + self.dependencies.serialized_length() - + self.chain_name.serialized_length() - } -} - -impl FromBytes for DeployHeader { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (account, remainder) = PublicKey::from_bytes(bytes)?; - let (timestamp, remainder) = Timestamp::from_bytes(remainder)?; - let (ttl, remainder) = TimeDiff::from_bytes(remainder)?; - let (gas_price, remainder) = u64::from_bytes(remainder)?; - let (body_hash, remainder) = Digest::from_bytes(remainder)?; - let (dependencies, remainder) = Vec::::from_bytes(remainder)?; - let (chain_name, remainder) = String::from_bytes(remainder)?; - let deploy_header = DeployHeader { - account, - timestamp, - ttl, - gas_price, - body_hash, - dependencies, - chain_name - }; - Ok((deploy_header, remainder)) - } -} - -impl Display for DeployHeader { - fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { - write!( - formatter, - "deploy-header[account: {}, timestamp: {}, ttl: {}, gas_price: {}, body_hash: {}, dependencies: [{}], chain_name: {}]", - self.account, - self.timestamp, - self.ttl, - self.gas_price, - self.body_hash, - DisplayIter::new(self.dependencies.iter()), - self.chain_name, - ) - } -} diff --git a/odra-casper/rpc-client/src/casper_node_port/error.rs b/odra-casper/rpc-client/src/casper_node_port/error.rs deleted file mode 100644 index c8435721..00000000 --- a/odra-casper/rpc-client/src/casper_node_port/error.rs +++ /dev/null @@ -1,131 +0,0 @@ -use datasize::DataSize; -use odra_core::casper_types::{TimeDiff, U512}; -use serde::Serialize; -use thiserror::Error; - -/// A representation of the way in which a deploy failed validation checks. -#[allow(dead_code)] -#[derive(Clone, DataSize, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Error, Serialize)] -pub enum DeployConfigurationFailure { - /// Invalid chain name. - #[error("invalid chain name: expected {expected}, got {got}")] - InvalidChainName { - /// The expected chain name. - expected: String, - /// The received chain name. - got: String - }, - - /// Too many dependencies. - #[error("{got} dependencies exceeds limit of {max_dependencies}")] - ExcessiveDependencies { - /// The dependencies limit. - max_dependencies: u8, - /// The actual number of dependencies provided. - got: usize - }, - - /// Deploy is too large. - #[error("deploy size too large: {0}")] - ExcessiveSize(#[from] ExcessiveSizeError), - - /// Excessive time-to-live. - #[error("time-to-live of {got} exceeds limit of {max_ttl}")] - ExcessiveTimeToLive { - /// The time-to-live limit. - max_ttl: TimeDiff, - /// The received time-to-live. - got: TimeDiff - }, - - /// The provided body hash does not match the actual hash of the body. - #[error("the provided body hash does not match the actual hash of the body")] - InvalidBodyHash, - - /// The provided deploy hash does not match the actual hash of the deploy. - #[error("the provided hash does not match the actual hash of the deploy")] - InvalidDeployHash, - - /// The deploy has no approvals. - #[error("the deploy has no approvals")] - EmptyApprovals, - - /// Invalid approval. - #[error("the approval at index {index} is invalid: {error_msg}")] - InvalidApproval { - /// The index of the approval at fault. - index: usize, - /// The approval validation error. - error_msg: String - }, - - /// Excessive length of deploy's session args. - #[error("serialized session code runtime args of {got} exceeds limit of {max_length}")] - ExcessiveSessionArgsLength { - /// The byte size limit of session arguments. - max_length: usize, - /// The received length of session arguments. - got: usize - }, - - /// Excessive length of deploy's payment args. - #[error("serialized payment code runtime args of {got} exceeds limit of {max_length}")] - ExcessivePaymentArgsLength { - /// The byte size limit of payment arguments. - max_length: usize, - /// The received length of payment arguments. - got: usize - }, - - /// Missing payment "amount" runtime argument. - #[error("missing payment 'amount' runtime argument ")] - MissingPaymentAmount, - - /// Failed to parse payment "amount" runtime argument. - #[error("failed to parse payment 'amount' as U512")] - FailedToParsePaymentAmount, - - /// The payment amount associated with the deploy exceeds the block gas limit. - #[error("payment amount of {got} exceeds the block gas limit of {block_gas_limit}")] - ExceededBlockGasLimit { - /// Configured block gas limit. - block_gas_limit: u64, - /// The payment amount received. - got: U512 - }, - - /// Missing payment "amount" runtime argument - #[error("missing transfer 'amount' runtime argument")] - MissingTransferAmount, - - /// Failed to parse transfer "amount" runtime argument. - #[error("failed to parse transfer 'amount' as U512")] - FailedToParseTransferAmount, - - /// Insufficient transfer amount. - #[error("insufficient transfer amount; minimum: {minimum} attempted: {attempted}")] - InsufficientTransferAmount { - /// The minimum transfer amount. - minimum: U512, - /// The attempted transfer amount. - attempted: U512 - }, - - /// The amount of approvals on the deploy exceeds the max_associated_keys limit. - #[error("number of associated keys {got} exceeds the maximum {max_associated_keys}")] - ExcessiveApprovals { - /// Number of approvals on the deploy. - got: u32, - /// The chainspec limit for max_associated_keys. - max_associated_keys: u32 - } -} - -#[derive(Clone, DataSize, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Error, Serialize)] -#[error("deploy size of {actual_deploy_size} bytes exceeds limit of {max_deploy_size}")] -pub struct ExcessiveSizeError { - /// The maximum permitted serialized deploy size, in bytes. - pub max_deploy_size: u32, - /// The serialized size of the deploy provided, in bytes. - pub actual_deploy_size: usize -} diff --git a/odra-casper/rpc-client/src/casper_node_port/mod.rs b/odra-casper/rpc-client/src/casper_node_port/mod.rs deleted file mode 100644 index f977fa46..00000000 --- a/odra-casper/rpc-client/src/casper_node_port/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Functionalities ported from casper-node. All the code in this module is copied from casper-node. - -pub mod account; -pub mod approval; -pub mod block_hash; -pub mod contract; -pub mod contract_package; -pub mod deploy; -pub mod deploy_hash; -pub mod deploy_header; -pub mod error; -pub mod query_balance; -pub mod rpcs; -pub mod utils; - -pub use deploy::Deploy; -pub use deploy_hash::DeployHash; diff --git a/odra-casper/rpc-client/src/casper_node_port/query_balance.rs b/odra-casper/rpc-client/src/casper_node_port/query_balance.rs deleted file mode 100644 index e5b77022..00000000 --- a/odra-casper/rpc-client/src/casper_node_port/query_balance.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::casper_node_port::rpcs::GlobalStateIdentifier; -use odra_core::casper_types::account::AccountHash; -use odra_core::casper_types::{ProtocolVersion, PublicKey, URef, U512}; -use serde::{Deserialize, Serialize}; - -pub(crate) const QUERY_BALANCE_METHOD: &str = "query_balance"; - -/// Identifier of a purse. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(deny_unknown_fields, rename_all = "snake_case")] -pub enum PurseIdentifier { - /// The main purse of the account identified by this public key. - MainPurseUnderPublicKey(PublicKey), - /// The main purse of the account identified by this account hash. - MainPurseUnderAccountHash(AccountHash), - /// The purse identified by this URef. - PurseUref(URef) -} - -/// Params for "query_balance" RPC request. -#[derive(Serialize, Deserialize, Debug)] -pub struct QueryBalanceParams { - /// The state identifier used for the query. - pub state_identifier: Option, - /// The identifier to obtain the purse corresponding to balance query. - pub purse_identifier: PurseIdentifier -} - -impl QueryBalanceParams { - pub(crate) fn new( - state_identifier: Option, - purse_identifier: PurseIdentifier - ) -> Self { - QueryBalanceParams { - state_identifier, - purse_identifier - } - } -} - -/// Result for "query_balance" RPC response. -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct QueryBalanceResult { - /// The RPC API version. - pub api_version: ProtocolVersion, - /// The balance represented in motes. - pub balance: U512 -} diff --git a/odra-casper/rpc-client/src/casper_node_port/rpcs.rs b/odra-casper/rpc-client/src/casper_node_port/rpcs.rs deleted file mode 100644 index 749bd50e..00000000 --- a/odra-casper/rpc-client/src/casper_node_port/rpcs.rs +++ /dev/null @@ -1,247 +0,0 @@ -use crate::casper_node_port::contract::Contract; -use casper_hashing::Digest; -use odra_core::casper_types::{ - CLValue, EraId, ExecutionResult, ProtocolVersion, PublicKey, Timestamp, U512 -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use super::{ - account::Account, - block_hash::{BlockHash, BlockHashAndHeight}, - contract_package::ContractPackage, - Deploy, DeployHash -}; - -/// Result for "account_put_deploy" RPC response. -#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct PutDeployResult { - /// The RPC API version. - #[schemars(with = "String")] - pub api_version: ProtocolVersion, - /// The deploy hash. - pub deploy_hash: DeployHash -} - -/// Result for "chain_get_state_root_hash" RPC response. -#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct GetStateRootHashResult { - /// The RPC API version. - #[schemars(with = "String")] - pub api_version: ProtocolVersion, - /// Hex-encoded hash of the state root. - pub state_root_hash: Option -} - -/// Params for "info_get_deploy" RPC request. -#[derive(Serialize, Deserialize, Debug, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct GetDeployParams { - /// The deploy hash. - pub deploy_hash: DeployHash, - /// Whether to return the deploy with the finalized approvals substituted. If `false` or - /// omitted, returns the deploy with the approvals that were originally received by the node. - #[serde(default = "finalized_approvals_default")] - pub finalized_approvals: bool -} - -/// The default for `GetDeployParams::finalized_approvals`. -fn finalized_approvals_default() -> bool { - false -} - -/// Result for "info_get_deploy" RPC response. -#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct GetDeployResult { - /// The RPC API version. - #[schemars(with = "String")] - pub api_version: ProtocolVersion, - /// The deploy. - pub deploy: Deploy, - /// The map of block hash to execution result. - pub execution_results: Vec, - /// The hash and height of the block in which this deploy was executed, - /// only provided if the full execution results are not know on this node. - #[serde(skip_serializing_if = "Option::is_none", flatten)] - pub block_hash_and_height: Option -} - -/// The execution result of a single deploy. -#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct JsonExecutionResult { - /// The block hash. - pub block_hash: BlockHash, - /// Execution result. - pub result: ExecutionResult -} - -#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] -/// Options for dictionary item lookups. -pub enum DictionaryIdentifier { - /// Lookup a dictionary item via an Account's named keys. - AccountNamedKey { - /// The account key as a formatted string whose named keys contains dictionary_name. - key: String, - /// The named key under which the dictionary seed URef is stored. - dictionary_name: String, - /// The dictionary item key formatted as a string. - dictionary_item_key: String - }, - /// Lookup a dictionary item via a Contract's named keys. - ContractNamedKey { - /// The contract key as a formatted string whose named keys contains dictionary_name. - key: String, - /// The named key under which the dictionary seed URef is stored. - dictionary_name: String, - /// The dictionary item key formatted as a string. - dictionary_item_key: String - }, - /// Lookup a dictionary item via its seed URef. - URef { - /// The dictionary's seed URef. - seed_uref: String, - /// The dictionary item key formatted as a string. - dictionary_item_key: String - }, - /// Lookup a dictionary item via its unique key. - Dictionary(String) -} - -/// Params for "state_get_dictionary_item" RPC request. -#[derive(Serialize, Deserialize, Debug, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct GetDictionaryItemParams { - /// Hash of the state root - pub state_root_hash: Digest, - /// The Dictionary query identifier. - pub dictionary_identifier: DictionaryIdentifier -} - -/// Result for "state_get_dictionary_item" RPC response. -#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct GetDictionaryItemResult { - /// The RPC API version. - #[schemars(with = "String")] - pub api_version: ProtocolVersion, - /// The key under which the value is stored. - pub dictionary_key: String, - /// The stored value. - pub stored_value: StoredValue, - /// The Merkle proof. - pub merkle_proof: String -} - -#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] -#[serde(deny_unknown_fields)] -pub enum GlobalStateIdentifier { - /// Query using a block hash. - BlockHash(BlockHash), - /// Query using a block height. - BlockHeight(u64), - /// Query using the state root hash. - StateRootHash(Digest) -} - -/// Params for "query_global_state" RPC -#[derive(Serialize, Deserialize, Debug, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct QueryGlobalStateParams { - /// The identifier used for the query. - pub state_identifier: GlobalStateIdentifier, - /// `casper_types::Key` as formatted string. - pub key: String, - /// The path components starting from the key as base. - #[serde(default)] - pub path: Vec -} - -/// Result for "query_global_state" RPC response. -#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct QueryGlobalStateResult { - /// The RPC API version. - #[schemars(with = "String")] - pub api_version: ProtocolVersion, - /// The block header if a Block hash was provided. - pub block_header: Option, - /// The stored value. - pub stored_value: StoredValue, - /// The Merkle proof. - pub merkle_proof: String -} - -/// JSON representation of a block header. -#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct JsonBlockHeader { - /// The parent hash. - pub parent_hash: BlockHash, - /// The state root hash. - pub state_root_hash: Digest, - /// The body hash. - pub body_hash: Digest, - /// Randomness bit. - pub random_bit: bool, - /// Accumulated seed. - pub accumulated_seed: Digest, - /// The era end. - pub era_end: Option, - /// The block timestamp. - pub timestamp: Timestamp, - /// The block era id. - pub era_id: EraId, - /// The block height. - pub height: u64, - /// The protocol version. - pub protocol_version: ProtocolVersion -} - -#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct JsonEraEnd { - era_report: JsonEraReport, - next_era_validator_weights: Vec -} - -/// Equivocation and reward information to be included in the terminal block. -#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -struct JsonEraReport { - equivocators: Vec, - rewards: Vec, - inactive_validators: Vec -} - -#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -struct Reward { - validator: PublicKey, - amount: u64 -} - -#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -struct ValidatorWeight { - validator: PublicKey, - weight: U512 -} - -#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, JsonSchema)] -#[serde(deny_unknown_fields)] -pub enum StoredValue { - /// A CasperLabs value. - CLValue(CLValue), - - /// An account. - Account(Account), - - /// A contract definition, metadata, and security container. - ContractPackage(ContractPackage), - - Contract(Contract) -} diff --git a/odra-casper/rpc-client/src/casper_node_port/utils.rs b/odra-casper/rpc-client/src/casper_node_port/utils.rs deleted file mode 100644 index 35a9e2b0..00000000 --- a/odra-casper/rpc-client/src/casper_node_port/utils.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::{cell::RefCell, fmt}; - -/// A display-helper that shows iterators display joined by ",". -#[derive(Debug)] -pub(crate) struct DisplayIter(RefCell>); - -impl DisplayIter { - pub(crate) fn new(item: T) -> Self { - DisplayIter(RefCell::new(Some(item))) - } -} - -impl fmt::Display for DisplayIter -where - I: IntoIterator, - T: fmt::Display -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(src) = self.0.borrow_mut().take() { - let mut first = true; - for item in src.into_iter().take(f.width().unwrap_or(usize::MAX)) { - if first { - first = false; - write!(f, "{}", item)?; - } else { - write!(f, ", {}", item)?; - } - } - - Ok(()) - } else { - write!(f, "DisplayIter:GONE") - } - } -} - -pub mod ds { - use datasize::DataSize; - use std::cell::OnceCell; - - pub(crate) fn once_cell(cell: &OnceCell) -> usize - where - T: DataSize - { - cell.get().map_or(0, |value| value.estimate_heap_size()) - } -} diff --git a/odra-casper/rpc-client/src/lib.rs b/odra-casper/rpc-client/src/lib.rs index dc159ea3..6b7e1043 100644 --- a/odra-casper/rpc-client/src/lib.rs +++ b/odra-casper/rpc-client/src/lib.rs @@ -1,5 +1,8 @@ //! Casper Client implementation for Odra. //! It uses parts of the Casper Client implementation to communicate with Casper Node's RPC API. + +extern crate core; + pub mod casper_client; -pub mod casper_node_port; pub mod log; +pub mod utils; diff --git a/odra-casper/rpc-client/src/utils.rs b/odra-casper/rpc-client/src/utils.rs new file mode 100644 index 00000000..79f1c67e --- /dev/null +++ b/odra-casper/rpc-client/src/utils.rs @@ -0,0 +1,55 @@ +use serde_json::Value; +use std::path::PathBuf; + +/// Search for the wasm file in the current directory and in the parent directory. +pub fn find_wasm_file_path(wasm_file_name: &str) -> PathBuf { + let mut path = PathBuf::from("wasm") + .join(wasm_file_name) + .with_extension("wasm"); + let mut checked_paths = vec![]; + for _ in 0..2 { + if path.exists() && path.is_file() { + crate::log::info(format!("Found wasm under {:?}.", path)); + return path; + } else { + checked_paths.push(path.clone()); + path = path.parent().unwrap().to_path_buf(); + } + } + crate::log::error(format!("Could not find wasm under {:?}.", checked_paths)); + panic!("Wasm not found"); +} + +/// Gets an env variable +pub fn get_env_variable(name: &str) -> String { + std::env::var(name).unwrap_or_else(|err| { + crate::log::error(format!( + "{} must be set. Have you setup your .env file?", + name + )); + panic!("{}", err) + }) +} + +/// Gets an optional env variable +pub fn get_optional_env_variable(name: &str) -> Option { + std::env::var(name).ok() +} + +/// Converts RuntimeArgs into Vec compatible with rustSDK +pub fn runtime_args_to_simple_args(runtime_args: &casper_types::RuntimeArgs) -> Vec { + runtime_args + .named_args() + .map(|named_arg| { + let value = serde_json::to_string(&named_arg.cl_value()).unwrap(); + let json: Value = serde_json::from_str(&value).unwrap(); + let value = json.get("parsed").unwrap().to_string(); + format!( + "{}:{}='{}'", + named_arg.name(), + named_arg.cl_value().cl_type(), + value, + ) + }) + .collect() +} diff --git a/odra-casper/test-vm/Cargo.toml b/odra-casper/test-vm/Cargo.toml index 5fc527e2..b5fe81c7 100644 --- a/odra-casper/test-vm/Cargo.toml +++ b/odra-casper/test-vm/Cargo.toml @@ -10,15 +10,16 @@ homepage = { workspace = true } repository = { workspace = true } [dependencies] -casper-engine-test-support = { version = "7.0.1", features = ["test-support"] } +casper-engine-test-support = { workspace = true } casper-execution-engine = { workspace = true } +casper-storage = { workspace = true } odra-core = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -casper-contract = { workspace = true } +casper-contract = { workspace = true, default-features = false } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -casper-contract = { version = "4.0.0", default-features = false, features = ["test-support"] } +casper-contract = { workspace = true, default-features = false, features = ["test-support"] } [lints.rust] missing_docs = "warn" diff --git a/odra-casper/test-vm/resources/chainspec.toml b/odra-casper/test-vm/resources/chainspec.toml index 63b7c8dd..a939119a 100644 --- a/odra-casper/test-vm/resources/chainspec.toml +++ b/odra-casper/test-vm/resources/chainspec.toml @@ -1,8 +1,8 @@ [protocol] # Protocol version. -version = '1.5.6' +version = '2.0.0' # Whether we need to clear latest blocks back to the switch block just before the activation point or not. -hard_reset = true +hard_reset = false # This protocol version becomes active at this point. # # If it is a timestamp string, it represents the timestamp for the genesis block. This is the beginning of era 0. By @@ -11,27 +11,27 @@ hard_reset = true # in contract-runtime for computing genesis post-state hash. # # If it is an integer, it represents an era ID, meaning the protocol version becomes active at the start of this era. -activation_point = 11000 +activation_point = '${TIMESTAMP}' [network] # Human readable name for convenience; the genesis_hash is the true identifier. The name influences the genesis hash by # contributing to the seeding of the pseudo-random number generator used in contract-runtime for computing genesis # post-state hash. -name = 'casper' +name = 'casper-example' # The maximum size of an acceptable networking message in bytes. Any message larger than this will # be rejected at the networking level. maximum_net_message_size = 25_165_824 [core] # Era duration. -era_duration = '120 minutes' +era_duration = '41 seconds' # Minimum number of blocks per era. An era will take longer than `era_duration` if that is necessary to reach the # minimum height. -minimum_era_height = 20 +minimum_era_height = 5 # Minimum difference between a block's and its child's timestamp. -minimum_block_time = '16384 ms' +minimum_block_time = '4096 ms' # Number of slots available in validator auction. -validator_slots = 100 +validator_slots = 7 # A number between 0 and 1 representing the fault tolerance threshold as a fraction, used by the internal finalizer. # It is the fraction of validators that would need to equivocate to make two honest nodes see two conflicting blocks as # finalized: A higher value F makes it safer to rely on finalized blocks. It also makes it more difficult to finalize @@ -44,6 +44,17 @@ start_protocol_version_with_strict_finality_signatures_required = '1.5.0' # in a protocol version before # `start_protocol_version_with_strict_finality_signatures_required`. legacy_required_finality = 'Strict' + +# If true, the protocol upgrade will migrate ALL userland accounts to addressable entity. +# If false, userland accounts will instead be left as is and will be lazily migrated +# on a per-account basis if / when that account is used during transaction execution. +migrate_legacy_accounts = true + +# If true, the protocol upgrade will migrate ALL userland contracts to addressable entity. +# If false, userland contracts will instead be left as is and will be lazily migrated +# on a per-contract basis if / when that contract is used during transaction execution. +migrate_legacy_contracts = true + # Number of eras before an auction actually defines the set of validators. If you bond with a sufficient bid in era N, # you will be a validator in era N + auction_delay + 1. auction_delay = 1 @@ -55,21 +66,16 @@ vesting_schedule_period = '0 weeks' unbonding_delay = 7 # Round seigniorage rate represented as a fraction of the total supply. # -# Annual issuance: 8% -# Minimum block time: 2^14 milliseconds -# Ticks per year: 31536000000 -# -# (1+0.08)^((2^14)/31536000000)-1 is expressed as a fractional number below -# Python: -# from fractions import Fraction -# Fraction((1 + 0.08)**((2**14)/31536000000) - 1).limit_denominator(1000000000) -round_seigniorage_rate = [7, 175070816] +# A rate that makes the rewards roughly 0.05% of the initial stake per block under default NCTL settings. +round_seigniorage_rate = [1, 4_200_000_000_000_000_000] # Maximum number of associated keys for a single account. max_associated_keys = 100 # Maximum height of contract runtime call stack. max_runtime_call_stack_height = 12 # Minimum allowed delegation amount in motes minimum_delegation_amount = 500_000_000_000 +# Maximum allowed delegation amount in motes +maximum_delegation_amount = 1_000_000_000_000_000_000 # Global state prune batch size (0 = this feature is off) prune_batch_size = 0 # Enables strict arguments checking when calling a contract; i.e. that all non-optional args are provided and of the correct `CLType`. @@ -77,77 +83,127 @@ strict_argument_checking = false # Number of simultaneous peer requests. simultaneous_peer_requests = 5 # The consensus protocol to use. Options are "Zug" and "Highway". -consensus_protocol = 'Highway' -# The maximum amount of delegators per validator. if the value is 0, there is no maximum capacity. +consensus_protocol = 'Zug' +# The maximum amount of delegators per validator. max_delegators_per_validator = 1200 -# Allows peer to peer transfers between users. +# The split in finality signature rewards between block producer and participating signers. +finders_fee = [1, 5] +# The proportion of baseline rewards going to reward finality signatures specifically. +finality_signature_proportion = [1, 2] +# Lookback interval indicating which past block we are looking at to reward. +signature_rewards_max_delay = 3 +# Setting this to false makes sense only on private chains which don't need to auction new validator slots. # -# Setting this to false makes sense only for private chains. -allow_unrestricted_transfers = true -# Enables the auction entry points 'delegate' and 'add_bid'. -# -# Setting this to false makes sense only for private chains which don't need to auction new validator slots. These -# auction entry points will return an error if called when this option is set to false. +# Changing this option makes sense only for private chains which dont need auctioning new validator slots. allow_auction_bids = true +# Allow peer to peer transfers between users. Setting this to false makes sense only on private chains. +allow_unrestricted_transfers = true # If set to false, then consensus doesn't compute rewards and always uses 0. compute_rewards = true # Defines how refunds of the unused portion of payment amounts are calculated and handled. # # Valid options are: -# 'refund': this causes excess payment amounts to be sent to either a pre-defined purse, or back to the sender. -# the refunded amount is calculated as the given ratio of the payment amount minus the execution costs. -# 'burn': similar to what refund does; except the refund amount is burned. -refund_handling = { type = 'refund', refund_ratio = [0, 100] } +# 'refund': a ratio of the unspent token is returned to the spender. +# 'burn': a ratio of the unspent token is burned. +# 'no_refund': no refunds are paid out; this is functionally equivalent to refund with 0% ratio. +# This causes excess payment amounts to be sent to either a +# pre-defined purse, or back to the sender. The refunded amount is calculated as the given ratio of the payment amount +# minus the execution costs. +refund_handling = { type = 'no_refund' } # Defines how fees are handled. # # Valid options are: +# 'no_fee': fees are eliminated. # 'pay_to_proposer': fees are paid to the block proposer # 'accumulate': fees are accumulated in a special purse and distributed at the end of each era evenly among all # administrator accounts # 'burn': fees are burned -fee_handling = { type = 'pay_to_proposer' } +fee_handling = { type = 'no_fee' } +# If a validator would recieve a validator credit, it cannot exceed this percentage of their total stake. +validator_credit_cap = [1, 5] +# Defines how pricing is handled. +# +# Valid options are: +# 'classic': senders of transaction self-specify how much they pay. +# 'fixed': costs are fixed, per the cost table +# 'reserved': prepaid transaction (currently not supported) +pricing_handling = { type = 'fixed' } +# Does the network allow pre-payment / reservations for future +# execution? Currently not supported. +# +allow_reservations = false +# Defines how gas holds affect available balance calculations. +# +# Valid options are: +# 'accrued': sum of full value of all non-expired holds. +# 'amortized': sum of each hold is amortized over the time remaining until expiry. +# +# For instance, if 12 hours remained on a gas hold with a 24-hour `gas_hold_interval`, +# with accrued, the full hold amount would be applied +# with amortized, half the hold amount would be applied +gas_hold_balance_handling = { type = 'accrued' } +# Defines how long gas holds last. +# +# If fee_handling is set to 'no_fee', the system places a balance hold on the payer +# equal to the value the fee would have been. Such balance holds expire after a time +# interval has elapsed. This setting controls how long that interval is. The available +# balance of a purse equals its total balance minus the held amount(s) of non-expired +# holds (see gas_hold_balance_handling setting for details of how that is calculated). +# +# For instance, if gas_hold_interval is 24 hours and 100 gas is used from a purse, +# a hold for 100 is placed on that purse and is considered when calculating total balance +# for 24 hours starting from the block_time when the hold was placed. +gas_hold_interval = '24 hours' # List of public keys of administrator accounts. Setting this option makes only on private chains which require # administrator accounts for regulatory reasons. administrators = [] [highway] # Highway dynamically chooses its round length, between minimum_block_time and maximum_round_length. -maximum_round_length = '66 seconds' -# The factor by which rewards for a round are multiplied if the greatest summit has ≤50% quorum, i.e. no finality. -# Expressed as a fraction (1/5 by default). -reduced_reward_multiplier = [1, 5] +maximum_round_length = '17 seconds' -[highway.performance_meter] -# The number of recent blocks to consider when measuring performance for the purpose of deciding the round length. -blocks_to_consider = 10 +[transactions] +# The duration after the transaction timestamp that it can be included in a block. +max_ttl = '18 hours' +# The maximum number of approvals permitted in a single block. +block_max_approval_count = 2600 +# Maximum block size in bytes including transactions contained by the block. 0 means unlimited. +max_block_size = 10_485_760 +# The upper limit of total gas of all transactions in a block. +block_gas_limit = 10_000_000_000_000 +# The minimum amount in motes for a valid native transfer. +native_transfer_minimum_motes = 2_500_000_000 +# The maximum value to which `transaction_acceptor.timestamp_leeway` can be set in the config.toml file. +max_timestamp_leeway = '5 seconds' + +[transactions.v1] +# The configuration settings for the lanes of transactions including both native and Wasm based interactions. +# Currently the node supports two native interactions the mint and auction and have the reserved identifiers of 0 and 1 +# respectively +# The remaining wasm based lanes specify the range of configuration settings for a given Wasm based transaction +# within a given lane. +# The maximum length in bytes of runtime args per V1 transaction. +# [0] -> Transaction lane label (apart from the reserved native identifiers these are simply labels) +# Note: For the Casper mainnet implementation we specially reserve the label 2 for install and upgrades and +# the lane must be present and defined. +# Different casper networks may not impose such a restriction. +# [1] -> Max transaction size in bytes for a given transaction in a certain lane +# [2] -> Max args length size in bytes for a given transaction in a certain lane +# [3] -> Transaction gas limit size in bytes for a given transaction in a certain lane +# [4] -> The maximum number of transactions the lane can contain +native_mint_lane = [0, 1024, 1024, 65_000_000_000, 650] +native_auction_lane = [1, 2048, 2048, 362_500_000_000, 145] +wasm_lanes = [[2, 1_048_576, 2048, 1_000_000_000_000, 2], [3, 262_144, 1024, 100_000_000_000, 3], [4, 131_072, 1024, 50_000_000_000, 5], [5, 8_192, 512, 1_500_000_000, 15]] -[deploys] +[transactions.deploy] # The maximum number of Motes allowed to be spent during payment. 0 means unlimited. max_payment_cost = '0' -# The duration after the deploy timestamp that it can be included in a block. -max_ttl = '2 hours' # The maximum number of other deploys a deploy can depend on (require to have been executed before it can execute). max_dependencies = 10 -# Maximum block size in bytes including deploys contained by the block. 0 means unlimited. -max_block_size = 5_242_880 -# Maximum deploy size in bytes. Size is of the deploy when serialized via ToBytes. -max_deploy_size = 1_048_576 -# The maximum number of non-transfer deploys permitted in a single block. -block_max_deploy_count = 25 -# The maximum number of wasm-less transfer deploys permitted in a single block. -block_max_transfer_count = 650 -# The maximum number of approvals permitted in a single block. -block_max_approval_count = 2600 -# The upper limit of total gas of all deploys in a block. -block_gas_limit = 4_000_000_000_000 # The limit of length of serialized payment code arguments. payment_args_max_length = 1024 # The limit of length of serialized session code arguments. session_args_max_length = 1024 -# The minimum amount in motes for a valid native transfer. -native_transfer_minimum_motes = 2_500_000_000 -# The maximum value to which `deploy_acceptor.timestamp_leeway` can be set in the config.toml file. -max_timestamp_leeway = '5 seconds' [wasm] # Amount of free memory (in 64kB pages) each contract can use for stack. @@ -215,21 +271,21 @@ size_multiplier = 100 # Host function declarations are located in smart_contracts/contract/src/ext_ffi.rs [wasm.host_function_costs] add = { cost = 5_800, arguments = [0, 0, 0, 0] } -add_associated_key = { cost = 1_200_000, arguments = [0, 0, 0] } -add_contract_version = { cost = 200, arguments = [0, 0, 0, 0, 120_000, 0, 0, 0, 0, 0] } -blake2b = { cost = 1_200_000, arguments = [0, 120_000, 0, 0] } -call_contract = { cost = 300_000_000, arguments = [0, 0, 0, 120_000, 0, 120_000, 0] } -call_versioned_contract = { cost = 300_000_000, arguments = [0, 0, 0, 0, 0, 120_000, 0, 120_000, 0] } +add_associated_key = { cost = 9_000, arguments = [0, 0, 0] } +add_contract_version = { cost = 200, arguments = [0, 0, 0, 0, 0, 0, 0, 0, 30_000, 0, 0] } +blake2b = { cost = 200, arguments = [0, 0, 0, 0] } +call_contract = { cost = 4_500, arguments = [0, 0, 0, 0, 0, 420, 0] } +call_versioned_contract = { cost = 4_500, arguments = [0, 0, 0, 0, 0, 0, 0, 420, 0] } create_contract_package_at_hash = { cost = 200, arguments = [0, 0] } create_contract_user_group = { cost = 200, arguments = [0, 0, 0, 0, 0, 0, 0, 0] } create_purse = { cost = 2_500_000_000, arguments = [0, 0] } disable_contract_version = { cost = 200, arguments = [0, 0, 0, 0] } -get_balance = { cost = 3_000_000, arguments = [0, 0, 0] } +get_balance = { cost = 3_800, arguments = [0, 0, 0] } get_blocktime = { cost = 330, arguments = [0] } get_caller = { cost = 380, arguments = [0] } get_key = { cost = 2_000, arguments = [0, 440, 0, 0, 0] } get_main_purse = { cost = 1_300, arguments = [0] } -get_named_arg = { cost = 200, arguments = [0, 120_000, 0, 120_000] } +get_named_arg = { cost = 200, arguments = [0, 0, 0, 0] } get_named_arg_size = { cost = 200, arguments = [0, 0, 0] } get_phase = { cost = 710, arguments = [0] } get_system_contract = { cost = 1_100, arguments = [0, 0, 0] } @@ -240,27 +296,34 @@ new_uref = { cost = 17_000, arguments = [0, 0, 590] } random_bytes = { cost = 200, arguments = [0, 0] } print = { cost = 20_000, arguments = [0, 4_600] } provision_contract_user_group_uref = { cost = 200, arguments = [0, 0, 0, 0, 0] } -put_key = { cost = 100_000_000, arguments = [0, 120_000, 0, 120_000] } +put_key = { cost = 38_000, arguments = [0, 1_100, 0, 0] } read_host_buffer = { cost = 3_500, arguments = [0, 310, 0] } -read_value = { cost = 60_000, arguments = [0, 120_000, 0] } -read_value_local = { cost = 5_500, arguments = [0, 590, 0] } +read_value = { cost = 6_000, arguments = [0, 0, 0] } +dictionary_get = { cost = 5_500, arguments = [0, 590, 0] } remove_associated_key = { cost = 4_200, arguments = [0, 0] } remove_contract_user_group = { cost = 200, arguments = [0, 0, 0, 0] } -remove_contract_user_group_urefs = { cost = 200, arguments = [0, 0, 0, 0, 0, 120_000] } +remove_contract_user_group_urefs = { cost = 200, arguments = [0, 0, 0, 0, 0, 0] } remove_key = { cost = 61_000, arguments = [0, 3_200] } ret = { cost = 23_000, arguments = [0, 420_000] } revert = { cost = 500, arguments = [0] } set_action_threshold = { cost = 74_000, arguments = [0, 0] } transfer_from_purse_to_account = { cost = 2_500_000_000, arguments = [0, 0, 0, 0, 0, 0, 0, 0, 0] } -transfer_from_purse_to_purse = { cost = 82_000_000, arguments = [0, 0, 0, 0, 0, 0, 0, 0] } +transfer_from_purse_to_purse = { cost = 82_000, arguments = [0, 0, 0, 0, 0, 0, 0, 0] } transfer_to_account = { cost = 2_500_000_000, arguments = [0, 0, 0, 0, 0, 0, 0] } update_associated_key = { cost = 4_200, arguments = [0, 0, 0] } write = { cost = 14_000, arguments = [0, 0, 0, 980] } -write_local = { cost = 9_500, arguments = [0, 1_800, 0, 520] } +dictionary_put = { cost = 9_500, arguments = [0, 1_800, 0, 520] } enable_contract_version = { cost = 200, arguments = [0, 0, 0, 0] } +manage_message_topic = { cost = 200, arguments = [0, 30_000, 0, 0] } +emit_message = { cost = 200, arguments = [0, 30_000, 0, 120_000] } +cost_increase_per_message = 50 + +[wasm.messages_limits] +max_topic_name_size = 256 +max_topics_per_contract = 128 +max_message_size = 1_024 [system_costs] -wasmless_transfer_cost = 100_000_000 [system_costs.auction_costs] get_era_validators = 10_000 @@ -277,12 +340,14 @@ withdraw_validator_reward = 10_000 read_era_id = 10_000 activate_bid = 10_000 redelegate = 2_500_000_000 +change_bid_public_key = 5_000_000_000 [system_costs.mint_costs] mint = 2_500_000_000 reduce_total_supply = 10_000 create = 2_500_000_000 balance = 10_000 +burn = 10_000 transfer = 10_000 read_base_round_reward = 10_000 mint_into_existing_purse = 2_500_000_000 @@ -294,4 +359,11 @@ get_refund_purse = 10_000 finalize_payment = 10_000 [system_costs.standard_payment_costs] -pay = 10_000 \ No newline at end of file +pay = 10_000 + + +[vacancy] +upper_threshold = 90 +lower_threshold = 50 +max_gas_price = 3 +min_gas_price = 1 diff --git a/odra-casper/test-vm/resources/proxy_caller.wasm b/odra-casper/test-vm/resources/proxy_caller.wasm index adc678f16164352961c47cb92198395c00f263a8..0d381bf7c48c0bd1e0ac9c5948ec1aec817e826c 100755 GIT binary patch literal 42817 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 literal 34392 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

7}wWFt)K{;x}zTix`V0GcB8;nuVB&YsizpCZ*-% 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 diff --git a/odra-casper/test-vm/src/casper_host.rs b/odra-casper/test-vm/src/casper_host.rs index 32d305dc..94d11235 100644 --- a/odra-casper/test-vm/src/casper_host.rs +++ b/odra-casper/test-vm/src/casper_host.rs @@ -4,19 +4,15 @@ use std::env; use std::path::PathBuf; use casper_engine_test_support::{ - DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, ARG_AMOUNT, - DEFAULT_ACCOUNT_INITIAL_BALANCE, DEFAULT_CHAINSPEC_REGISTRY, DEFAULT_GENESIS_CONFIG, - DEFAULT_GENESIS_CONFIG_HASH, DEFAULT_PAYMENT + DeployItemBuilder, ExecuteRequestBuilder, ARG_AMOUNT, DEFAULT_ACCOUNT_INITIAL_BALANCE, + DEFAULT_CHAINSPEC_REGISTRY, DEFAULT_GENESIS_CONFIG_HASH, DEFAULT_PAYMENT }; use std::rc::Rc; use crate::CasperVm; -use casper_execution_engine::core::engine_state::{GenesisAccount, RunGenesisRequest}; use odra_core::casper_types::account::AccountHash; use odra_core::casper_types::bytesrepr::{Bytes, ToBytes}; -use odra_core::casper_types::{ - runtime_args, BlockTime, ContractPackageHash, Key, Motes, SecretKey -}; +use odra_core::casper_types::{runtime_args, BlockTime, Key, Motes, SecretKey}; use odra_core::casper_types::{PublicKey, RuntimeArgs, U512}; use odra_core::consts; use odra_core::consts::*; @@ -64,9 +60,19 @@ impl HostContext for CasperHost { } fn get_event(&self, contract_address: &Address, index: u32) -> Result { + if !contract_address.is_contract() { + panic!("Events can be retrieved only for contracts, not for accounts.") + } self.vm.borrow().get_event(contract_address, index) } + fn get_events_count(&self, contract_address: &Address) -> u32 { + if !contract_address.is_contract() { + panic!("Events can be retrieved only for contracts, not for accounts.") + } + self.vm.borrow().get_events_count(contract_address) + } + fn call_contract( &self, address: &Address, @@ -91,10 +97,6 @@ impl HostContext for CasperHost { } } - fn get_events_count(&self, contract_address: &Address) -> u32 { - self.vm.borrow().get_events_count(contract_address) - } - fn new_contract( &self, name: &str, diff --git a/odra-casper/test-vm/src/vm/casper_vm.rs b/odra-casper/test-vm/src/vm/casper_vm.rs index 2ba20e86..c5ac2333 100644 --- a/odra-casper/test-vm/src/vm/casper_vm.rs +++ b/odra-casper/test-vm/src/vm/casper_vm.rs @@ -1,3 +1,7 @@ +use odra_core::casper_types::{ + AddressableEntity, AddressableEntityHash, GenesisConfig, GenesisConfigBuilder, Package, + PackageHash, ProtocolVersion +}; use odra_core::consts::*; use odra_core::prelude::*; use odra_core::OdraResult; @@ -6,21 +10,28 @@ use std::env; use std::path::PathBuf; use casper_engine_test_support::{ - DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, ARG_AMOUNT, - DEFAULT_ACCOUNT_INITIAL_BALANCE, DEFAULT_CHAINSPEC_REGISTRY, DEFAULT_GENESIS_CONFIG, - DEFAULT_GENESIS_CONFIG_HASH, DEFAULT_PAYMENT + DeployItemBuilder, EntityWithNamedKeys, ExecuteRequestBuilder, LmdbWasmTestBuilder, + WasmTestBuilder, ARG_AMOUNT, DEFAULT_ACCOUNTS, DEFAULT_ACCOUNT_INITIAL_BALANCE, + DEFAULT_AUCTION_DELAY, DEFAULT_CHAINSPEC_REGISTRY, DEFAULT_EXEC_CONFIG, + DEFAULT_GENESIS_CONFIG_HASH, DEFAULT_GENESIS_TIMESTAMP_MILLIS, + DEFAULT_LOCKED_FUNDS_PERIOD_MILLIS, DEFAULT_PAYMENT, DEFAULT_ROUND_SEIGNIORAGE_RATE, + DEFAULT_SYSTEM_CONFIG, DEFAULT_UNBONDING_DELAY, DEFAULT_VALIDATOR_SLOTS, DEFAULT_WASM_CONFIG }; use casper_event_standard::try_full_name_from_bytes; +use casper_execution_engine::{engine_state, execution}; +use casper_storage::data_access_layer::{DataAccessLayer, GenesisRequest}; use odra_core::{casper_event_standard, DeployReport, GasReport}; use std::rc::Rc; -use casper_execution_engine::core::engine_state::{self, GenesisAccount, RunGenesisRequest}; use odra_core::casper_types::account::{Account, AccountHash}; +use odra_core::casper_types::addressable_entity::NamedKeys; use odra_core::casper_types::bytesrepr::{Bytes, ToBytes}; -use odra_core::casper_types::{bytesrepr::FromBytes, CLTyped, PublicKey, RuntimeArgs, U512}; +use odra_core::casper_types::contracts::{ContractHash, ContractPackageHash}; +use odra_core::casper_types::{ + bytesrepr::FromBytes, CLTyped, GenesisAccount, PublicKey, RuntimeArgs, U512 +}; use odra_core::casper_types::{ - runtime_args, ApiError, BlockTime, Contract, ContractHash, ContractPackageHash, Key, Motes, - SecretKey, StoredValue, URef + runtime_args, ApiError, BlockTime, Contract, Key, Motes, SecretKey, StoredValue, URef }; use odra_core::consts; use odra_core::consts::*; @@ -33,12 +44,12 @@ use odra_core::{ }; use odra_core::{Address, ExecutionError, OdraError, VmError}; -/// Casper virtual machine utilizing [InMemoryWasmTestBuilder]. +/// Casper virtual machine utilizing [LmdbWasmTestBuilder]. pub struct CasperVm { accounts: Vec

, key_pairs: BTreeMap, active_account: Address, - context: InMemoryWasmTestBuilder, + context: LmdbWasmTestBuilder, block_time: u64, calls_counter: u32, error: Option, @@ -55,12 +66,12 @@ impl CasperVm { /// Read a ContractPackageHash of a given name, from the active account. pub fn contract_package_hash_from_name(&self, name: &str) -> ContractPackageHash { - let account = self + let named_keys = self .context - .get_account(self.active_account_hash()) - .unwrap(); - let key: &Key = account.named_keys().get(name).unwrap(); - ContractPackageHash::from(key.into_hash().unwrap()) + .get_named_keys_by_account_hash(self.active_account_hash()); + + let key: &Key = named_keys.get(name).unwrap(); + ContractPackageHash::from(key.into_package_hash().unwrap().value()) } /// Updates the active account (caller) address. @@ -95,41 +106,38 @@ impl CasperVm { /// Returns [EventError::IndexOutOfBounds] if the index is out of bounds. pub fn get_event(&self, contract_address: &Address, index: u32) -> Result { let contract_package_hash = contract_address.as_contract_package_hash().unwrap(); - let contract_hash: ContractHash = self.get_contract_package_hash(contract_package_hash); - - let dictionary_seed_uref: URef = *self - .context - .get_contract(contract_hash) - .unwrap() - .named_keys() - .get(consts::EVENTS) - .unwrap() - .as_uref() - .unwrap(); - - match self - .context - .query_dictionary_item(None, dictionary_seed_uref, &index.to_string()) - { - Ok(val) => { - let bytes = val - .as_cl_value() - .unwrap() - .clone() - .into_t::() - .unwrap(); - Ok(bytes) - } - Err(_) => Err(EventError::IndexOutOfBounds) - } + let contract_hash: ContractHash = self.get_contract_hash(contract_package_hash); + + let dictionary_seed_uref = self + .package_named_key(*contract_package_hash, EVENTS) + .ok_or(EventError::ContractDoesntSupportEvents)?; + + Ok(self.get_dict_value(*dictionary_seed_uref.as_uref().unwrap(), &index.to_string())) + // TODO: Handle errors properly... + // match self.context.query_dictionary_item( + // None, + // *dictionary_seed_uref.as_uref().unwrap(), + // &index.to_string() + // ) { + // Ok(val) => { + // let bytes = val + // .as_cl_value() + // .unwrap() + // .clone() + // .into_t::() + // .unwrap(); + // Ok(bytes) + // } + // Err(_) => Err(EventError::IndexOutOfBounds) + // } } /// Gets the count of events for the given contract address. 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) + let contract_package_hash = contract_address + .as_contract_package_hash() + .expect("Events can only be queried for contracts"); + self.events_length(*contract_package_hash) } /// Attaches a value to the next call. @@ -147,9 +155,7 @@ impl CasperVm { use_proxy: bool ) -> Bytes { self.error = None; - let hash = *address - .as_contract_package_hash() - .expect("Contract hash expected"); + let hash = address.as_package_hash().expect("Contract hash expected"); let deploy_item = if use_proxy { let session_code = @@ -158,9 +164,9 @@ impl CasperVm { .args() .to_bytes() .expect("Should serialize to bytes"); - let entry_point = call_def.entry_point().to_string(); + let entry_point = call_def.entry_point(); let args = runtime_args! { - CONTRACT_PACKAGE_HASH_ARG => hash, + PACKAGE_HASH_ARG => hash, ENTRY_POINT_ARG => entry_point, ARGS_ARG => Bytes::from(args_bytes), ATTACHED_VALUE_ARG => call_def.amount(), @@ -168,7 +174,7 @@ impl CasperVm { }; DeployItemBuilder::new() - .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT}) + .with_standard_payment(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT}) .with_authorization_keys(&[self.active_account_hash()]) .with_address(self.active_account_hash()) .with_session_bytes(session_code, args) @@ -176,7 +182,7 @@ impl CasperVm { .build() } else { DeployItemBuilder::new() - .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT}) + .with_standard_payment(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT}) .with_authorization_keys(&[self.active_account_hash()]) .with_address(self.active_account_hash()) .with_stored_versioned_contract_by_hash( @@ -189,7 +195,7 @@ impl CasperVm { .build() }; - let execute_request = ExecuteRequestBuilder::from_deploy_item(deploy_item) + let execute_request = ExecuteRequestBuilder::from_deploy_item(&deploy_item) .with_block_time(self.block_time) .build(); self.context.exec(execute_request).commit(); @@ -248,7 +254,9 @@ impl CasperVm { pub fn balance_of(&self, address: &Address) -> U512 { match address { Address::Account(account_hash) => self.get_account_cspr_balance(account_hash), - Address::Contract(contract_hash) => self.get_contract_cspr_balance(contract_hash) + Address::Contract(contract_package_hash) => { + self.get_contract_cspr_balance(contract_package_hash) + } } } @@ -257,7 +265,6 @@ impl CasperVm { /// Results an OdraError if the transfer fails. pub fn transfer(&mut self, to: Address, amount: U512) -> OdraResult<()> { let deploy_item = DeployItemBuilder::new() - .with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT}) .with_transfer_args(runtime_args! { "amount" => amount, "target" => to, @@ -268,7 +275,7 @@ impl CasperVm { .with_deploy_hash(self.next_hash()) .build(); - let execute_request = ExecuteRequestBuilder::from_deploy_item(deploy_item) + let execute_request = ExecuteRequestBuilder::from_deploy_item(&deploy_item) .with_block_time(self.block_time) .build(); self.context.exec(execute_request).commit(); @@ -295,7 +302,7 @@ impl CasperVm { } /// Returns the cost of the last deploy. - /// Keep in mind that this may be different from the cost of the deploy on the live network. + /// Keep in mind that this may be different from the cost of the transaction on the live network. /// This is NOT the amount of gas charged - see [last_call_contract_gas_used()](Self::last_call_contract_gas_used). pub fn last_call_contract_gas_cost(&self) -> U512 { self.context.last_exec_gas_cost().value() @@ -369,63 +376,85 @@ impl CasperVm { } fn get_account_cspr_balance(&self, account_hash: &AccountHash) -> U512 { - let account: Account = self.context.get_account(*account_hash).unwrap(); + let account: AddressableEntity = self + .context + .get_entity_by_account_hash(*account_hash) + .unwrap(); let purse = account.main_purse(); - let gas_used = self - .gas_used - .get(account_hash) - .copied() - .unwrap_or(U512::zero()); - self.context.get_purse_balance(purse) + gas_used + self.context.get_purse_balance(purse) + } + + fn get_contract_cspr_balance(&self, contract_package_hash: &ContractPackageHash) -> U512 { + // TODO: Addressable entity has main purse inside it, is it the same as ours for contracts? + let purse_key = self.package_named_key(*contract_package_hash, CONTRACT_MAIN_PURSE); + match purse_key { + None => U512::zero(), + Some(purse_key) => { + let purse_uref = purse_key.as_uref().unwrap_or_else(|| { + panic!( + "Contract doesn't have main purse uref under {} named key", + CONTRACT_MAIN_PURSE + ) + }); + self.context.get_purse_balance(*purse_uref) + } + } } - fn get_contract_cspr_balance(&self, contract_hash: &ContractPackageHash) -> U512 { - let contract_hash: ContractHash = self.get_contract_package_hash(contract_hash); - let contract: Contract = self.context.get_contract(contract_hash).unwrap(); - contract - .named_keys() - .get(consts::CONTRACT_MAIN_PURSE) - .and_then(|key| key.as_uref()) - .map(|purse| self.context.get_purse_balance(*purse)) - .unwrap_or_else(U512::zero) + fn get_contract_hash(&self, contract_package_hash: &ContractPackageHash) -> ContractHash { + ContractHash::new(contract_package_hash.value()) } - fn get_contract_package_hash(&self, contract_hash: &ContractPackageHash) -> ContractHash { - self.context - .get_contract_package(*contract_hash) - .unwrap() - .current_contract_hash() - .unwrap() + fn genesis_accounts( + key_pairs: &BTreeMap + ) -> Vec { + let mut accounts = Vec::new(); + for (_, (_, public_key)) in key_pairs.iter() { + accounts.push(GenesisAccount::account( + public_key.clone(), + Motes::new(DEFAULT_ACCOUNT_INITIAL_BALANCE), + None + )); + } + accounts + } + + /// Creates a new genesis config. + /// It is the same as the default one, but with the given genesis, + /// so we will know their private keys. + fn genesis_config(genesis_accounts: Vec) -> GenesisConfig { + GenesisConfigBuilder::default() + .with_accounts(genesis_accounts) + .with_wasm_config(*DEFAULT_WASM_CONFIG) + .with_system_config(*DEFAULT_SYSTEM_CONFIG) + .with_validator_slots(DEFAULT_VALIDATOR_SLOTS) + .with_auction_delay(DEFAULT_AUCTION_DELAY) + .with_locked_funds_period_millis(DEFAULT_LOCKED_FUNDS_PERIOD_MILLIS) + .with_round_seigniorage_rate(DEFAULT_ROUND_SEIGNIORAGE_RATE) + .with_unbonding_delay(DEFAULT_UNBONDING_DELAY) + .with_genesis_timestamp_millis(DEFAULT_GENESIS_TIMESTAMP_MILLIS) + .build() } fn new_instance() -> Self { - let mut genesis_config = DEFAULT_GENESIS_CONFIG.clone(); - let mut accounts: Vec
= Vec::new(); - let key_pairs = generate_key_pairs(20); - key_pairs - .iter() - .for_each(|(address, (secret_key, public_key))| { - accounts.push(*address); - let account = GenesisAccount::account( - public_key.clone(), - Motes::new(U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE)), - None - ); - genesis_config.ee_config_mut().push_account(account); - }); - - let run_genesis_request = RunGenesisRequest::new( - *DEFAULT_GENESIS_CONFIG_HASH, - genesis_config.protocol_version(), - genesis_config.take_ee_config(), + let key_pairs = generate_key_pairs(ACCOUNTS_NUMBER); + let genesis_accounts = Self::genesis_accounts(&key_pairs); + let accounts: Vec
= key_pairs.keys().copied().collect(); + + let genesis_config = Self::genesis_config(genesis_accounts); + + let run_genesis_request = GenesisRequest::new( + DEFAULT_GENESIS_CONFIG_HASH, + ProtocolVersion::V2_0_0, + genesis_config, DEFAULT_CHAINSPEC_REGISTRY.clone() ); let chainspec_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/chainspec.toml"); - let mut builder = InMemoryWasmTestBuilder::new_with_chainspec(chainspec_path, None); + let mut builder = LmdbWasmTestBuilder::new_temporary_with_chainspec(chainspec_path); - builder.run_genesis(&run_genesis_request).commit(); + builder.run_genesis(run_genesis_request).commit(); Self { active_account: accounts[0], @@ -449,14 +478,14 @@ impl CasperVm { self.error = None; let session_code = PathBuf::from(wasm_path); let deploy_item = DeployItemBuilder::new() - .with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT}) + .with_standard_payment(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT}) .with_authorization_keys(&[self.active_account_hash()]) .with_address(self.active_account_hash()) .with_session_code(session_code, args.clone()) .with_deploy_hash(self.next_hash()) .build(); - let execute_request = ExecuteRequestBuilder::from_deploy_item(deploy_item) + let execute_request = ExecuteRequestBuilder::from_deploy_item(&deploy_item) .with_block_time(self.block_time) .build(); let result = self.context.exec(execute_request).commit(); @@ -470,44 +499,102 @@ impl CasperVm { } impl CasperVm { - fn events_length(&self, contract_hash: &ContractHash) -> u32 { + fn get_package(&self, contract_package_hash: ContractPackageHash) -> Package { + let stored_value = self + .context + .query(None, Key::Package(contract_package_hash.value()), &[]) + .unwrap(); + + match stored_value { + StoredValue::Package(package) => package, + _ => panic!("Expected Package") + } + } + + fn get_current_contract( + &self, + contract_package_hash: ContractPackageHash + ) -> EntityWithNamedKeys { + let package = self.get_package(contract_package_hash); + let addressable_entity_hash = package + .current_entity_hash() + .expect("Package doesn't have current entity hash"); self.context - .query( - None, - Key::Hash(contract_hash.value()), - &[String::from(consts::EVENTS_LENGTH)] - ) + .get_entity_with_named_keys_by_entity_hash(addressable_entity_hash) + .expect("Couldn't find entity by hash") + } + + /// Gets current contract from contract package and + /// returns its named keys. + fn package_named_keys(&self, contract_package_hash: ContractPackageHash) -> NamedKeys { + let package = self.get_package(contract_package_hash); + let addressable_entity_hash = package + .current_entity_hash() + .expect("Package doesn't have current entity hash"); + let contract = self + .context + .get_entity_with_named_keys_by_entity_hash(addressable_entity_hash) + .expect("Entity not found"); + contract.named_keys().clone() + } + + fn package_named_key( + &self, + contract_package_hash: ContractPackageHash, + name: &str + ) -> Option { + let keys = self.package_named_keys(contract_package_hash); + let key = keys.get(name); + key.copied() + } + + fn events_length(&self, contract_package_hash: ContractPackageHash) -> u32 { + let key = self.package_named_key(contract_package_hash, EVENTS_LENGTH); + match key { + None => 0, + Some(key) => self.get_value(key) + } + } + + // TODO: Make this return Result + fn get_value(&self, key: Key) -> T { + let value = self.context.query(None, key, &[]); + value .unwrap() .as_cl_value() .unwrap() .clone() - .into_t() + .into_t::() .unwrap() } - fn panic_with_error( - &self, - error: OdraError, - entrypoint: &str, - contract_package_hash: ContractPackageHash - ) -> ! { - panic!( - "Revert: {:?} - {:?}::{}", - error, contract_package_hash, entrypoint - ) + // Make this return Result also + fn get_dict_value(&self, uref: URef, name: &str) -> T { + let value = self.context.query_dictionary_item(None, uref, name); + value + .unwrap() + .as_cl_value() + .unwrap() + .clone() + .into_t::() + .unwrap() + } + + fn panic_with_error(&self, error: OdraError, entrypoint: &str, package_hash: PackageHash) -> ! { + panic!("Revert: {:?} - {:?}::{}", error, package_hash, entrypoint) } } fn parse_error(err: engine_state::Error) -> OdraError { if let engine_state::Error::Exec(exec_err) = err { match exec_err { - engine_state::ExecError::Revert(ApiError::MissingArgument) => { + execution::ExecError::Revert(ApiError::MissingArgument) => { OdraError::ExecutionError(ExecutionError::MissingArg) } - engine_state::ExecError::Revert(ApiError::Mint(0)) => { + execution::ExecError::Revert(ApiError::Mint(0)) => { OdraError::VmError(VmError::BalanceExceeded) } - engine_state::ExecError::Revert(ApiError::User(code)) => match code { + execution::ExecError::Revert(ApiError::User(code)) => match code { x if x == ExecutionError::UnwrapError.code() => { OdraError::ExecutionError(ExecutionError::UnwrapError) } @@ -582,11 +669,11 @@ fn parse_error(err: engine_state::Error) -> OdraError { } _ => OdraError::ExecutionError(ExecutionError::User(code)) }, - engine_state::ExecError::InvalidContext => OdraError::VmError(VmError::InvalidContext), - engine_state::ExecError::NoSuchMethod(name) => { + execution::ExecError::InvalidContext => OdraError::VmError(VmError::InvalidContext), + execution::ExecError::NoSuchMethod(name) => { OdraError::VmError(VmError::NoSuchMethod(name)) } - engine_state::ExecError::MissingArgument { name } => { + execution::ExecError::MissingArgument { name } => { OdraError::ExecutionError(ExecutionError::MissingArg) } _ => OdraError::VmError(VmError::Other(format!("Casper ExecError: {}", exec_err))) diff --git a/odra-casper/utils/Cargo.toml b/odra-casper/utils/Cargo.toml new file mode 100644 index 00000000..6bd5093c --- /dev/null +++ b/odra-casper/utils/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "odra-casper-utils" +edition = "2021" +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +casper-types = { workspace = true } \ No newline at end of file diff --git a/odra-casper/utils/src/lib.rs b/odra-casper/utils/src/lib.rs new file mode 100644 index 00000000..fec5a596 --- /dev/null +++ b/odra-casper/utils/src/lib.rs @@ -0,0 +1,8 @@ +use casper_types::bytesrepr::FromBytes; +use casper_types::{CLTyped, CLValueError, StoredValue}; + +pub fn stored_value_into_t( + stored_value: StoredValue +) -> Result { + stored_value.into_cl_value().unwrap().into_t() +} diff --git a/odra-casper/wasm-env/Cargo.toml b/odra-casper/wasm-env/Cargo.toml index d8195f35..b53ba85d 100644 --- a/odra-casper/wasm-env/Cargo.toml +++ b/odra-casper/wasm-env/Cargo.toml @@ -13,11 +13,11 @@ lazy_static = { version = "1.4.0", features = [ "spin_no_std" ] } odra-core = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -casper-contract = { version = "4.0.0", default-features = false } +casper-contract = { workspace = true, default-features = false } ink_allocator = { version = "4.2.1", default-features = false } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -casper-contract = { version = "4.0.0", default-features = false, features = ["test-support"] } +casper-contract = { workspace = true, default-features = false, features = ["test-support"] } [lints.rust] missing_docs = "warn" diff --git a/odra-casper/wasm-env/src/host_functions.rs b/odra-casper/wasm-env/src/host_functions.rs index 9ddb105d..23b7f48e 100644 --- a/odra-casper/wasm-env/src/host_functions.rs +++ b/odra-casper/wasm-env/src/host_functions.rs @@ -20,14 +20,15 @@ use casper_contract::{ unwrap_or_revert::UnwrapOrRevert }; use core::mem::MaybeUninit; +use odra_core::casper_types::addressable_entity::NamedKeys; use odra_core::casper_types::bytesrepr::deserialize; +use odra_core::casper_types::contracts::{ContractPackageHash, ContractVersion}; +use odra_core::casper_types::system::Caller; use odra_core::casper_types::{ api_error, bytesrepr, bytesrepr::{Bytes, FromBytes, ToBytes}, - contracts::NamedKeys, - system::CallStackElement, - ApiError, CLTyped, CLValue, ContractPackageHash, ContractVersion, EntryPoints, Key, - RuntimeArgs, URef, DICTIONARY_ITEM_KEY_MAX_LENGTH, U512, UREF_SERIALIZED_LENGTH + ApiError, CLTyped, CLValue, EntryPoints, Key, PackageHash, RuntimeArgs, URef, + DICTIONARY_ITEM_KEY_MAX_LENGTH, U512, UREF_SERIALIZED_LENGTH }; use odra_core::ExecutionError::EmptyDictionaryName; use odra_core::{ @@ -65,7 +66,7 @@ pub fn install_contract( entry_points: EntryPoints, events: Schemas, init_args: Option -) -> ContractPackageHash { +) -> PackageHash { // Read arguments let package_hash_key: String = runtime::get_named_arg(consts::PACKAGE_HASH_KEY_NAME_ARG); let allow_key_override: bool = runtime::get_named_arg(consts::ALLOW_KEY_OVERRIDE_ARG); @@ -83,35 +84,38 @@ pub fn install_contract( // Create new contract. let access_uref_key = format!("{}_access_token", package_hash_key); if is_upgradable { + // TODO: Handle message topics storage::new_contract( entry_points, Some(named_keys), Some(package_hash_key.clone()), - Some(access_uref_key) + Some(access_uref_key), + None ); } else { + // TODO: Handle message topics storage::new_locked_contract( entry_points, Some(named_keys), Some(package_hash_key.clone()), - Some(access_uref_key) + Some(access_uref_key), + None ); } - // Read contract package hash from the storage. - let contract_package_hash: ContractPackageHash = runtime::get_key(&package_hash_key) + // Read package hash from the storage. + let package_hash: PackageHash = runtime::get_key(&package_hash_key) .unwrap_or_revert() - .into_hash() - .unwrap_or_revert() - .into(); + .into_package_hash() + .unwrap_or_revert(); if let Some(args) = init_args { - let init_access = create_constructor_group(contract_package_hash); - let _: () = runtime::call_versioned_contract(contract_package_hash, None, "init", args); - revoke_access_to_constructor_group(contract_package_hash, init_access); + let init_access = create_constructor_group(package_hash); + let _: () = runtime::call_versioned_contract(package_hash, None, "init", args); + revoke_access_to_constructor_group(package_hash, init_access); } - contract_package_hash + package_hash } /// Stops a contract execution and reverts the state with a given error. @@ -351,8 +355,8 @@ pub fn emit_event(event: &Bytes) { /// session/stored contracts. #[inline(always)] pub fn caller() -> Address { - let second_elem = take_call_stack_elem(1); - call_stack_element_to_address(second_elem) + let second_elem = take_nth_caller_from_stack(1); + caller_to_address(second_elem) } /// Calls a contract method by Address @@ -383,8 +387,8 @@ pub fn call_contract(address: Address, call_def: CallDef) -> Bytes { /// Gets the address of the currently run contract #[inline(always)] pub fn self_address() -> Address { - let first_elem = take_call_stack_elem(0); - call_stack_element_to_address(first_elem) + let first_elem = take_nth_caller_from_stack(0); + caller_to_address(first_elem) } /// Gets the balance of the current contract. @@ -555,16 +559,16 @@ fn deserialize_contract_result(bytes_written: usize) -> Vec { } } -fn take_call_stack_elem(n: usize) -> CallStackElement { +fn take_nth_caller_from_stack(n: usize) -> Caller { runtime::get_call_stack() .into_iter() .nth_back(n) .unwrap_or_revert() } -fn create_constructor_group(contract_package_hash: ContractPackageHash) -> URef { +fn create_constructor_group(package_hash: PackageHash) -> URef { storage::create_contract_user_group( - contract_package_hash, + package_hash, consts::CONSTRUCTOR_GROUP_NAME, 1, Default::default() @@ -574,42 +578,26 @@ fn create_constructor_group(contract_package_hash: ContractPackageHash) -> URef .unwrap_or_revert() } -fn revoke_access_to_constructor_group( - contract_package_hash: ContractPackageHash, - constructor_access: URef -) { +fn revoke_access_to_constructor_group(package_hash: PackageHash, constructor_access: URef) { let mut urefs = BTreeSet::new(); urefs.insert(constructor_access); - storage::remove_contract_user_group_urefs( - contract_package_hash, - consts::CONSTRUCTOR_GROUP_NAME, - urefs - ) - .unwrap_or_revert(); + storage::remove_contract_user_group_urefs(package_hash, consts::CONSTRUCTOR_GROUP_NAME, urefs) + .unwrap_or_revert(); } /// Returns address based on a [`CallStackElement`]. /// /// For `Session` and `StoredSession` variants it will return account hash, and for `StoredContract` /// case it will use contract hash as the address. -fn call_stack_element_to_address(call_stack_element: CallStackElement) -> Address { - match call_stack_element { - CallStackElement::Session { account_hash } => Address::try_from(account_hash) +fn caller_to_address(caller: Caller) -> Address { + match caller { + Caller::Initiator { account_hash } => Address::try_from(account_hash) .map_err(|e| ApiError::User(ExecutionError::from(e).code())) .unwrap_or_revert(), - CallStackElement::StoredSession { account_hash, .. } => { - // Stored session code acts in account's context, so if stored session - // wants to interact, caller's address will be used. - Address::try_from(account_hash) - .map_err(|e| ApiError::User(ExecutionError::from(e).code())) - .unwrap_or_revert() - } - CallStackElement::StoredContract { - contract_package_hash, - .. - } => Address::try_from(contract_package_hash) - .map_err(|e| ApiError::User(ExecutionError::from(e).code())) - .unwrap_or_revert() + Caller::Entity { + entity_hash, + package_hash + } => Address::from(package_hash) } } diff --git a/odra-casper/wasm-env/src/lib.rs b/odra-casper/wasm-env/src/lib.rs index 408f08f9..df953808 100644 --- a/odra-casper/wasm-env/src/lib.rs +++ b/odra-casper/wasm-env/src/lib.rs @@ -21,10 +21,10 @@ pub use wasm_contract_env::WasmContractEnv; #[allow(unused_imports)] use ink_allocator; -/// Panic handler for the WASM target architecture. -#[cfg(target_arch = "wasm32")] -#[panic_handler] -#[no_mangle] -pub fn panic(_info: &core::panic::PanicInfo) -> ! { - core::intrinsics::abort(); -} +// /// Panic handler for the WASM target architecture. +// #[cfg(target_arch = "wasm32")] +// #[panic_handler] +// #[no_mangle] +// pub fn panic(_info: &core::panic::PanicInfo) -> ! { +// core::intrinsics::abort(); +// } diff --git a/odra-macros/src/ast/wasm_parts.rs b/odra-macros/src/ast/wasm_parts.rs index 4bf56654..5aa729ce 100644 --- a/odra-macros/src/ast/wasm_parts.rs +++ b/odra-macros/src/ast/wasm_parts.rs @@ -251,6 +251,7 @@ impl TryFrom<&'_ FnIR> for NewEntryPointItem { param_ret_ty, param_access, utils::expr::entry_point_contract(), + utils::expr::entry_point_payment(), ]); Ok(Self { ty: utils::ty::entry_point(), @@ -288,14 +289,18 @@ mod test { vec![odra::args::parameter:: >("total_supply")].into_iter().filter_map(|x| x).collect(), <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Groups(vec![odra::casper_types::Group::new("constructor_group")]), - odra::casper_types::EntryPointType::Contract + odra::casper_types::EntryPointType::Called, + odra::casper_types::EntryPointPayment::Caller, + )); entry_points.add_entry_point(odra::casper_types::EntryPoint::new( "total_supply", vec![], ::cl_type(), odra::casper_types::EntryPointAccess::Public, - odra::casper_types::EntryPointType::Contract + odra::casper_types::EntryPointType::Called, + odra::casper_types::EntryPointPayment::Caller, + )); entry_points .add_entry_point( @@ -304,7 +309,9 @@ mod test { vec![], <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Public, - odra::casper_types::EntryPointType::Contract, + odra::casper_types::EntryPointType::Called, + odra::casper_types::EntryPointPayment::Caller, + ), ); entry_points @@ -318,7 +325,9 @@ mod test { ].into_iter().filter_map(|x| x).collect(), <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Public, - odra::casper_types::EntryPointType::Contract, + odra::casper_types::EntryPointType::Called, + odra::casper_types::EntryPointPayment::Caller, + ), ); entry_points @@ -331,7 +340,9 @@ mod test { ].into_iter().filter_map(|x| x).collect(), <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Public, - odra::casper_types::EntryPointType::Contract, + odra::casper_types::EntryPointType::Called, + odra::casper_types::EntryPointPayment::Caller, + ), ); entry_points @@ -420,7 +431,9 @@ mod test { vec![], ::cl_type(), odra::casper_types::EntryPointAccess::Public, - odra::casper_types::EntryPointType::Contract + odra::casper_types::EntryPointType::Called, + odra::casper_types::EntryPointPayment::Caller, + )); entry_points .add_entry_point( @@ -429,7 +442,9 @@ mod test { vec![], <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Public, - odra::casper_types::EntryPointType::Contract, + odra::casper_types::EntryPointType::Called, + odra::casper_types::EntryPointPayment::Caller, + ), ); entry_points @@ -488,7 +503,8 @@ mod test { vec![], ::cl_type(), odra::casper_types::EntryPointAccess::Public, - odra::casper_types::EntryPointType::Contract + odra::casper_types::EntryPointType::Called, + odra::casper_types::EntryPointPayment::Caller, )); entry_points .add_entry_point( @@ -497,7 +513,8 @@ mod test { vec![],
::cl_type(), odra::casper_types::EntryPointAccess::Public, - odra::casper_types::EntryPointType::Contract, + odra::casper_types::EntryPointType::Called, + odra::casper_types::EntryPointPayment::Caller, ), ); entry_points @@ -507,7 +524,9 @@ mod test { vec![odra::args::parameter::
("new_owner")].into_iter().filter_map(|x| x).collect(), <() as odra::casper_types::CLTyped>::cl_type(), odra::casper_types::EntryPointAccess::Public, - odra::casper_types::EntryPointType::Contract, + odra::casper_types::EntryPointType::Called, + odra::casper_types::EntryPointPayment::Caller, + ), ); entry_points @@ -517,7 +536,9 @@ mod test { vec![], ::cl_type(), odra::casper_types::EntryPointAccess::Public, - odra::casper_types::EntryPointType::Contract, + odra::casper_types::EntryPointType::Called, + odra::casper_types::EntryPointPayment::Caller, + ), ); entry_points @@ -527,7 +548,9 @@ mod test { vec![], ::cl_type(), odra::casper_types::EntryPointAccess::Public, - odra::casper_types::EntryPointType::Contract, + odra::casper_types::EntryPointType::Called, + odra::casper_types::EntryPointPayment::Caller, + ), ); entry_points diff --git a/odra-macros/src/lib.rs b/odra-macros/src/lib.rs index f87456a1..8b7d98e3 100644 --- a/odra-macros/src/lib.rs +++ b/odra-macros/src/lib.rs @@ -25,7 +25,7 @@ macro_rules! span_error { /// /// Each module consists of two parts: /// 1. Module definition - a struct which composition of stored values (Vars and Mappings) -/// and modules. +/// and modules. /// 2. Module implementation - an implementation block. /// /// The macro produces all the required code to use the module as a standalone smart contract. diff --git a/odra-macros/src/utils/expr.rs b/odra-macros/src/utils/expr.rs index bdc0097f..e4da4465 100644 --- a/odra-macros/src/utils/expr.rs +++ b/odra-macros/src/utils/expr.rs @@ -34,7 +34,12 @@ pub fn new_entry_points() -> syn::Expr { pub fn entry_point_contract() -> syn::Expr { let ty = super::ty::entry_point_type(); - parse_quote!(#ty::Contract) + parse_quote!(#ty::Called) +} + +pub fn entry_point_payment() -> syn::Expr { + let ty = super::ty::entry_point_payment(); + parse_quote!(#ty::Caller) } pub fn entry_point_public() -> syn::Expr { diff --git a/odra-macros/src/utils/ty.rs b/odra-macros/src/utils/ty.rs index 8d5a6e7f..55cdd7a9 100644 --- a/odra-macros/src/utils/ty.rs +++ b/odra-macros/src/utils/ty.rs @@ -89,6 +89,10 @@ pub fn entry_point_type() -> syn::Type { parse_quote!(odra::casper_types::EntryPointType) } +pub fn entry_point_payment() -> syn::Type { + parse_quote!(odra::casper_types::EntryPointPayment) +} + pub fn group() -> syn::Type { parse_quote!(odra::casper_types::Group) } diff --git a/odra-schema/Cargo.toml b/odra-schema/Cargo.toml index d13a4200..e7448eb1 100644 --- a/odra-schema/Cargo.toml +++ b/odra-schema/Cargo.toml @@ -9,7 +9,7 @@ homepage.workspace = true repository.workspace = true [dependencies] -casper-contract-schema = "0.1.0" +casper-contract-schema = { workspace = true } casper-types = { workspace = true } odra-core = { workspace = true } num-traits = { workspace = true } diff --git a/odra-schema/src/ty.rs b/odra-schema/src/ty.rs index a24f9540..a070caae 100644 --- a/odra-schema/src/ty.rs +++ b/odra-schema/src/ty.rs @@ -2,7 +2,8 @@ use std::collections::BTreeMap; pub use casper_contract_schema; use casper_contract_schema::NamedCLType; -use casper_types::{bytesrepr::Bytes, ContractHash, Key, PublicKey, URef, U128, U256, U512}; +use casper_types::contracts::ContractHash; +use casper_types::{bytesrepr::Bytes, Key, PublicKey, URef, U128, U256, U512}; use odra_core::{args::Maybe, Address}; /// Trait for types that can be represented as a NamedCLType. diff --git a/odra-vm/src/vm/odra_vm_state.rs b/odra-vm/src/vm/odra_vm_state.rs index 0caaf00b..5a09d2f1 100644 --- a/odra-vm/src/vm/odra_vm_state.rs +++ b/odra-vm/src/vm/odra_vm_state.rs @@ -74,6 +74,7 @@ impl OdraVmState { pub fn emit_event(&mut self, event_data: &Bytes) { let contract_address = self.callstack.current().address(); + #[allow(clippy::manual_inspect)] let events = self.events.get_mut(contract_address).map(|events| { events.push(event_data.clone()); events diff --git a/odra-vm/src/vm/utils.rs b/odra-vm/src/vm/utils.rs index 4165c820..a5943d9f 100644 --- a/odra-vm/src/vm/utils.rs +++ b/odra-vm/src/vm/utils.rs @@ -1,5 +1,5 @@ use odra_core::{ - casper_types::{account::AccountHash, ContractPackageHash}, + casper_types::{account::AccountHash, contracts::ContractPackageHash}, Address }; diff --git a/rust-toolchain b/rust-toolchain index e02da0b2..542e8cbc 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2024-04-26 \ No newline at end of file +nightly-2024-07-02 From bff634a0c5c2d0faea6add731dc43673d5bb8066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Tue, 30 Jul 2024 11:41:01 +0200 Subject: [PATCH 02/22] Improved errors. --- core/src/address.rs | 5 +- .../livenet-env/src/livenet_contract_env.rs | 8 +- odra-casper/rpc-client/src/casper_client.rs | 312 +++++++++--------- 3 files changed, 165 insertions(+), 160 deletions(-) diff --git a/core/src/address.rs b/core/src/address.rs index acfd43af..a19bb73a 100644 --- a/core/src/address.rs +++ b/core/src/address.rs @@ -87,9 +87,8 @@ impl Address { /// Returns the inner contract hash if `self` is the `Contract` variant. pub fn as_package_hash(&self) -> Option { - self.as_contract_package_hash().map(|cph| { - PackageHash::new(cph.value()) - }) + self.as_contract_package_hash() + .map(|cph| PackageHash::new(cph.value())) } /// Returns true if the address is a contract address. diff --git a/odra-casper/livenet-env/src/livenet_contract_env.rs b/odra-casper/livenet-env/src/livenet_contract_env.rs index 3e5b2880..23c424dc 100644 --- a/odra-casper/livenet-env/src/livenet_contract_env.rs +++ b/odra-casper/livenet-env/src/livenet_contract_env.rs @@ -24,12 +24,8 @@ impl ContractContext for LivenetContractEnv { fn get_value(&self, key: &[u8]) -> Option { let callstack = self.callstack.borrow(); let client = self.casper_client.borrow(); - self.runtime.block_on(async { - client - .get_value(callstack.current().address(), key) - .await - .ok() - }) + self.runtime + .block_on(async { client.get_value(callstack.current().address(), key).await }) } fn set_value(&self, _key: &[u8], _value: Bytes) { diff --git a/odra-casper/rpc-client/src/casper_client.rs b/odra-casper/rpc-client/src/casper_client.rs index 48b944bb..1606f36d 100644 --- a/odra-casper/rpc-client/src/casper_client.rs +++ b/odra-casper/rpc-client/src/casper_client.rs @@ -15,7 +15,7 @@ use crate::casper_client::configuration::CasperClientConfiguration; use crate::log; use anyhow::{Context, Result}; use casper_client::cli::{ - get_balance, get_deploy, get_dictionary_item, get_entity, get_state_root_hash, + get_balance, get_deploy, get_dictionary_item, get_entity, get_node_status, get_state_root_hash, query_global_state, DictionaryItemStrParams }; use casper_client::rpcs::results::{GetDeployResult, GetDictionaryItemResult, PutDeployResult}; @@ -26,6 +26,7 @@ use casper_types::execution::ExecutionResultV1::{Failure, Success}; use casper_types::StoredValue::CLValue; use casper_types::{execution::ExecutionResult, EntityAddr}; use casper_types::{Deploy, DeployHash, ExecutableDeployItem, StoredValue, TimeDiff, Timestamp}; +use odra_core::casper_event_standard::EVENTS_DICT; use odra_core::casper_types::{sign, URef}; use odra_core::{ casper_types::{ @@ -79,20 +80,14 @@ impl CasperClient { } } - /// Gets a value from the storage - pub async fn get_value(&self, address: &Address, key: &[u8]) -> Result { - Ok(self - .get_dictionary_value(address, "state", key) - .await - .unwrap()) + /// Gets a value from the Odra storage (`state` dictionary) + pub async fn get_value(&self, address: &Address, key: &[u8]) -> Option { + self.get_dictionary_value(address, STATE_KEY, key).await } - /// Gets a value from a named key + /// Gets a value from a named key of an account or a contract pub async fn get_named_value(&self, address: &Address, name: &str) -> Option { - let entity_hash = self - .query_global_state_for_entity_addr(address) - .await - .unwrap(); + let entity_hash = self.query_global_state_for_entity_addr(address).await; let stored_value = self .query_global_state(&entity_hash.to_formatted_string(), Some(name.to_string())) .await; @@ -115,14 +110,22 @@ impl CasperClient { &self .caller() .as_account_hash() - .unwrap() + .expect( + format!( + "Tried to query for proxy results from contract, it should be an account: {:?}", + self.caller() + ) + .as_str() + ) .to_formatted_string(), Some(RESULT_KEY.to_string()) ) .await; match stored_value { CLValue(value) => { - let bytes: Bytes = value.into_t().unwrap(); + let bytes: Bytes = value.into_t().expect( + format!("Value stored in result key is not Bytes: {:?}", value).as_str() + ); bytes } _ => panic!("Value stored in result key is not a CLValue") @@ -136,7 +139,8 @@ impl CasperClient { dictionary_name: &str, key: &[u8] ) -> Option { - let key = String::from_utf8(key.to_vec()).unwrap(); + let key = String::from_utf8(key.to_vec()) + .expect(format!("Couldn't convert key to string: {:?}", key).as_str()); self.query_dict(address, dictionary_name.to_string(), key) .await .ok() @@ -149,12 +153,12 @@ impl CasperClient { /// Node rpc address. pub fn node_address_rpc(&self) -> String { - format!("{}/rpc", self.configuration.node_address) + format!("{}/rpc", self.node_address()) } /// Node address. pub fn node_address(&self) -> &str { - &self.configuration.node_address + &self.node_address() } /// Chain name. @@ -220,27 +224,25 @@ impl CasperClient { // TODO: Use rpc when it will be public to do this in one call let main_purse = self.get_main_purse(address).await.to_formatted_string(); get_balance( - // TODO: set rpc id to a random number - "", - &self.configuration.node_address, + &self.rpc_id(), + &self.node_address(), self.configuration.verbosity(), &self.get_state_root_hash().await, &main_purse ) .await - .unwrap() + .expect(format!("Couldn't get balance for address: {:?}", address).as_str()) .result .balance_value } + /// Gets an uref of a main purse of an account or a contract. pub async fn get_main_purse(&self, address: &Address) -> URef { let purse_uref = self .query_global_state(&address.to_formatted_string(), None) .await; match purse_uref { - CLValue(value) => { - value.into_t().unwrap() - } + CLValue(value) => value.into_t().unwrap(), StoredValue::AddressableEntity(entity) => entity.main_purse(), _ => panic!("Not an addressable entity") } @@ -270,55 +272,72 @@ impl CasperClient { /// Returns the current block_time pub async fn get_block_time(&self) -> u64 { - let request = json!( - { - "jsonrpc": "2.0", - "method": "info_get_status", - "id": 1, - } - ); - let result: Value = self.post_request(request).await; - let result = result["last_added_block_info"]["timestamp"] + get_node_status( + &self.rpc_id(), + self.node_address(), + self.configuration.verbosity() + ) + .await + .expect( + format!( + "Couldn't get block time from node: {:?}", + self.node_address() + ) .as_str() - .unwrap_or_else(|| { - panic!( - "Couldn't get block time - malformed JSON response: {:?}", - result - ) - }); - let system_time = humantime::parse_rfc3339_weak(result).expect("Couldn't parse block time"); - system_time - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap_or_else(|_| panic!("Couldn't parse block time")) - .as_millis() as u64 + ) + .result + .last_added_block_info + .expect( + format!( + "Couldn't get last added block info from node: {:?}", + self.node_address() + ) + .as_str() + ) + .timestamp + .millis() } /// Get the event bytes from storage pub async fn get_event(&self, contract_address: &Address, index: u32) -> OdraResult { - self.query_dict(contract_address, "__events".to_string(), index.to_string()) + self.query_dict(contract_address, EVENTS.to_string(), index.to_string()) .await } /// Get the events count from storage pub async fn events_count(&self, contract_address: &Address) -> Option { - self.get_named_value(contract_address, "__events_length") + self.get_named_value(contract_address, EVENTS_LENGTH) .await - .map(|bytes| deserialize_from_slice(&bytes).unwrap()) + .map(|bytes| { + deserialize_from_slice(&bytes).expect( + format!( + "Couldn't deserialize events count for contract: {:?}. Bytes: {:?}", + contract_address, bytes + ) + .as_str() + ) + }) } /// Query the node for the current state root hash. pub async fn get_state_root_hash(&self) -> String { let digest = get_state_root_hash( - "", - &self.configuration.node_address, - Verbosity::Low as u64, + &self.rpc_id(), + &self.node_address(), + self.configuration.verbosity(), "" ) .await - .unwrap() + .expect( + format!( + "Couldn't get state root hash from node: {:?}", + self.node_address() + ) + .as_str() + ) .result .state_root_hash - .unwrap(); + .expect(format!("Node doesn't have the root hash: {:?}", self.node_address()).as_str()); base16::encode_lower(&digest) } @@ -330,79 +349,123 @@ impl CasperClient { dictionary_name: String, dictionary_item_key: String ) -> OdraResult { - let entity_addr = self - .query_global_state_for_entity_addr(address) - .await - .unwrap(); + let entity_addr = self.query_global_state_for_entity_addr(address).await; let params = DictionaryItemStrParams::EntityNamedKey { entity_addr: &entity_addr.to_formatted_string(), dictionary_name: &dictionary_name, dictionary_item_key: &dictionary_item_key }; let r = get_dictionary_item( - "", - &self.configuration.node_address, - Verbosity::Low as u64, + &self.rpc_id(), + &self.node_address(), + self.configuration.verbosity(), &self.get_state_root_hash().await, params ) .await; - r.unwrap() - .result - .stored_value - .into_cl_value() - .unwrap() - .into_t() - .map_err(|_| OdraError::ExecutionError(ExecutionError::TypeMismatch)) + r.expect( + format!( + "Couldn't get dictionary item for contract: {:?}", + address.to_formatted_string() + ) + .as_str() + ) + .result + .stored_value + .into_cl_value() + .map_err(|_| OdraError::ExecutionError(ExecutionError::TypeMismatch)) + .into_t() + .map_err(|_| OdraError::ExecutionError(ExecutionError::TypeMismatch)) } /// Query the node for the transaction state. pub async fn get_deploy(&self, deploy_hash: DeployHash) -> GetDeployResult { let t = get_deploy( - "", - &self.configuration.node_address.clone(), - Verbosity::Low as u64, + &self.rpc_id(), + &self.node_address().clone(), + self.configuration.verbosity(), &deploy_hash.to_hex_string(), true ) .await; - - t.unwrap().result + t.expect(format!("Couldn't get deploy: {:?}", deploy_hash.to_hex_string()).as_str()) + .result } /// Discover the contract address by name. async fn get_contract_address(&self, key_name: &str) -> Address { - let key_name = format!("{}_package_hash", key_name); + let key_name = format!("{}_{}", key_name, PACKAGE_HASH_ARG); - // TODO: set rpc id to a random number let result = get_entity( - "", - &self.configuration.node_address, - Verbosity::Low as u64, + &self.rpc_id(), + &self.node_address(), + self.configuration.verbosity(), "", &self.public_key().to_hex_string() ) .await - .unwrap() + .expect( + format!( + "Couldn't get entity for public key: {:?}", + self.public_key().to_hex_string() + ) + .as_str() + ) .result; - let account = result.entity_result.addressable_entity().unwrap(); + let account = result.entity_result.addressable_entity().expect( + format!( + "Couldn't get addressable entity for public key: {:?}", + self.public_key().to_hex_string() + ) + .as_str() + ); - let key = account.named_keys.get(&key_name).unwrap(); + let key = account.named_keys.get(&key_name).expect( + format!( + "Couldn't get key {:?} for account: {:?}", + key_name, + self.public_key().to_hex_string() + ) + .as_str() + ); - Address::from(key.into_package_hash().unwrap()) + Address::from( + key.into_package_hash().expect( + format!( + "Couldn't get package hash from key {:?} for account: {:?}", + key_name, + self.public_key().to_hex_string() + ) + .as_str() + ) + ) } /// Find the entity addr in global state for an address - async fn query_global_state_for_entity_addr(&self, address: &Address) -> Result { + async fn query_global_state_for_entity_addr(&self, address: &Address) -> EntityAddr { let result = self .query_global_state(&address.to_formatted_string(), None) .await; match result { - StoredValue::Package(package) => Ok(EntityAddr::SmartContract( - package.current_entity_hash().unwrap().value() - )), - _ => Err(anyhow::anyhow!("Not a package")) + StoredValue::Package(package) => EntityAddr::SmartContract( + package + .current_entity_hash() + .expect( + format!( + "Couldn't get entity addr for address: {:?}", + address.to_formatted_string() + ) + .as_str() + ) + .value() + ), + _ => { + panic!( + "Couldn't get entity addr for address: {:?}", + address.to_formatted_string() + ) + } } } @@ -530,7 +593,7 @@ impl CasperClient { // Todo: set rpc id to a random number query_global_state( "", - &self.configuration.node_address, + &self.node_address(), Verbosity::Low as u64, "", &self.get_state_root_hash().await, @@ -538,75 +601,17 @@ impl CasperClient { &path.unwrap_or_default() ) .await - .unwrap() + .expect( + format!( + "Couldn't query global state for key: {:?}, and path: {:?}", + key, path + ) + .as_str() + ) .result .stored_value } - async fn _query_state_dictionary(&self, address: &Address, key: &str) -> Result { - let contract_hash = self.query_global_state_for_entity_addr(address).await?; - let contract_hash = contract_hash - .to_formatted_string() - .replace("contract-", "hash-"); - let params = DictionaryItemIdentifier::ContractNamedKey { - key: contract_hash, - dictionary_name: "state".to_string(), - dictionary_item_key: key.to_string() - }; - // TODO: set rpc id to a random number - let request = json!( - { - "jsonrpc": "2.0", - "method": "state_get_dictionary_item", - "params": params, - "id": 1, - } - ); - - let result = self - .safe_post_request(request.clone()) - .await - .get_result() - .and_then(|result| { - serde_json::from_value::(result.clone()).ok() - }); - result - .context("Couldn't get dictionary item") - .and_then(|result| { - let result_as_json = - serde_json::to_value(result).context("Couldn't parse result")?; - let result = result_as_json["stored_value"]["CLValue"]["bytes"] - .as_str() - .context("Couldn't get bytes")?; - let bytes = hex::decode(result).context("Couldn't decode bytes")?; - let (value, _) = FromBytes::from_bytes(&bytes) - .map_err(|_| anyhow::anyhow!("Couldn't parse bytes"))?; - - Ok(value) - }) - } - - async fn _query_uref(&self, _uref: URef) -> OdraResult { - todo!() - // let result = self.query_global_state(&CasperKey::URef(uref)).await; - // match result.stored_value { - // CLValue(value) => value - // .into_t() - // .map_err(|_| OdraError::ExecutionError(ExecutionError::UnwrapError)), - // _ => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) - // } - } - - async fn _query_uref_bytes(&self, _uref: URef) -> OdraResult { - todo!() - // let key = CasperKey::URef(uref); - // let result = self.query_global_state(&key).await; - // match result.stored_value { - // CLValue(value) => Ok(value.inner_bytes().as_slice().into()), - // _ => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) - // } - } - async fn wait_for_deploy(&self, deploy_hash: DeployHash) -> ExecutionResult { let deploy_hash_str = format!("{:?}", deploy_hash.inner()); let time_diff = Duration::from_secs(15); @@ -755,6 +760,11 @@ impl CasperClient { fn secret_keys(&self) -> &Vec { &self.configuration.secret_keys } + + // TODO: Maybe make it random to be in line with rpc spec? + fn rpc_id(&self) -> String { + "1".to_string() + } } #[cfg(feature = "std")] From 95ae3d50d70889dd52284c7006947c7ba36f1375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Tue, 30 Jul 2024 12:18:46 +0200 Subject: [PATCH 03/22] Unified rust-toolchain across packages. --- benchmark/rust-toolchain | 1 - 1 file changed, 1 deletion(-) delete mode 100644 benchmark/rust-toolchain diff --git a/benchmark/rust-toolchain b/benchmark/rust-toolchain deleted file mode 100644 index e02da0b2..00000000 --- a/benchmark/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly-2024-04-26 \ No newline at end of file From f10d7dbaf202744e1641b12ba8cefc4e52103f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Tue, 30 Jul 2024 13:12:30 +0200 Subject: [PATCH 04/22] Clippy fixes. --- Cargo.toml | 6 +- benchmark/Cargo.toml | 2 +- examples/ourcoin/bin/build_schema.rs | 2 - .../bin/proxy_caller_with_return.rs | 1 - odra-casper/rpc-client/src/casper_client.rs | 182 +++++++++--------- 5 files changed, 97 insertions(+), 96 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8886139b..49d19f16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "odra", "odra-vm", + "benchmark", "core", "examples", "examples/ourcoin", @@ -16,9 +17,8 @@ members = [ "odra-test", "odra-build", "odra-casper/utils", - "odra-casper/proxy-caller" ] -exclude = [ "examples", "modules", "benchmark", "odra-casper/proxy-caller", "templates/blank", "templates/full", "templates/workspace", "tests"] +exclude = ["odra-casper/proxy-caller", "templates/blank", "templates/full", "templates/workspace", "tests"] resolver = "2" [workspace.package] @@ -42,7 +42,7 @@ casper-types = "5.0.0" casper-execution-engine = { git = "https://github.com/casper-network/casper-node.git", branch = "rustSDK-feat-2.0" } casper-engine-test-support = { git = "https://github.com/casper-network/casper-node.git", branch = "rustSDK-feat-2.0" } casper-storage = { git = "https://github.com/casper-network/casper-node.git", branch = "rustSDK-feat-2.0" } -casper-event-standard = { path = "../../casper-event-standard/casper-event-standard" } +casper-event-standard = { git = "https://github.com/odradev/casper-event-standard.git", branch = "rustSDK-feat-2.0" } casper-client = { git = "https://github.com/casper-ecosystem/casper-client-rs.git", branch = "rustSDK-feat-2.0" } blake2 = "0.10.6" log = "0.4.20" diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 5822ffd6..60e05ecc 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" odra = { path = "../odra" } odra-test = { path = "../odra-test", optional = true } odra-modules = { path = "../modules" } -base64 = { workspace = true, default-features = false, features = ["alloc"] } +base64 = { workspace = true, features = ["alloc"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] serde_json = { workspace = true } diff --git a/examples/ourcoin/bin/build_schema.rs b/examples/ourcoin/bin/build_schema.rs index 249faec9..8605a467 100644 --- a/examples/ourcoin/bin/build_schema.rs +++ b/examples/ourcoin/bin/build_schema.rs @@ -1,6 +1,4 @@ #![doc = "Binary for building schema definitions from odra contracts."] -#[allow(unused_imports)] -use ourcoin; #[cfg(not(target_arch = "wasm32"))] extern "Rust" { diff --git a/odra-casper/proxy-caller/bin/proxy_caller_with_return.rs b/odra-casper/proxy-caller/bin/proxy_caller_with_return.rs index 11c142cf..6c79186f 100644 --- a/odra-casper/proxy-caller/bin/proxy_caller_with_return.rs +++ b/odra-casper/proxy-caller/bin/proxy_caller_with_return.rs @@ -10,7 +10,6 @@ extern crate alloc; use odra_casper_proxy_caller::{ call_versioned_contract_ret_bytes, ensure_cargo_purse_is_empty, set_key, ProxyCall }; -use odra_casper_wasm_env::host_functions::revert; use odra_core::casper_types::bytesrepr::Bytes; use odra_core::casper_types::contracts::ContractPackageHash; use odra_core::consts::RESULT_KEY; diff --git a/odra-casper/rpc-client/src/casper_client.rs b/odra-casper/rpc-client/src/casper_client.rs index 1606f36d..a55551c0 100644 --- a/odra-casper/rpc-client/src/casper_client.rs +++ b/odra-casper/rpc-client/src/casper_client.rs @@ -2,7 +2,6 @@ use std::collections::BTreeMap; use std::time::Duration; -use std::time::SystemTime; use itertools::Itertools; use jsonrpc_lite::JsonRpc; @@ -13,20 +12,17 @@ use serde_json::{json, Value}; use crate::casper_client::configuration::CasperClientConfiguration; use crate::log; -use anyhow::{Context, Result}; use casper_client::cli::{ get_balance, get_deploy, get_dictionary_item, get_entity, get_node_status, get_state_root_hash, query_global_state, DictionaryItemStrParams }; -use casper_client::rpcs::results::{GetDeployResult, GetDictionaryItemResult, PutDeployResult}; -use casper_client::rpcs::DictionaryItemIdentifier; +use casper_client::rpcs::results::{GetDeployResult, PutDeployResult}; use casper_client::Verbosity; use casper_types::bytesrepr::deserialize_from_slice; use casper_types::execution::ExecutionResultV1::{Failure, Success}; use casper_types::StoredValue::CLValue; use casper_types::{execution::ExecutionResult, EntityAddr}; use casper_types::{Deploy, DeployHash, ExecutableDeployItem, StoredValue, TimeDiff, Timestamp}; -use odra_core::casper_event_standard::EVENTS_DICT; use odra_core::casper_types::{sign, URef}; use odra_core::{ casper_types::{ @@ -110,24 +106,21 @@ impl CasperClient { &self .caller() .as_account_hash() - .expect( - format!( + .unwrap_or_else(|| { + panic!( "Tried to query for proxy results from contract, it should be an account: {:?}", self.caller() ) - .as_str() - ) + }) .to_formatted_string(), Some(RESULT_KEY.to_string()) ) .await; match stored_value { - CLValue(value) => { - let bytes: Bytes = value.into_t().expect( - format!("Value stored in result key is not Bytes: {:?}", value).as_str() - ); - bytes - } + CLValue(value) => value + .clone() + .into_t() + .unwrap_or_else(|_| panic!("Couldn't get bytes from CLValue: {:?}", value)), _ => panic!("Value stored in result key is not a CLValue") } } @@ -140,7 +133,7 @@ impl CasperClient { key: &[u8] ) -> Option { let key = String::from_utf8(key.to_vec()) - .expect(format!("Couldn't convert key to string: {:?}", key).as_str()); + .unwrap_or_else(|_| panic!("Couldn't convert key to string: {:?}", key)); self.query_dict(address, dictionary_name.to_string(), key) .await .ok() @@ -158,7 +151,7 @@ impl CasperClient { /// Node address. pub fn node_address(&self) -> &str { - &self.node_address() + &self.configuration.node_address } /// Chain name. @@ -225,13 +218,18 @@ impl CasperClient { let main_purse = self.get_main_purse(address).await.to_formatted_string(); get_balance( &self.rpc_id(), - &self.node_address(), + self.node_address(), self.configuration.verbosity(), &self.get_state_root_hash().await, &main_purse ) .await - .expect(format!("Couldn't get balance for address: {:?}", address).as_str()) + .unwrap_or_else(|_| { + panic!( + "Couldn't get balance for address: {:?}", + address.to_formatted_string() + ) + }) .result .balance_value } @@ -278,22 +276,20 @@ impl CasperClient { self.configuration.verbosity() ) .await - .expect( - format!( + .unwrap_or_else(|_| { + panic!( "Couldn't get block time from node: {:?}", self.node_address() ) - .as_str() - ) + }) .result .last_added_block_info - .expect( - format!( + .unwrap_or_else(|| { + panic!( "Couldn't get last added block info from node: {:?}", self.node_address() ) - .as_str() - ) + }) .timestamp .millis() } @@ -309,13 +305,12 @@ impl CasperClient { self.get_named_value(contract_address, EVENTS_LENGTH) .await .map(|bytes| { - deserialize_from_slice(&bytes).expect( - format!( - "Couldn't deserialize events count for contract: {:?}. Bytes: {:?}", + deserialize_from_slice(&bytes).unwrap_or_else(|_| { + panic!( + "Couldn't deserialize events count for contract: {:?}, bytes: {:?}", contract_address, bytes ) - .as_str() - ) + }) }) } @@ -323,21 +318,25 @@ impl CasperClient { pub async fn get_state_root_hash(&self) -> String { let digest = get_state_root_hash( &self.rpc_id(), - &self.node_address(), + self.node_address(), self.configuration.verbosity(), "" ) .await - .expect( - format!( + .unwrap_or_else(|_| { + panic!( "Couldn't get state root hash from node: {:?}", self.node_address() ) - .as_str() - ) + }) .result .state_root_hash - .expect(format!("Node doesn't have the root hash: {:?}", self.node_address()).as_str()); + .unwrap_or_else(|| { + panic!( + "Couldn't get state root hash from node: {:?}", + self.node_address() + ) + }); base16::encode_lower(&digest) } @@ -357,24 +356,28 @@ impl CasperClient { }; let r = get_dictionary_item( &self.rpc_id(), - &self.node_address(), + self.node_address(), self.configuration.verbosity(), &self.get_state_root_hash().await, params ) .await; - r.expect( - format!( + r.unwrap_or_else(|_| { + panic!( "Couldn't get dictionary item for contract: {:?}", address.to_formatted_string() ) - .as_str() - ) + }) .result .stored_value .into_cl_value() - .map_err(|_| OdraError::ExecutionError(ExecutionError::TypeMismatch)) + .unwrap_or_else(|| { + panic!( + "Couldn't get CLValue from dictionary item for contract: {:?}", + address.to_formatted_string() + ) + }) .into_t() .map_err(|_| OdraError::ExecutionError(ExecutionError::TypeMismatch)) } @@ -383,14 +386,19 @@ impl CasperClient { pub async fn get_deploy(&self, deploy_hash: DeployHash) -> GetDeployResult { let t = get_deploy( &self.rpc_id(), - &self.node_address().clone(), + self.node_address(), self.configuration.verbosity(), &deploy_hash.to_hex_string(), true ) .await; - t.expect(format!("Couldn't get deploy: {:?}", deploy_hash.to_hex_string()).as_str()) - .result + t.unwrap_or_else(|_| { + panic!( + "Couldn't get deploy: {:?}", + deploy_hash.to_hex_string().as_str() + ) + }) + .result } /// Discover the contract address by name. @@ -399,47 +407,47 @@ impl CasperClient { let result = get_entity( &self.rpc_id(), - &self.node_address(), + self.node_address(), self.configuration.verbosity(), "", &self.public_key().to_hex_string() ) .await - .expect( - format!( - "Couldn't get entity for public key: {:?}", - self.public_key().to_hex_string() - ) - .as_str() - ) + .unwrap_or_else(|_| { + panic!( + "{}", + format!( + "Couldn't get entity for public key: {:?}", + &self.public_key().to_hex_string() + ) + ); + }) .result; - let account = result.entity_result.addressable_entity().expect( - format!( - "Couldn't get addressable entity for public key: {:?}", - self.public_key().to_hex_string() - ) - .as_str() - ); + let account = result + .entity_result + .addressable_entity() + .unwrap_or_else(|| { + panic!( + "Couldn't get addressable entity for public key: {:?}", + self.public_key().to_hex_string() + ) + }); - let key = account.named_keys.get(&key_name).expect( - format!( - "Couldn't get key {:?} for account: {:?}", + let key = account.named_keys.get(&key_name).unwrap_or_else(|| { + panic!( + "Couldn't get named key {:?} for account: {:?}", key_name, self.public_key().to_hex_string() ) - .as_str() - ); + }); - Address::from( - key.into_package_hash().expect( - format!( - "Couldn't get package hash from key {:?} for account: {:?}", - key_name, - self.public_key().to_hex_string() - ) - .as_str() + Address::from(key.into_package_hash().unwrap_or_else(|| { + panic!( + "Couldn't get package hash from key {:?} for account: {:?}", + key_name, + self.public_key().to_hex_string() ) - ) + })) } /// Find the entity addr in global state for an address @@ -451,13 +459,12 @@ impl CasperClient { StoredValue::Package(package) => EntityAddr::SmartContract( package .current_entity_hash() - .expect( - format!( + .unwrap_or_else(|| { + panic!( "Couldn't get entity addr for address: {:?}", address.to_formatted_string() ) - .as_str() - ) + }) .value() ), _ => { @@ -593,21 +600,18 @@ impl CasperClient { // Todo: set rpc id to a random number query_global_state( "", - &self.node_address(), + self.node_address(), Verbosity::Low as u64, "", &self.get_state_root_hash().await, key, - &path.unwrap_or_default() + &path.clone().unwrap_or_default() ) .await - .expect( - format!( - "Couldn't query global state for key: {:?}, and path: {:?}", - key, path - ) - .as_str() - ) + .unwrap_or_else(|e| { + log::error(format!("Couldn't query global state: {:?}", e)); + panic!("Couldn't query global state") + }) .result .stored_value } From 1dee4147d8953488214d7d0a3c8c276e99268a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Tue, 30 Jul 2024 14:06:45 +0200 Subject: [PATCH 05/22] Where is my ContractPackageHash? --- Cargo.toml | 3 +- core/src/address.rs | 97 ++++++------------ core/src/callstack.rs | 17 ++- core/src/crypto.rs | 6 +- core/src/host.rs | 4 +- examples/ourcoin/wasm/OurToken.wasm | Bin 252475 -> 251253 bytes odra-casper/proxy-caller/Cargo.toml | 3 + .../bin/proxy_caller_with_return.rs | 4 +- odra-casper/proxy-caller/src/lib.rs | 9 +- odra-casper/rpc-client/src/casper_client.rs | 7 +- .../test-vm/resources/proxy_caller.wasm | Bin 42817 -> 42817 bytes .../resources/proxy_caller_with_return.wasm | Bin 44418 -> 44418 bytes odra-casper/test-vm/src/vm/casper_vm.rs | 70 ++++++------- odra-casper/wasm-env/src/host_functions.rs | 21 ++-- odra-vm/src/vm/utils.rs | 10 +- 15 files changed, 103 insertions(+), 148 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 49d19f16..54322b50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ odra-vm = { path = "odra-vm", version = "1.1.0" } odra-casper-wasm-env = { path = "odra-casper/wasm-env", version = "1.1.0"} odra-schema = { path = "odra-schema", version = "1.1.0" } casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "rustSDK-feat-2.0", default-features = false } -casper-contract-schema = { path = "../casper-contract-schema"} +casper-contract-schema = { git = "https://github.com/odradev/casper-contract-schema.git", branch = "feature/casper-2.0" } casper-types = "5.0.0" casper-execution-engine = { git = "https://github.com/casper-network/casper-node.git", branch = "rustSDK-feat-2.0" } casper-engine-test-support = { git = "https://github.com/casper-network/casper-node.git", branch = "rustSDK-feat-2.0" } @@ -57,7 +57,6 @@ tokio = "1.38" base16 = "0.2" base64 = "0.22" serde-json-wasm = "1.0" -casper-rust-wasm-sdk = { path = "../../rustSDK" } [patch.crates-io] casper-types = { version = "5.0.0", git = "https://github.com/casper-network/casper-node", branch = "rustSDK-feat-2.0" } diff --git a/core/src/address.rs b/core/src/address.rs index a19bb73a..6b6c74cb 100644 --- a/core/src/address.rs +++ b/core/src/address.rs @@ -5,7 +5,6 @@ use core::hash::Hash; use casper_types::{ account::AccountHash, bytesrepr::{self, FromBytes, ToBytes}, - contracts::ContractPackageHash, AddressableEntityHash, CLType, CLTyped, EntityAddr, HashAddr, Key, PackageHash, PublicKey }; use serde::{Deserialize, Serialize}; @@ -20,16 +19,18 @@ const ADDRESS_HASH_LENGTH: usize = 64; const CONTRACT_STR_LENGTH: usize = 69; /// An address has format `contract-package-wasm<64-byte-hash>`. const LEGACY_CONTRACT_STR_LENGTH: usize = 85; +/// An address has format `package-<64-byte-hash>`. +const PACKAGE_STR_LENGTH: usize = 72; /// An address has format `account-hash-<64-byte-hash>`. const ACCOUNT_STR_LENGTH: usize = 77; -/// An enum representing an [`AccountHash`] or a [`ContractPackageHash`]. +/// An enum representing an [`AccountHash`] or a [`PackageHash`]. #[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy, Debug)] pub enum Address { /// Represents an account hash. Account(AccountHash), /// Represents a contract package hash. - Contract(ContractPackageHash) + Contract(PackageHash) } /// A trait for types that can be converted into an [`Address`]. @@ -53,9 +54,10 @@ impl Address { if let Ok(dst) = decode_base16(src) { // depending on the length of the input, we can determine the type of address match src_len { - LEGACY_CONTRACT_STR_LENGTH => Ok(Self::Contract(ContractPackageHash::new(dst))), + LEGACY_CONTRACT_STR_LENGTH => Ok(Self::Contract(PackageHash::new(dst))), + PACKAGE_STR_LENGTH => Ok(Self::Contract(PackageHash::new(dst))), ACCOUNT_STR_LENGTH => Ok(Self::Account(AccountHash::new(dst))), - CONTRACT_STR_LENGTH => Ok(Self::Contract(ContractPackageHash::new(dst))), + CONTRACT_STR_LENGTH => Ok(Self::Contract(PackageHash::new(dst))), _ => Err(OdraError::ExecutionError( ExecutionError::AddressCreationFailed )) @@ -77,7 +79,7 @@ impl Address { } /// Returns the inner contract hash if `self` is the `Contract` variant. - pub fn as_contract_package_hash(&self) -> Option<&ContractPackageHash> { + pub fn as_package_hash(&self) -> Option<&PackageHash> { if let Self::Contract(v) = self { Some(v) } else { @@ -85,22 +87,16 @@ impl Address { } } - /// Returns the inner contract hash if `self` is the `Contract` variant. - pub fn as_package_hash(&self) -> Option { - self.as_contract_package_hash() - .map(|cph| PackageHash::new(cph.value())) - } - /// Returns true if the address is a contract address. pub fn is_contract(&self) -> bool { - self.as_contract_package_hash().is_some() + self.as_package_hash().is_some() } /// Returns the [`HashAddr`] of the address. pub fn value(&self) -> HashAddr { match self { Address::Account(account_hash) => account_hash.value(), - Address::Contract(contract_package_hash) => contract_package_hash.value() + Address::Contract(package_hash) => package_hash.value() } } @@ -123,29 +119,14 @@ impl Address { } } -impl TryFrom for Address { - type Error = AddressError; - fn try_from(contract_package_hash: ContractPackageHash) -> Result { - if contract_package_hash.value().iter().all(|&b| b == 0) { - return Err(ZeroAddress); - } - Ok(Self::Contract(contract_package_hash)) - } -} - impl From for Address { fn from(package_hash: PackageHash) -> Self { - let contract_package_hash = ContractPackageHash::new(package_hash.value()); - Self::Contract(contract_package_hash) + Self::Contract(package_hash) } } -impl TryFrom for Address { - type Error = AddressError; - fn try_from(account_hash: AccountHash) -> Result { - if account_hash.value().iter().all(|&b| b == 0) { - return Err(ZeroAddress); - } - Ok(Self::Account(account_hash)) +impl From for Address { + fn from(account_hash: AccountHash) -> Self { + Self::Account(account_hash) } } @@ -153,7 +134,7 @@ impl From
for Key { fn from(address: Address) -> Self { match address { Address::Account(account_hash) => Key::Account(account_hash), - Address::Contract(contract_package_hash) => Key::Hash(contract_package_hash.value()) + Address::Contract(package_hash) => Key::Hash(package_hash.value()) } } } @@ -163,10 +144,8 @@ impl TryFrom for Address { fn try_from(key: Key) -> Result { match key { - Key::Account(account_hash) => Self::try_from(account_hash), - Key::Hash(contract_package_hash) => { - Self::try_from(ContractPackageHash::new(contract_package_hash)) - } + Key::Account(account_hash) => Ok(Self::from(account_hash)), + Key::Hash(hash_addr) => Ok(Self::from(PackageHash::new(hash_addr))), _ => Err(AddressError::AddressCreationError) } } @@ -201,7 +180,7 @@ impl FromBytes for Address { let address = match key { Key::Account(account_hash) => Address::Account(account_hash), Key::Hash(raw_contract_package_hash) => { - Address::Contract(ContractPackageHash::new(raw_contract_package_hash)) + Address::Contract(PackageHash::new(raw_contract_package_hash)) } _ => return Err(bytesrepr::Error::Formatting) }; @@ -253,12 +232,6 @@ impl ToString for Address { } } -impl From for Address { - fn from(value: AddressableEntityHash) -> Self { - Address::Contract(ContractPackageHash::new(value.value())) - } -} - impl From
for AddressableEntityHash { fn from(value: Address) -> Self { match value { @@ -329,8 +302,8 @@ mod tests { use super::*; // TODO: casper-types > 1.5.0 will have prefix fixed. - const CONTRACT_PACKAGE_HASH: &str = - "contract-package-wasm7ba9daac84bebee8111c186588f21ebca35550b6cf1244e71768bd871938be6a"; + const PACKAGE_HASH: &str = + "package-7ba9daac84bebee8111c186588f21ebca35550b6cf1244e71768bd871938be6a"; const ACCOUNT_HASH: &str = "account-hash-3b4ffcfb21411ced5fc1560c3f6ffed86f4885e5ea05cde49d90962a48a14d95"; const CONTRACT_HASH: &str = @@ -340,18 +313,15 @@ mod tests { AccountHash::from_formatted_str(ACCOUNT_HASH).unwrap() } - fn mock_contract_package_hash() -> ContractPackageHash { - ContractPackageHash::from_formatted_str(CONTRACT_PACKAGE_HASH).unwrap() + fn mock_package_hash() -> PackageHash { + PackageHash::from_formatted_str(PACKAGE_HASH).unwrap() } #[test] fn test_casper_address_new() { - let address = Address::new(CONTRACT_PACKAGE_HASH).unwrap(); + let address = Address::new(PACKAGE_HASH).unwrap(); assert!(address.is_contract()); - assert_eq!( - address.as_contract_package_hash().unwrap(), - &mock_contract_package_hash() - ); + assert_eq!(address.as_package_hash().unwrap(), &mock_package_hash()); let address = Address::new(ACCOUNT_HASH).unwrap(); assert!(!address.is_contract()); @@ -389,11 +359,11 @@ mod tests { let account_hash = mock_account_hash(); // It is possible to convert Address back to AccountHash. - let casper_address = Address::try_from(account_hash).unwrap(); + let casper_address = Address::from(account_hash); assert_eq!(casper_address.as_account_hash().unwrap(), &account_hash); - // It is not possible to convert Address to ContractPackageHash. - assert!(casper_address.as_contract_package_hash().is_none()); + // It is not possible to convert Address to PackageHash. + assert!(casper_address.as_package_hash().is_none()); // And it is not a contract. assert!(!casper_address.is_contract()); @@ -403,14 +373,11 @@ mod tests { #[test] fn test_casper_address_contract_package_hash_conversion() { - let contract_package_hash = mock_contract_package_hash(); - let casper_address = Address::try_from(contract_package_hash).unwrap(); + let package_hash = mock_package_hash(); + let casper_address = Address::from(package_hash); - // It is possible to convert Address back to ContractPackageHash. - assert_eq!( - casper_address.as_contract_package_hash().unwrap(), - &contract_package_hash - ); + // It is possible to convert Address back to . + assert_eq!(casper_address.as_package_hash().unwrap(), &package_hash); // It is not possible to convert Address to AccountHash. assert!(casper_address.as_account_hash().is_none()); @@ -445,7 +412,7 @@ mod tests { assert_eq!(&address.to_string(), ACCOUNT_HASH); assert_eq!( - Address::from_str(CONTRACT_PACKAGE_HASH).unwrap_err(), + Address::from_str(PACKAGE_HASH).unwrap_err(), OdraError::VmError(VmError::Deserialization) ) } diff --git a/core/src/callstack.rs b/core/src/callstack.rs index 0dd84c2a..a6425d97 100644 --- a/core/src/callstack.rs +++ b/core/src/callstack.rs @@ -4,9 +4,10 @@ //! the current account and contract address, the attached value, and the current entry point. //! //! The module provides building blocks for a callstack, such as `CallstackElement` and `ContractCall`. -use super::{casper_types::U512, Address, CallDef}; use crate::prelude::*; +use super::{casper_types::U512, Address, CallDef}; + /// A struct representing a callstack element. #[derive(Clone, Debug, PartialEq, Eq)] pub enum CallstackElement { @@ -113,7 +114,7 @@ impl Callstack { #[cfg(test)] mod tests { - use casper_types::{account::AccountHash, contracts::ContractPackageHash, RuntimeArgs}; + use casper_types::{account::AccountHash, PackageHash, RuntimeArgs}; use super::*; @@ -196,8 +197,8 @@ mod tests { assert!(!callstack.is_empty()); } - const CONTRACT_PACKAGE_HASH: &str = - "contract-package-wasm7ba9daac84bebee8111c186588f21ebca35550b6cf1244e71768bd871938be6a"; + const PACKAGE_HASH: &str = + "package-7ba9daac84bebee8111c186588f21ebca35550b6cf1244e71768bd871938be6a"; const ACCOUNT_HASH: &str = "account-hash-3b4ffcfb21411ced5fc1560c3f6ffed86f4885e5ea05cde49d90962a48a14d95"; @@ -209,18 +210,14 @@ mod tests { fn mock_contract_element() -> CallstackElement { CallstackElement::new_contract_call( - Address::Contract( - ContractPackageHash::from_formatted_str(CONTRACT_PACKAGE_HASH).unwrap() - ), + Address::Contract(PackageHash::from_formatted_str(PACKAGE_HASH).unwrap()), CallDef::new("a", false, RuntimeArgs::default()) ) } fn mock_contract_element_with_value(amount: U512) -> CallstackElement { CallstackElement::new_contract_call( - Address::Contract( - ContractPackageHash::from_formatted_str(CONTRACT_PACKAGE_HASH).unwrap() - ), + Address::Contract(PackageHash::from_formatted_str(PACKAGE_HASH).unwrap()), CallDef::new("a", false, RuntimeArgs::default()).with_amount(amount) ) } diff --git a/core/src/crypto.rs b/core/src/crypto.rs index 416801e7..b3be4d9d 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -30,12 +30,8 @@ pub fn generate_key_pairs(amount: u8) -> BTreeMapm+PJ2n0n|S&kwupn!_XBAXx(c8DlmB>~w~mcSsz zz9=p%N-)TvfPkRLCYuWGps0u-xF8A$->>@2Ov2*5_xs-W|9t=Cnd#Hr)zwwi)z#J2 zeP-S2#52nh^Nwr%mM0WEsWY>^mSqt>7U55M-DiBuzt4*r@>*JIH7R9zy~>BbSS!}j z+Vc6lXkl5XvV8alpbzcPs3>{jV#8RquP#!gh#>I+&{FbIszh*yC;b-XQ&NCF zw4gJ8iqN^&>y7saU4Fk`6ZuRtpmW;;PI=`S6b0kG_TV2i zh92R|r65_l@hIU_3kOn4Uav+!6icyIkr?nNR9Rj{nB(YD+0M~4gnf)6}J zdiEI5W6;CByA6KC+U|+X2HwGsNrAP)6Vqb|Xc*Dk+UY3{6vKM=05t^wyFA7F4Z5my zx5u9~tjD0?kMtg9edtMuJa>C!*x-TI9*@Uxve)D3`RK4g);>>>ZrytI$m-Fp_n=;y z;<^R9#bi|ak$zG;)NwjV+trKeW%ZJJK`l``)jBoKv)J=Oa9K=sZ;{|fMML?Y#cXIB z?DfeTr597GaxT$O$9G$T6|kJN%mCSA^J^V!OtdlB=THv46Fhck8jZ<+;?sMHUe5pU zvpte5_@cDO^D?D+f*o_K=YM_pK8l@B0hMkgrBE=jODTSFyDFal@HZI1MU(RTe%sPR z3-Vw4VT&)gZe;!Z*oQKF^lWh7BV~f^A3mA-GNtHve;m5^OZQtP8xt2$jwjQU+0Piz z$9g?RugFuruXhuo=S*S8M;@aIcv_kGJgF9{MF3QwojpB&YMl#V@3eq?ag=2pELolzOgN zs-vXt6R?@ZP}ecgv==*9fNo?lebVoJdfIOQ{i z8dqgoC0KERV4L0d(CXmA-4)8LCNN?<9=ihA4R|usl$C1PtAlsXuUu-S+*{?sW{nIvC z&W+CcX~E;GDx<9E8&kbyrvlLf7Dm%q;}*5`luZc9}_T#ff?pzPlFX6Yz#erICr z6~uYBAEfKA>OV}J|C+=}W&Wv=?G&xcZ}H>>MQ;ULJXt3HzU-1joAUeR%uvvei({71 zOnfH?*FRmCo(}%}^oh9j)5AhqF`YNxO(lc5UHthpuDrs~SDYXdv&n>#R2VG3)5O|J z?V28Z_Sqqqm3yDdSM{gEAVpL|VYWDhItcea%J|Oem@dzq$`?G~in9_rtp7y+tDZ?9AQ<-4ipY4M0b*=+3p6UDuCSI2= zkAUJWU0UT2`8kKUKC5=L!*H*vbYkp7r7BY_J|LYOXza%$QN$ zE1d6zrIRN#%g^~UlZx*IXPAMhz$A&s-Wj~`R}&wI_t-mvP0u~$*-P;;yttVz0U?Ve zG<1D_t_HAPnqDeSjG`ch=&yom7c9kga~%WFZs#kjy~ItM>r_d5G}n~`Gja3gx-2hf zu7%(e=L7goxFFxPFXRMIpXwRRy!a3WgCAVHj%EaZyLc}Dp3{xI{9IKmuZw2z1J%?( zl{Yi=el?ZtDYa-OR0}4<>F!ugo@DN~;(~SimgTIPs-4Q6$?w-xuT;tj*93fy&II=+ zFP6p&TLYj;wN!`1)6b*%9b$e09_R4$e7crenZ77e7=vbckqqSOY>H%JklAZ8`9EE1 z<*jjSj!RR(hNXyR#HK|vZcV6@m*#os>(JR)8lXylO#yG38j|9}I^s;6Ys}iFxjZr+ zTJ?2kPCOmb<(A9>Zr8HqX=6M-LJ2I}*x8}UHg#9mNBVH`Kn85vA)!WWn=cU49~R`# zFm(m>p-LsFgs!nydc46IJI{r8X5hi8?yQgGqxiUUY3dleORCeH-{*xkl%_Mf#@PAD zU+=6%BHcM3NIuOu9my9o|GaEL^PB~trse2Eot(E2;MX}{ATiCO;Grs&ur$*Lp;iHk z)${?kt4yUztQM{V&Wzb%=KS_Pe!Mbur{kedE7Mw4V-rT)Np@EAf8r+eb~UB%jk4%4k58kn^bLQPMnmWb-(Q37s`Cv6G#2iZL`nQ9UKyy|1?XrBIg28c z7ZmXR8Z@*p=04Za?&60rx#F;xeqm)g-av`|_K{GlnlwkpMp@<`YtvhFlwYhv-_X&} z_zYU?q3<~DMmiFE0USFi&Z0S7ygp4xU%wdqK_8%;hpo&iR@^#x>axBE`lsqDvSy1{ zCtu1w$kP&Z4gTwSYS{qI>SGe?n>`JbZJ?ahF96kT7&(sv%>_yrr}|1z1b{eT#g!-G zz#2*pRj#d;s@R_(jDX+%i9$cFP}Mzrp`I$23LH*?^NS?jfaPqJhdz6dbK*WXs@v6<#H(&pRd4=6+)nsXgc+K<1cclULKfk{Nq1Jfg#3db$Patp%!*TR zSOS>F)HcC#&I|JEoSY?`RA1d*9H34-<|DdVFVBZw;$ihwW!|?&RZ%C1PghcvN*+++ zsl~wffk$u&w7Wx#OR9HN>BBFJV3GncBA5VRk}{oRFNZF^rS4Vg&zHGPRaI72Ds@m>!OsjR_^Y60KTrr5&m&7i&UQ=(kY)CbXAm8OJoGd+0ar)Ra1_9L1rg zbZ=4)993~6Qm`lS+Xg54JN+uBHls9}!>yaqC|VKP(TuPI{XMk1IW15s=he`_o9Q(| zeYuwO6fNi3Eh&%AgzmhRs%drl)zGlp>24L5vlOZ%<`ejK-bt-#HkWBFG=wr+)8|B! zxYb>BA4r;e7b>^$4|h=;1(1(X7#@WJAJhWd?^%r52YI5 z^m}Qn(8{2-Dj(`Vf6*o0e?QfwOCiq#be?GL%#KDP_jaTwXbpeZk;>Aa{9{LIKx;Xr z6E#tgaMw=g`~{xYiRy-BzO55glA2?ks5~uTZ)Xg_TJF%9>d*`x*O^*{;r4c>)`>E3 z0iAC3a)ry+b)kA_)U6BM8VHMfDWVSHO@eDgWG@H@VgYaMLNzJKr@PQSwZg!HD>_hJ zUF+lSE?|_R4S_zHCp<*etA?9(h(e+Z)xu0_8T$B4+hecg0}s(y@bF+)Y8M;TI9}D2 zZba!1U8x_E_T5~zhjoL1&++r!s8L-5n`*Q(tfT>g3Pu#EM07I*56kOV_SDd&ZgiR; zfYUu7t{?|`(kIBB?P+q=AEqzwpQbQv4il^(Jl2?WYb!<2{r9;mIwpH6cj`r1G2<{r z?9qxp?nTvnOJUdS^Gv;|I$h)%y{T=|T1xdBueyQ62)rgn0Q>w>D7QB(eH+6rp#XL< z;7zw`MJfBoj1vJz>X2;L!lTDuK7yrg0pHyRlk_hh-iK;I$cy?=D-n4gSJwW%REaKd zT3_m0J)!_I?=hz?m%1bq>o0EI59rtOkbcxMtd?)}qlT#ewI8)d zMaxXO1Id(3sub2$EI{yHJofU?`b_duiEzJ#JLH4?G5r_txB(zK$ZrgwIh7*hK|i02 zilrdK{RbLlT0D?S)AG<-Ny-Z&7z2|itg}WwI?TuML70xq*gKdSgTNMp>E2WfkIx+* z?1x2%Zlk0}jay3Tr!C{ci zTDFJNxQM)?0|*gECtjc+!|R8`b6LjavY^7txOEn_6HN27Fm>1R_AI(DI(4BW1sMJz zlV5aaYf`gS!AcR#-8Df9uu$B{&pbwx!Wpw_l;Adx(+rx+M;@mPHz~!{N0LqRx#38v z94<;I+1SEdl+6X%^Lf-ra6O;d1z0!IG(F)a6`nA;yWJ$)P4e92$P+MLbGh`t49IP6 z^0=EUb(7EBB<@KAa?_LW_U7`CCo%SO`Q;~}i?bz`#os)MQJupTo}xQZ(f28;3VNnI zWe9%5%^h-c7oMV~Ro0LVRfz!$_L~$}8mW_%IV$)?Tpb>fgK355#mQB8T@DtTMeH9<*;tn*j)oDR!-qy=?kwV# zW2g^SrxjzcJj~%sW2hadYxgwf>m1(vG<8B!ek^?-n}-F?Nwg=ZP~14WRo2+{jHZDrK)wQ03FE<#P@Rk_IR^ zMVZbAlKBFR+8r?UEqj4j0KZAZ;AYv2lv@_Ax(s4b0wv1&!ZUBDGTe6}y{~>>9?E!z znh?0!qhC|wz2cVv4b$l+aD8q%bp*Eig7kn#dgqlvDqB3# z7(|UF7wP9gsK7j~KLe_>nAgmpWzANq1TQ>l$DcxGn%OAu$cCkGfsT&^Cs7Lr-G%kr zD|x~5RN5{s>b*)~```eHeU=yQrJ5OT!Pbwp{j9d)>?jN-at;a#HTw1=f#Gv|safpI z6;POXd$eX}9}QBIHLu!7i;KF&7>Kk?dxGZf`za}0SA`esr}$!Th-f^{Q7fSAvv^*| zfA6Ix*G5-|7aX8r{}E@kFiw{|EACeurd;XnD0@Fo`H0GuSO&Vyb}toS5Q*CUOuYUh zx}nqy!VtDl(1uiZUuaL%?D?3|J(Dq;IPVGg)NMYd#fDGl=KN#I+>AhE0qtN0zKcHpn3!dH_?c<|kB} z^10I|G@ADF7oX5g>Y(DX2dM?JoexsOaJDk9JxD84_8}zX{Ap#v@9-mN=bU^UEK76* z3W50qb|)U9=ViPqeM+|{xqLb~t5Me;qlVV9^yYK+r|{YL^Tki8k#x}LGt=J_pGC8k z`Os&yqAdC|J^2cH65)e2Uhp|3iCn#|T=#uWb&{pO0AkHDw=V0Or?*zA4bBR+9=lhH zC0ecm7A#tQ6%PY;-qb+puuaTa32Glnkx7mXAvU^PaUSG&fSb>w`~A77b&6o-&7ViD zh4wmgsAYwfD#c^K?NhLtDez4oXK4;v_jB$XY99tI!{_EudR!rRJ~^MdNNw(1dOgz1 z(*Kjaf!%hXe$HS_PyZJ*8X*uHeVsy5DzF!+fy&Pv;njXmtRTgLSPggu!RUr zU*ZxtYKco=1dO*Wp+|i`D$JVGoW7KvbcvmK4H9SM{a;05Z%$lxD#<6$?%Et4#xOi@8*qo8@CKyygvfey%|&#V6jN zs%3sr;k|@84mNX%>`zs+jWNzJ8*RDuPG$4T1yqamYO0;Ut3ZitI&iG^8aG=__a)4e zqN0(ag{!Fn51mOHVT9{49I8)vNO9eP-(X6n_jni6L$@5nMij9jd?jVtA4?`lG8x9w zKc{j2XKG|EiLNppca;(Ar?JZLfLV0Ai?0fQI*VGUX`0K;rgE}EG@MQ8p`Hn91gYbS zdn{1x`NMS8h)=dwl`8KNa$}v3C4otIR>;F)Q;2RQn74}Sv{5(nh6Sp2^<4_ofC`*4 z;>#;m7eyc$|0u%bHAS40sJ>LY6#ueNE$1S|)C}K8ateBahZa{A)klh_6<4qL_7^JK zA1*sksO*3#E5R>JRHNuPpDv*u3{79JzEKMCx?gTp2PD0_v8sVI^h+z%M)^-;z41AF zRcKm$^}UjKp@Bnc;7�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 delta 25591 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

JinEGJ*)sFSA9^&SG)lu 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 = call_versioned_contract_ret_bytes( - cph, + proxy_call.package_hash, proxy_call.entry_point_name.as_str(), proxy_call.runtime_args ); diff --git a/odra-casper/proxy-caller/src/lib.rs b/odra-casper/proxy-caller/src/lib.rs index 9a499445..633cc9d4 100644 --- a/odra-casper/proxy-caller/src/lib.rs +++ b/odra-casper/proxy-caller/src/lib.rs @@ -2,6 +2,7 @@ #![doc = "It allows calling other contracts and saving the return values to the named key"] #![doc = "of the Proxy Caller."] #![no_std] +#![feature(core_intrinsics)] extern crate alloc; use core::mem::MaybeUninit; @@ -15,12 +16,12 @@ use odra_casper_wasm_env::casper_contract::{ ext_ffi, unwrap_or_revert::UnwrapOrRevert }; -use odra_core::casper_types::contracts::{ContractPackageHash, ContractVersion}; use odra_core::casper_types::{ api_error, - bytesrepr::{Bytes, FromBytes, ToBytes}, - ApiError, CLTyped, PackageHash, RuntimeArgs, URef, U512 + ApiError, + bytesrepr::{Bytes, FromBytes, ToBytes}, CLTyped, PackageHash, RuntimeArgs, U512, URef }; +use odra_core::casper_types::contracts::ContractVersion; use odra_core::consts::{ ARGS_ARG, ATTACHED_VALUE_ARG, CARGO_PURSE_ARG, CARGO_PURSE_KEY, ENTRY_POINT_ARG, PACKAGE_HASH_ARG @@ -85,7 +86,7 @@ pub fn set_key(name: &str, value: T) { /// Customized version of `call_versioned_contract` from `casper_contract::contract_api::runtime`. /// It returns raw bytes instead of deserialized value. pub fn call_versioned_contract_ret_bytes( - package_hash: ContractPackageHash, + package_hash: PackageHash, entry_point_name: &str, runtime_args: RuntimeArgs ) -> Vec { diff --git a/odra-casper/rpc-client/src/casper_client.rs b/odra-casper/rpc-client/src/casper_client.rs index a55551c0..0785bab6 100644 --- a/odra-casper/rpc-client/src/casper_client.rs +++ b/odra-casper/rpc-client/src/casper_client.rs @@ -581,7 +581,12 @@ impl CasperClient { call_def.entry_point() )); let session = ExecutableDeployItem::StoredVersionedContractByHash { - hash: addr.as_package_hash().unwrap(), + hash: *addr.as_package_hash().unwrap_or_else(|| { + panic!( + "Couldn't get package hash from address: {:?}", + addr.to_formatted_string() + ) + }), version: None, entry_point: call_def.entry_point().to_string(), args: call_def.args().clone() diff --git a/odra-casper/test-vm/resources/proxy_caller.wasm b/odra-casper/test-vm/resources/proxy_caller.wasm index 0d381bf7c48c0bd1e0ac9c5948ec1aec817e826c..c4e18a14ae8c80ce7d9bb173ffaa4e5d631f1037 100755 GIT binary patch delta 290 zcmX?jj_KezrVU3}WyKg27#t-^93@H}nMxe*G#p^a(qLlXW&;Wfcy4~kTFu5NxH*>R z69=Qv<^WM`2}Xm>HM)~oHlH!-Wt_}!w0Co)$!-=#y~(1M?-&gx&o2?3ENP{|W5BJz zsKLadz?|g>RAnVUImb#EsI$RpB0Er(kdr!)v~scsl2uN@K=PcE3XuE^CMBFfY+Epy z1SZ?TCdCjqYTY$#abNZss@YW!xNTvYUlbf3m3MJ4U_9^Gif0OIm60=y5AB zYA~@VFlRY3PUg3gpPXYQ4Aj|RHIW^tO2|naNLo4B1Ia3kV*$InF=Pmz~p)tkonhLbbus_YaWm+b_LnB8ANXW?D~NZ iNIeO8#R#O%hXMU$u=#i14i=z_Eyb^ZV)IKD836!0Syz1k diff --git a/odra-casper/test-vm/resources/proxy_caller_with_return.wasm b/odra-casper/test-vm/resources/proxy_caller_with_return.wasm index 23129c460749a976c4f7ffcb30da26c07d03dcc1..a4d7a046c2dc9ebd389012eabadeadf9d6aee84b 100755 GIT binary patch delta 1842 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 ContractPackageHash { + /// Read a PackageHash of a given name, from the active account. + pub fn package_hash_from_name(&self, name: &str) -> PackageHash { let named_keys = self .context .get_named_keys_by_account_hash(self.active_account_hash()); let key: &Key = named_keys.get(name).unwrap(); - ContractPackageHash::from(key.into_package_hash().unwrap().value()) + PackageHash::from(key.into_package_hash().unwrap().value()) } /// Updates the active account (caller) address. @@ -105,11 +105,10 @@ impl CasperVm { /// /// Returns [EventError::IndexOutOfBounds] if the index is out of bounds. pub fn get_event(&self, contract_address: &Address, index: u32) -> Result { - let contract_package_hash = contract_address.as_contract_package_hash().unwrap(); - let contract_hash: ContractHash = self.get_contract_hash(contract_package_hash); + let package_hash = contract_address.as_package_hash().unwrap(); let dictionary_seed_uref = self - .package_named_key(*contract_package_hash, EVENTS) + .package_named_key(*package_hash, EVENTS) .ok_or(EventError::ContractDoesntSupportEvents)?; Ok(self.get_dict_value(*dictionary_seed_uref.as_uref().unwrap(), &index.to_string())) @@ -134,10 +133,10 @@ impl CasperVm { /// Gets the count of events for the given contract address. pub fn get_events_count(&self, contract_address: &Address) -> u32 { - let contract_package_hash = contract_address - .as_contract_package_hash() + let package_hash = contract_address + .as_package_hash() .expect("Events can only be queried for contracts"); - self.events_length(*contract_package_hash) + self.events_length(*package_hash) } /// Attaches a value to the next call. @@ -237,9 +236,8 @@ impl CasperVm { self.error = Some(odra_error.clone()); panic!("Revert: Contract deploy failed {:?}", odra_error); } else { - let contract_package_hash = - self.contract_package_hash_from_name(&package_hash_key_name); - contract_package_hash.try_into().unwrap() + let package_hash = self.package_hash_from_name(&package_hash_key_name); + package_hash.into() } } @@ -254,9 +252,7 @@ impl CasperVm { pub fn balance_of(&self, address: &Address) -> U512 { match address { Address::Account(account_hash) => self.get_account_cspr_balance(account_hash), - Address::Contract(contract_package_hash) => { - self.get_contract_cspr_balance(contract_package_hash) - } + Address::Contract(package_hash) => self.get_contract_cspr_balance(package_hash) } } @@ -384,9 +380,9 @@ impl CasperVm { self.context.get_purse_balance(purse) } - fn get_contract_cspr_balance(&self, contract_package_hash: &ContractPackageHash) -> U512 { + fn get_contract_cspr_balance(&self, package_hash: &PackageHash) -> U512 { // TODO: Addressable entity has main purse inside it, is it the same as ours for contracts? - let purse_key = self.package_named_key(*contract_package_hash, CONTRACT_MAIN_PURSE); + let purse_key = self.package_named_key(*package_hash, CONTRACT_MAIN_PURSE); match purse_key { None => U512::zero(), Some(purse_key) => { @@ -401,8 +397,8 @@ impl CasperVm { } } - fn get_contract_hash(&self, contract_package_hash: &ContractPackageHash) -> ContractHash { - ContractHash::new(contract_package_hash.value()) + fn get_contract_hash(&self, package_hash: &PackageHash) -> ContractHash { + ContractHash::new(package_hash.value()) } fn genesis_accounts( @@ -499,10 +495,10 @@ impl CasperVm { } impl CasperVm { - fn get_package(&self, contract_package_hash: ContractPackageHash) -> Package { + fn get_package(&self, package_hash: PackageHash) -> Package { let stored_value = self .context - .query(None, Key::Package(contract_package_hash.value()), &[]) + .query(None, Key::Package(package_hash.value()), &[]) .unwrap(); match stored_value { @@ -511,11 +507,8 @@ impl CasperVm { } } - fn get_current_contract( - &self, - contract_package_hash: ContractPackageHash - ) -> EntityWithNamedKeys { - let package = self.get_package(contract_package_hash); + fn get_current_contract(&self, package_hash: PackageHash) -> EntityWithNamedKeys { + let package = self.get_package(package_hash); let addressable_entity_hash = package .current_entity_hash() .expect("Package doesn't have current entity hash"); @@ -526,8 +519,8 @@ impl CasperVm { /// Gets current contract from contract package and /// returns its named keys. - fn package_named_keys(&self, contract_package_hash: ContractPackageHash) -> NamedKeys { - let package = self.get_package(contract_package_hash); + fn package_named_keys(&self, package_hash: PackageHash) -> NamedKeys { + let package = self.get_package(package_hash); let addressable_entity_hash = package .current_entity_hash() .expect("Package doesn't have current entity hash"); @@ -538,18 +531,14 @@ impl CasperVm { contract.named_keys().clone() } - fn package_named_key( - &self, - contract_package_hash: ContractPackageHash, - name: &str - ) -> Option { - let keys = self.package_named_keys(contract_package_hash); + fn package_named_key(&self, package_hash: PackageHash, name: &str) -> Option { + let keys = self.package_named_keys(package_hash); let key = keys.get(name); key.copied() } - fn events_length(&self, contract_package_hash: ContractPackageHash) -> u32 { - let key = self.package_named_key(contract_package_hash, EVENTS_LENGTH); + fn events_length(&self, package_hash: PackageHash) -> u32 { + let key = self.package_named_key(package_hash, EVENTS_LENGTH); match key { None => 0, Some(key) => self.get_value(key) @@ -580,7 +569,12 @@ impl CasperVm { .unwrap() } - fn panic_with_error(&self, error: OdraError, entrypoint: &str, package_hash: PackageHash) -> ! { + fn panic_with_error( + &self, + error: OdraError, + entrypoint: &str, + package_hash: &PackageHash + ) -> ! { panic!("Revert: {:?} - {:?}::{}", error, package_hash, entrypoint) } } diff --git a/odra-casper/wasm-env/src/host_functions.rs b/odra-casper/wasm-env/src/host_functions.rs index 23b7f48e..3d131c67 100644 --- a/odra-casper/wasm-env/src/host_functions.rs +++ b/odra-casper/wasm-env/src/host_functions.rs @@ -22,7 +22,7 @@ use casper_contract::{ use core::mem::MaybeUninit; use odra_core::casper_types::addressable_entity::NamedKeys; use odra_core::casper_types::bytesrepr::deserialize; -use odra_core::casper_types::contracts::{ContractPackageHash, ContractVersion}; +use odra_core::casper_types::contracts::ContractVersion; use odra_core::casper_types::system::Caller; use odra_core::casper_types::{ api_error, bytesrepr, @@ -362,11 +362,11 @@ pub fn caller() -> Address { /// Calls a contract method by Address #[inline(always)] pub fn call_contract(address: Address, call_def: CallDef) -> Bytes { - let contract_package_hash = *address.as_contract_package_hash().unwrap_or_revert(); + let package_hash = *address.as_package_hash().unwrap_or_revert(); let method = call_def.entry_point(); let mut args = call_def.args().to_owned(); if call_def.amount() == U512::zero() { - call_versioned_contract(contract_package_hash, None, method, args) + call_versioned_contract(package_hash, None, method, args) } else { let cargo_purse = get_or_create_cargo_purse(); let main_purse = get_main_purse().unwrap_or_revert(); @@ -376,7 +376,7 @@ pub fn call_contract(address: Address, call_def: CallDef) -> Bytes { args.insert(consts::CARGO_PURSE_ARG, cargo_purse) .unwrap_or_revert(); - let result = call_versioned_contract(contract_package_hash, None, method, args); + let result = call_versioned_contract(package_hash, None, method, args); if !is_purse_empty(cargo_purse) { runtime::revert(ApiError::InvalidPurse) } @@ -402,13 +402,12 @@ pub fn self_balance() -> U512 { /// address, for the most current version of a contract package by default or a specific /// `contract_version` if one is provided, and passing the provided `runtime_args` to it. pub fn call_versioned_contract( - contract_package_hash: ContractPackageHash, + package_hash: PackageHash, contract_version: Option, entry_point_name: &str, runtime_args: RuntimeArgs ) -> Bytes { - let (contract_package_hash_ptr, contract_package_hash_size, _bytes) = - to_ptr(contract_package_hash); + let (package_hash_ptr, package_hash_size, _bytes) = to_ptr(package_hash); let (contract_version_ptr, contract_version_size, _bytes) = to_ptr(contract_version); let (entry_point_name_ptr, entry_point_name_size, _bytes) = to_ptr(entry_point_name); let (runtime_args_ptr, runtime_args_size, _bytes) = to_ptr(runtime_args); @@ -417,8 +416,8 @@ pub fn call_versioned_contract( let mut bytes_written = MaybeUninit::uninit(); let ret = unsafe { ext_ffi::casper_call_versioned_contract( - contract_package_hash_ptr, - contract_package_hash_size, + package_hash_ptr, + package_hash_size, contract_version_ptr, contract_version_size, entry_point_name_ptr, @@ -591,9 +590,7 @@ fn revoke_access_to_constructor_group(package_hash: PackageHash, constructor_acc /// case it will use contract hash as the address. fn caller_to_address(caller: Caller) -> Address { match caller { - Caller::Initiator { account_hash } => Address::try_from(account_hash) - .map_err(|e| ApiError::User(ExecutionError::from(e).code())) - .unwrap_or_revert(), + Caller::Initiator { account_hash } => Address::from(account_hash), Caller::Entity { entity_hash, package_hash diff --git a/odra-vm/src/vm/utils.rs b/odra-vm/src/vm/utils.rs index a5943d9f..a5ba66e1 100644 --- a/odra-vm/src/vm/utils.rs +++ b/odra-vm/src/vm/utils.rs @@ -1,7 +1,5 @@ -use odra_core::{ - casper_types::{account::AccountHash, contracts::ContractPackageHash}, - Address -}; +use odra_core::casper_types::PackageHash; +use odra_core::{casper_types::account::AccountHash, Address}; pub fn account_address_from_str(str: &str) -> Address { use odra_core::casper_types::account::{ @@ -22,6 +20,6 @@ pub fn contract_address_from_u32(i: u32) -> Address { let padding = "0".repeat(padding_length); let a = i.to_string(); - let account_str = format!("{}{}{}", "contract-package-", a, padding); - Address::Contract(ContractPackageHash::from_formatted_str(account_str.as_str()).unwrap()) + let account_str = format!("{}{}{}", "package-", a, padding); + Address::Contract(PackageHash::from_formatted_str(account_str.as_str()).unwrap()) } From e43f13d6c34d2e1ea686ef9b58fa1f7d5f57e8d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Tue, 30 Jul 2024 14:29:32 +0200 Subject: [PATCH 06/22] Bump the version number to 2.0.0 --- Cargo.toml | 16 ++++++++-------- benchmark/Cargo.toml | 2 +- examples/Cargo.toml | 2 +- examples/ourcoin/Cargo.toml | 2 +- justfile | 3 ++- modules/Cargo.toml | 10 +++++----- odra-casper/proxy-caller/Cargo.toml | 2 +- odra-casper/proxy-caller/src/lib.rs | 6 +++--- 8 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 54322b50..f52e37c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,20 +22,20 @@ exclude = ["odra-casper/proxy-caller", "templates/blank", "templates/full", "tem resolver = "2" [workspace.package] -version = "1.1.0" +version = "2.0.0" authors = ["Jakub Płaskonka ", "Krzysztof Pobiarżyn ", "Maciej Zieliński "] license = "MIT" homepage = "https://odra.dev/docs" repository = "https://github.com/odradev/odra" [workspace.dependencies] -odra-core = { path = "core", version = "1.1.0" } -odra-macros = { path = "odra-macros", version = "1.1.0" } -odra-casper-test-vm = { path = "odra-casper/test-vm", version = "1.1.0" } -odra-casper-rpc-client = { path = "odra-casper/rpc-client", version = "1.1.0" } -odra-vm = { path = "odra-vm", version = "1.1.0" } -odra-casper-wasm-env = { path = "odra-casper/wasm-env", version = "1.1.0"} -odra-schema = { path = "odra-schema", version = "1.1.0" } +odra-core = { path = "core", version = "2.0.0" } +odra-macros = { path = "odra-macros", version = "2.0.0" } +odra-casper-test-vm = { path = "odra-casper/test-vm", version = "2.0.0" } +odra-casper-rpc-client = { path = "odra-casper/rpc-client", version = "2.0.0" } +odra-vm = { path = "odra-vm", version = "2.0.0" } +odra-casper-wasm-env = { path = "odra-casper/wasm-env", version = "2.0.0"} +odra-schema = { path = "odra-schema", version = "2.0.0" } casper-contract = { git = "https://github.com/casper-network/casper-node.git", branch = "rustSDK-feat-2.0", default-features = false } casper-contract-schema = { git = "https://github.com/odradev/casper-contract-schema.git", branch = "feature/casper-2.0" } casper-types = "5.0.0" diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 60e05ecc..e492b1f7 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "benchmark" -version = "0.1.0" +version = "2.0.0" edition = "2021" [dependencies] diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 3b588c35..da677948 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "odra-examples" -version = "1.3.0" +version = "2.0.0" edition = "2021" [dependencies] diff --git a/examples/ourcoin/Cargo.toml b/examples/ourcoin/Cargo.toml index be156743..6ccd04fe 100644 --- a/examples/ourcoin/Cargo.toml +++ b/examples/ourcoin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ourcoin" -version = "0.1.0" +version = "2.0.0" edition = "2021" [dependencies] diff --git a/justfile b/justfile index ef4b4ee3..c2973189 100644 --- a/justfile +++ b/justfile @@ -66,6 +66,7 @@ test-examples-on-odravm: cd examples/ourcoin && cargo odra test test-examples-on-casper: + cp modules/wasm/Erc20.wasm examples/wasm/ cd examples && cargo odra test -b casper cd examples/ourcoin && cargo odra test -b casper @@ -79,7 +80,7 @@ test-modules-on-casper: test-modules: test-modules-on-odravm test-modules-on-casper -test: test-odra test-examples test-modules +test: test-odra test-modules test-examples test-template name: cd tests && cargo odra new -n {{name}} --template {{name}} -s ../ \ diff --git a/modules/Cargo.toml b/modules/Cargo.toml index 6cd01350..b23419d3 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "odra-modules" -version = "1.1.0" +version = "2.0.0" edition = "2021" authors = ["Jakub Płaskonka ", "Krzysztof Pobiarżyn ", "Maciej Zieliński "] license = "MIT" @@ -9,7 +9,7 @@ description = "Collection of reusable Odra modules." keywords = ["wasm", "webassembly", "blockchain"] [dependencies] -odra = { path = "../odra", version = "1.1.0", default-features = false } +odra = { path = "../odra", default-features = false } serde = { workspace = true, default-features = false } serde_json = { workspace = true, default-features = false } serde-json-wasm = { workspace = true } @@ -17,15 +17,15 @@ base16 = { workspace = true } base64 = { workspace = true, features = ["alloc"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -odra-build = { path = "../odra-build", version = "1.1.0" } +odra-build = { path = "../odra-build" } [dev-dependencies] -odra-test = { path = "../odra-test", version = "1.1.0" } +odra-test = { path = "../odra-test" } once_cell = "1" blake2 = "0.10.6" [build-dependencies] -odra-build = { path = "../odra-build", version = "1.1.0" } +odra-build = { path = "../odra-build" } [features] default = [] diff --git a/odra-casper/proxy-caller/Cargo.toml b/odra-casper/proxy-caller/Cargo.toml index 39d27e72..e96e2ca8 100644 --- a/odra-casper/proxy-caller/Cargo.toml +++ b/odra-casper/proxy-caller/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "odra-casper-proxy-caller" edition = "2021" -version = "1.3.0" +version = "2.0.0" authors = ["Jakub Płaskonka ", "Krzysztof Pobiarżyn ", "Maciej Zieliński "] license = "MIT" homepage = "https://odra.dev/docs" diff --git a/odra-casper/proxy-caller/src/lib.rs b/odra-casper/proxy-caller/src/lib.rs index 633cc9d4..10f3e1d7 100644 --- a/odra-casper/proxy-caller/src/lib.rs +++ b/odra-casper/proxy-caller/src/lib.rs @@ -16,12 +16,12 @@ use odra_casper_wasm_env::casper_contract::{ ext_ffi, unwrap_or_revert::UnwrapOrRevert }; +use odra_core::casper_types::contracts::ContractVersion; use odra_core::casper_types::{ api_error, - ApiError, - bytesrepr::{Bytes, FromBytes, ToBytes}, CLTyped, PackageHash, RuntimeArgs, U512, URef + bytesrepr::{Bytes, FromBytes, ToBytes}, + ApiError, CLTyped, PackageHash, RuntimeArgs, URef, U512 }; -use odra_core::casper_types::contracts::ContractVersion; use odra_core::consts::{ ARGS_ARG, ATTACHED_VALUE_ARG, CARGO_PURSE_ARG, CARGO_PURSE_KEY, ENTRY_POINT_ARG, PACKAGE_HASH_ARG From 13353f906981f71666d4664f69657f5af6a267b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Mon, 5 Aug 2024 10:28:44 +0200 Subject: [PATCH 07/22] Fix test pipeline. Removed livenet tests, waiting for working nctl image. --- .github/workflows/test.yml | 26 +++++++++++++------------- .gitignore | 1 - Cargo.toml | 7 +++++++ justfile | 1 + 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index baabf922..f94181b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,17 +18,17 @@ jobs: test: name: Test runs-on: buildjet-8vcpu-ubuntu-2204 - services: - casper-nctl: - image: makesoftware/casper-nctl:v155 - options: --name mynctl - env: - PREDEFINED_ACCOUNTS: 'true' - MINIMUM_ROUND_EXPONENT: '12' - MAXIMUM_ROUND_EXPONENT: '14' - DEPLOY_DELAY: '5sec' - ports: - - 11101:11101 +# services: +# casper-nctl: +# image: makesoftware/casper-nctl:v200-rc3 +# options: --name mynctl +# env: +# PREDEFINED_ACCOUNTS: 'true' +# MINIMUM_ROUND_EXPONENT: '12' +# MAXIMUM_ROUND_EXPONENT: '14' +# DEPLOY_DELAY: '30sec' +# ports: +# - 11101:11101 steps: - name: Setup just uses: extractions/setup-just@v1 @@ -50,5 +50,5 @@ jobs: run: just prepare-test-env - name: Run tests run: just test - - name: Run livenet tests - run: just test-livenet \ No newline at end of file +# - name: Run livenet tests +# run: just test-livenet \ No newline at end of file diff --git a/.gitignore b/.gitignore index f2370838..db82c1e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # Generated by Cargo # will have compiled files and executables -global_state target .* *.env diff --git a/Cargo.toml b/Cargo.toml index f52e37c2..9c31fd90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,3 +60,10 @@ serde-json-wasm = "1.0" [patch.crates-io] casper-types = { version = "5.0.0", git = "https://github.com/casper-network/casper-node", branch = "rustSDK-feat-2.0" } + +[profile.release] +codegen-units = 1 +lto = true + +[profile.dev.package."*"] +opt-level = 3 \ No newline at end of file diff --git a/justfile b/justfile index c2973189..dea4016c 100644 --- a/justfile +++ b/justfile @@ -66,6 +66,7 @@ test-examples-on-odravm: cd examples/ourcoin && cargo odra test test-examples-on-casper: + mkdir -p examples/wasm cp modules/wasm/Erc20.wasm examples/wasm/ cd examples && cargo odra test -b casper cd examples/ourcoin && cargo odra test -b casper From b9fcf8937ca73175aec912468194fe255ecde673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Mon, 5 Aug 2024 13:35:08 +0200 Subject: [PATCH 08/22] PR fixes. --- Cargo.toml | 4 +-- core/src/address.rs | 30 ++++++++++++++++++++-- odra-casper/utils/Cargo.toml | 11 -------- odra-casper/utils/src/lib.rs | 8 ------ odra-casper/wasm-env/src/host_functions.rs | 18 ++----------- odra-casper/wasm-env/src/lib.rs | 8 ------ 6 files changed, 31 insertions(+), 48 deletions(-) delete mode 100644 odra-casper/utils/Cargo.toml delete mode 100644 odra-casper/utils/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 9c31fd90..269af047 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,13 +12,11 @@ members = [ "odra-casper/livenet-env", "odra-casper/wasm-env", "odra-casper/test-vm", - "odra-casper/utils", "odra-schema", "odra-test", "odra-build", - "odra-casper/utils", ] -exclude = ["odra-casper/proxy-caller", "templates/blank", "templates/full", "templates/workspace", "tests"] +exclude = ["odra-casper/proxy-caller", "templates/blank", "templates/full", "templates/workspace"] resolver = "2" [workspace.package] diff --git a/core/src/address.rs b/core/src/address.rs index 6b6c74cb..0766316c 100644 --- a/core/src/address.rs +++ b/core/src/address.rs @@ -2,6 +2,7 @@ use core::hash::Hash; +use casper_types::system::Caller; use casper_types::{ account::AccountHash, bytesrepr::{self, FromBytes, ToBytes}, @@ -255,6 +256,15 @@ impl<'de> Deserialize<'de> for Address { } } +impl From for Address { + fn from(value: Caller) -> Self { + match value { + Caller::Initiator { account_hash } => Address::from(account_hash), + Caller::Entity { package_hash, .. } => Address::from(package_hash) + } + } +} + const fn decode_base16(input: &[u8]) -> Result<[u8; 32], &'static str> { // fail fast if the input is too short let input_len = input.len(); @@ -297,9 +307,9 @@ const fn hex_char_to_value(c: u8) -> Result { #[cfg(test)] mod tests { - use casper_types::EraId; - use super::*; + use casper_types::system::Caller; + use casper_types::EraId; // TODO: casper-types > 1.5.0 will have prefix fixed. const PACKAGE_HASH: &str = @@ -458,4 +468,20 @@ mod tests { let deserialized: Address = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized, address); } + + #[test] + fn test_address_from_caller() { + let account_hash = mock_account_hash(); + let address = Address::from(account_hash); + let caller = Caller::Initiator { account_hash }; + assert_eq!(address, caller.into()); + + let package_hash = mock_package_hash(); + let address = Address::from(package_hash); + let caller = Caller::Entity { + package_hash, + entity_hash: AddressableEntityHash::new(package_hash.value()) + }; + assert_eq!(address, caller.into()); + } } diff --git a/odra-casper/utils/Cargo.toml b/odra-casper/utils/Cargo.toml deleted file mode 100644 index 6bd5093c..00000000 --- a/odra-casper/utils/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "odra-casper-utils" -edition = "2021" -version.workspace = true -authors.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[dependencies] -casper-types = { workspace = true } \ No newline at end of file diff --git a/odra-casper/utils/src/lib.rs b/odra-casper/utils/src/lib.rs deleted file mode 100644 index fec5a596..00000000 --- a/odra-casper/utils/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -use casper_types::bytesrepr::FromBytes; -use casper_types::{CLTyped, CLValueError, StoredValue}; - -pub fn stored_value_into_t( - stored_value: StoredValue -) -> Result { - stored_value.into_cl_value().unwrap().into_t() -} diff --git a/odra-casper/wasm-env/src/host_functions.rs b/odra-casper/wasm-env/src/host_functions.rs index 3d131c67..c0aceddd 100644 --- a/odra-casper/wasm-env/src/host_functions.rs +++ b/odra-casper/wasm-env/src/host_functions.rs @@ -356,7 +356,7 @@ pub fn emit_event(event: &Bytes) { #[inline(always)] pub fn caller() -> Address { let second_elem = take_nth_caller_from_stack(1); - caller_to_address(second_elem) + second_elem.into() } /// Calls a contract method by Address @@ -388,7 +388,7 @@ pub fn call_contract(address: Address, call_def: CallDef) -> Bytes { #[inline(always)] pub fn self_address() -> Address { let first_elem = take_nth_caller_from_stack(0); - caller_to_address(first_elem) + first_elem.into() } /// Gets the balance of the current contract. @@ -584,20 +584,6 @@ fn revoke_access_to_constructor_group(package_hash: PackageHash, constructor_acc .unwrap_or_revert(); } -/// Returns address based on a [`CallStackElement`]. -/// -/// For `Session` and `StoredSession` variants it will return account hash, and for `StoredContract` -/// case it will use contract hash as the address. -fn caller_to_address(caller: Caller) -> Address { - match caller { - Caller::Initiator { account_hash } => Address::from(account_hash), - Caller::Entity { - entity_hash, - package_hash - } => Address::from(package_hash) - } -} - fn is_purse_empty(purse: URef) -> bool { get_purse_balance(purse) .map(|balance| balance.is_zero()) diff --git a/odra-casper/wasm-env/src/lib.rs b/odra-casper/wasm-env/src/lib.rs index df953808..54bf7639 100644 --- a/odra-casper/wasm-env/src/lib.rs +++ b/odra-casper/wasm-env/src/lib.rs @@ -20,11 +20,3 @@ pub use wasm_contract_env::WasmContractEnv; #[cfg(all(target_arch = "wasm32", not(feature = "disable-allocator")))] #[allow(unused_imports)] use ink_allocator; - -// /// Panic handler for the WASM target architecture. -// #[cfg(target_arch = "wasm32")] -// #[panic_handler] -// #[no_mangle] -// pub fn panic(_info: &core::panic::PanicInfo) -> ! { -// core::intrinsics::abort(); -// } From 260583f4874ac8e0de81eafb0b09a1c4c6f190a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Mon, 5 Aug 2024 15:57:15 +0200 Subject: [PATCH 09/22] Map errors. --- core/src/contract_env.rs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/core/src/contract_env.rs b/core/src/contract_env.rs index ea4806dc..eb78dcf1 100644 --- a/core/src/contract_env.rs +++ b/core/src/contract_env.rs @@ -1,16 +1,18 @@ use casper_event_standard::EventInstance; +use casper_types::CLValueError; +use crate::{consts, ExecutionError, prelude::*}; +use crate::{UnwrapOrRevert, utils}; +use crate::{Address, OdraError}; use crate::args::EntrypointArgument; use crate::call_def::CallDef; -use crate::casper_types::bytesrepr::{deserialize_from_slice, Bytes, FromBytes, ToBytes}; +use crate::casper_types::{BLAKE2B_DIGEST_LENGTH, CLTyped, CLValue, U512}; +use crate::casper_types::bytesrepr::{Bytes, deserialize_from_slice, FromBytes, ToBytes}; use crate::casper_types::crypto::PublicKey; -use crate::casper_types::{CLTyped, CLValue, BLAKE2B_DIGEST_LENGTH, U512}; -use crate::module::Revertible; pub use crate::ContractContext; use crate::ExecutionError::Formatting; -use crate::{consts, prelude::*, ExecutionError}; -use crate::{utils, UnwrapOrRevert}; -use crate::{Address, OdraError}; +use crate::module::Revertible; +use crate::VmError::{Serialization, TypeMismatch}; const INDEX_SIZE: usize = 4; const KEY_LEN: usize = 64; @@ -108,7 +110,19 @@ impl ContractEnv { pub fn set_named_value>(&self, name: U, value: T) { let key = name.as_ref(); // todo: map errors to correct Odra errors - let cl_value = CLValue::from_t(value).unwrap_or_revert(self); + let cl_value = CLValue::from_t(value).map_err(|e| { + match e { + CLValueError::Serialization(_) => { + OdraError::VmError(Serialization) + } + CLValueError::Type(e) => { + OdraError::VmError(TypeMismatch { + found: e.found, + expected: e.expected + }) + } + } + }).unwrap_or_revert(self); self.backend.borrow().set_named_value(key, cl_value); } From 3944a69a02062315624acbcf9b73024272bca36b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 7 Aug 2024 13:32:19 +0200 Subject: [PATCH 10/22] Removing std from rpc client. --- Cargo.toml | 1 + core/Cargo.toml | 1 + core/src/contract_env.rs | 34 ++-- core/src/error.rs | 11 +- examples/bin/livenet_tests.rs | 4 +- odra-casper/livenet-env/src/lib.rs | 1 + .../livenet-env/src/livenet_contract_env.rs | 2 +- odra-casper/livenet-env/src/livenet_host.rs | 5 +- odra-casper/rpc-client/Cargo.toml | 4 +- odra-casper/rpc-client/src/casper_client.rs | 165 ++++++++---------- odra-casper/rpc-client/src/error.rs | 23 +++ odra-casper/rpc-client/src/lib.rs | 1 + 12 files changed, 128 insertions(+), 124 deletions(-) create mode 100644 odra-casper/rpc-client/src/error.rs diff --git a/Cargo.toml b/Cargo.toml index 269af047..053a1723 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ tokio = "1.38" base16 = "0.2" base64 = "0.22" serde-json-wasm = "1.0" +anyhow = "1.0.86" [patch.crates-io] casper-types = { version = "5.0.0", git = "https://github.com/casper-network/casper-node", branch = "rustSDK-feat-2.0" } diff --git a/core/Cargo.toml b/core/Cargo.toml index b12a0d22..3149e099 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -14,6 +14,7 @@ casper-event-standard = { workspace = true } num-traits = { workspace = true } serde = { workspace = true, default-features = false, features = ["alloc", "derive"] } serde_json = { workspace = true, default-features = false, features = ["alloc"] } +anyhow = { workspace = true } [dev-dependencies] mockall = { workspace = true } diff --git a/core/src/contract_env.rs b/core/src/contract_env.rs index eb78dcf1..9370c40c 100644 --- a/core/src/contract_env.rs +++ b/core/src/contract_env.rs @@ -1,18 +1,18 @@ use casper_event_standard::EventInstance; use casper_types::CLValueError; -use crate::{consts, ExecutionError, prelude::*}; -use crate::{UnwrapOrRevert, utils}; -use crate::{Address, OdraError}; use crate::args::EntrypointArgument; use crate::call_def::CallDef; -use crate::casper_types::{BLAKE2B_DIGEST_LENGTH, CLTyped, CLValue, U512}; -use crate::casper_types::bytesrepr::{Bytes, deserialize_from_slice, FromBytes, ToBytes}; +use crate::casper_types::bytesrepr::{deserialize_from_slice, Bytes, FromBytes, ToBytes}; use crate::casper_types::crypto::PublicKey; +use crate::casper_types::{CLTyped, CLValue, BLAKE2B_DIGEST_LENGTH, U512}; +use crate::module::Revertible; pub use crate::ContractContext; use crate::ExecutionError::Formatting; -use crate::module::Revertible; use crate::VmError::{Serialization, TypeMismatch}; +use crate::{consts, prelude::*, ExecutionError}; +use crate::{utils, UnwrapOrRevert}; +use crate::{Address, OdraError}; const INDEX_SIZE: usize = 4; const KEY_LEN: usize = 64; @@ -110,19 +110,15 @@ impl ContractEnv { pub fn set_named_value>(&self, name: U, value: T) { let key = name.as_ref(); // todo: map errors to correct Odra errors - let cl_value = CLValue::from_t(value).map_err(|e| { - match e { - CLValueError::Serialization(_) => { - OdraError::VmError(Serialization) - } - CLValueError::Type(e) => { - OdraError::VmError(TypeMismatch { - found: e.found, - expected: e.expected - }) - } - } - }).unwrap_or_revert(self); + let cl_value = CLValue::from_t(value) + .map_err(|e| match e { + CLValueError::Serialization(_) => OdraError::VmError(Serialization), + CLValueError::Type(e) => OdraError::VmError(TypeMismatch { + found: e.found, + expected: e.expected + }) + }) + .unwrap_or_revert(self); self.backend.borrow().set_named_value(key, cl_value); } diff --git a/core/src/error.rs b/core/src/error.rs index 176bc778..f35ef2c7 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -1,7 +1,6 @@ -use core::any::Any; - use casper_types::bytesrepr::Error as BytesReprError; use casper_types::{CLType, CLValueError}; +use core::any::Any; use crate::arithmetic::ArithmeticsError; use crate::prelude::*; @@ -240,7 +239,7 @@ impl From for OdraError { /// Event-related errors. #[derive(Debug, PartialEq, Eq, PartialOrd)] pub enum EventError { - /// The type of event is different than expected. + /// The type of event is different from expected. UnexpectedType(String), /// Index of the event is out of bounds. IndexOutOfBounds, @@ -285,3 +284,9 @@ impl From for OdraError { .into() } } + +impl From for OdraError { + fn from(value: anyhow::Error) -> Self { + OdraError::VmError(VmError::Other(value.to_string())) + } +} diff --git a/examples/bin/livenet_tests.rs b/examples/bin/livenet_tests.rs index 9575f59e..75360935 100644 --- a/examples/bin/livenet_tests.rs +++ b/examples/bin/livenet_tests.rs @@ -11,6 +11,8 @@ fn main() { let owner = env.caller(); + println!("Block time: {}", env.block_time()); + // Funds can be transferred let another_account = env.get_account(1); let another_account_balance = env.balance_of(&another_account); @@ -20,7 +22,7 @@ fn main() { env.balance_of(&another_account), another_account_balance + U512::from(10_000_000_000u64) ); - // + // Contract can be deployed env.set_gas(30_000_000_000u64); let (contract, erc20) = deploy_new(&env); diff --git a/odra-casper/livenet-env/src/lib.rs b/odra-casper/livenet-env/src/lib.rs index 1c0a0381..3a93f284 100644 --- a/odra-casper/livenet-env/src/lib.rs +++ b/odra-casper/livenet-env/src/lib.rs @@ -1,6 +1,7 @@ //! This crate provides a host environment for the livenet. pub mod livenet_contract_env; pub mod livenet_host; + use livenet_host::LivenetHost; use odra_core::host::HostEnv; diff --git a/odra-casper/livenet-env/src/livenet_contract_env.rs b/odra-casper/livenet-env/src/livenet_contract_env.rs index 23c424dc..405ece76 100644 --- a/odra-casper/livenet-env/src/livenet_contract_env.rs +++ b/odra-casper/livenet-env/src/livenet_contract_env.rs @@ -95,7 +95,7 @@ impl ContractContext for LivenetContractEnv { fn get_block_time(&self) -> u64 { let client = self.casper_client.borrow(); self.runtime - .block_on(async { client.get_block_time().await }) + .block_on(async { client.get_block_time().await.unwrap() }) } fn attached_value(&self) -> U512 { diff --git a/odra-casper/livenet-env/src/livenet_host.rs b/odra-casper/livenet-env/src/livenet_host.rs index 0e01150d..3f697546 100644 --- a/odra-casper/livenet-env/src/livenet_host.rs +++ b/odra-casper/livenet-env/src/livenet_host.rs @@ -87,7 +87,7 @@ impl HostContext for LivenetHost { fn block_time(&self) -> u64 { let rt = Runtime::new().unwrap(); let client = self.casper_client.borrow(); - rt.block_on(async { client.get_block_time().await }) + rt.block_on(async { client.get_block_time().await.unwrap() }) } fn get_event(&self, contract_address: &Address, index: u32) -> Result { @@ -133,6 +133,7 @@ impl HostContext for LivenetHost { client .deploy_entrypoint_call_with_proxy(*address, call_def, timestamp) .await + .map_err(|e| e.into()) }), false => { rt.block_on(async { @@ -215,7 +216,7 @@ impl HostContext for LivenetHost { let rt = Runtime::new().unwrap(); let timestamp = Timestamp::now(); let client = self.casper_client.borrow_mut(); - rt.block_on(async { client.transfer(to, amount, timestamp).await }) + Ok(rt.block_on(async { client.transfer(to, amount, timestamp).await })?) } } diff --git a/odra-casper/rpc-client/Cargo.toml b/odra-casper/rpc-client/Cargo.toml index 8229ca07..434e26a5 100644 --- a/odra-casper/rpc-client/Cargo.toml +++ b/odra-casper/rpc-client/Cargo.toml @@ -14,6 +14,7 @@ odra-schema = { workspace = true } casper-execution-engine = { workspace = true } casper-types = { workspace = true } casper-client = { workspace = true } +derive_more = { version = "1.0.0-beta.7", features = ["from"]} jsonrpc-lite = "0.6.0" serde_json = { version = "1.0", features = ["raw_value"] } @@ -28,10 +29,11 @@ dotenv = "0.15.0" prettycli = "0.1.1" thiserror = "1.0.40" bytes = "1.5.0" -anyhow = "1.0.86" humantime = "2.1.0" tokio = { workspace = true, features = ["rt-multi-thread"]} base16 = { workspace = true } +snafu = { version = "0.8.4", no-default-features = true } +anyhow = { workspace = true } [features] default = [] diff --git a/odra-casper/rpc-client/src/casper_client.rs b/odra-casper/rpc-client/src/casper_client.rs index 0785bab6..9819d74e 100644 --- a/odra-casper/rpc-client/src/casper_client.rs +++ b/odra-casper/rpc-client/src/casper_client.rs @@ -1,16 +1,16 @@ //! Client for interacting with Casper node. -use std::collections::BTreeMap; -use std::time::Duration; - use itertools::Itertools; use jsonrpc_lite::JsonRpc; -use odra_core::OdraResult; use serde::de::DeserializeOwned; use serde_json::{json, Value}; +use std::collections::BTreeMap; +use std::time::Duration; use crate::casper_client::configuration::CasperClientConfiguration; +use crate::error::Error; +use crate::error::Error::LivenetToDoError; use crate::log; use casper_client::cli::{ get_balance, get_deploy, get_dictionary_item, get_entity, get_node_status, get_state_root_hash, @@ -51,6 +51,10 @@ pub const ENV_ACCOUNT_PREFIX: &str = "ODRA_CASPER_LIVENET_KEY_"; pub const ENV_CSPR_CLOUD_AUTH_TOKEN: &str = "CSPR_CLOUD_AUTH_TOKEN"; /// Environment variable holding a path to an additional .env file. pub const ENV_LIVENET_ENV_FILE: &str = "ODRA_CASPER_LIVENET_ENV"; +/// Time between retries when waiting for a deploy to be processed. +pub const DEPLOY_WAIT_TIME: Duration = Duration::from_secs(5); + +pub type Result = core::result::Result; enum ContractId { Name(String), @@ -175,12 +179,12 @@ impl CasperClient { } /// Signs the message using keys associated with an address. - pub fn sign_message(&self, message: &Bytes, address: &Address) -> OdraResult { + pub fn sign_message(&self, message: &Bytes, address: &Address) -> Result { let secret_key = self.address_secret_key(address); let public_key = &PublicKey::from(secret_key); let signature = sign(message, secret_key, public_key) .to_bytes() - .map_err(|_| OdraError::ExecutionError(ExecutionError::CouldNotSignMessage))?; + .map_err(|_| LivenetToDoError)?; Ok(Bytes::from(signature)) } @@ -246,12 +250,7 @@ impl CasperClient { } } - pub async fn transfer( - &self, - to: Address, - amount: U512, - timestamp: Timestamp - ) -> OdraResult<()> { + pub async fn transfer(&self, to: Address, amount: U512, timestamp: Timestamp) -> Result<()> { let session = ExecutableDeployItem::Transfer { args: runtime_args! { "amount" => amount, @@ -261,41 +260,32 @@ impl CasperClient { }; let deploy = self.new_deploy(session, self.gas, timestamp); let request = put_deploy_request(deploy); - let response: PutDeployResult = self.post_request(request).await; + let response: PutDeployResult = self.post_request(request).await?; let deploy_hash = response.deploy_hash; // TODO: wait_for_deploy_hash should return a result not panic, then this function can return a result - self.wait_for_deploy(deploy_hash).await; + self.wait_for_deploy(deploy_hash).await?; Ok(()) } /// Returns the current block_time - pub async fn get_block_time(&self) -> u64 { - get_node_status( + pub async fn get_block_time(&self) -> Result { + let block_time = get_node_status( &self.rpc_id(), self.node_address(), self.configuration.verbosity() ) .await - .unwrap_or_else(|_| { - panic!( - "Couldn't get block time from node: {:?}", - self.node_address() - ) - }) + .map_err(|_| LivenetToDoError)? .result .last_added_block_info - .unwrap_or_else(|| { - panic!( - "Couldn't get last added block info from node: {:?}", - self.node_address() - ) - }) + .ok_or(LivenetToDoError)? .timestamp - .millis() + .millis(); + Ok(block_time) } /// Get the event bytes from storage - pub async fn get_event(&self, contract_address: &Address, index: u32) -> OdraResult { + pub async fn get_event(&self, contract_address: &Address, index: u32) -> Result { self.query_dict(contract_address, EVENTS.to_string(), index.to_string()) .await } @@ -347,7 +337,7 @@ impl CasperClient { address: &Address, dictionary_name: String, dictionary_item_key: String - ) -> OdraResult { + ) -> Result { let entity_addr = self.query_global_state_for_entity_addr(address).await; let params = DictionaryItemStrParams::EntityNamedKey { entity_addr: &entity_addr.to_formatted_string(), @@ -363,23 +353,13 @@ impl CasperClient { ) .await; - r.unwrap_or_else(|_| { - panic!( - "Couldn't get dictionary item for contract: {:?}", - address.to_formatted_string() - ) - }) - .result - .stored_value - .into_cl_value() - .unwrap_or_else(|| { - panic!( - "Couldn't get CLValue from dictionary item for contract: {:?}", - address.to_formatted_string() - ) - }) - .into_t() - .map_err(|_| OdraError::ExecutionError(ExecutionError::TypeMismatch)) + r.map_err(|_| LivenetToDoError)? + .result + .stored_value + .into_cl_value() + .ok_or(LivenetToDoError)? + .into_t() + .map_err(|_| LivenetToDoError) } /// Query the node for the transaction state. @@ -483,7 +463,7 @@ impl CasperClient { args: RuntimeArgs, timestamp: Timestamp, wasm_bytes: Vec - ) -> OdraResult

{ + ) -> Result
{ log::info(format!("Deploying \"{}\".", contract_name)); let session = ExecutableDeployItem::ModuleBytes { module_bytes: Bytes::from(wasm_bytes), @@ -491,9 +471,9 @@ impl CasperClient { }; let deploy = self.new_deploy(session, self.gas, timestamp); let request = put_deploy_request(deploy); - let response: PutDeployResult = self.post_request(request).await; + let response: PutDeployResult = self.post_request(request).await?; let deploy_hash = response.deploy_hash; - let result = self.wait_for_deploy(deploy_hash).await; + let result = self.wait_for_deploy(deploy_hash).await?; self.process_execution( result, ContractId::Name(contract_name.to_string()), @@ -531,7 +511,7 @@ impl CasperClient { address: Address, call_def: CallDef, timestamp: Timestamp - ) -> OdraResult { + ) -> Result { log::info(format!( "Calling {:?} with entrypoint \"{}\" through proxy.", address.to_formatted_string(), @@ -561,9 +541,9 @@ impl CasperClient { let deploy = self.new_deploy(session, self.gas, timestamp); let request = put_deploy_request(deploy); - let response: PutDeployResult = self.post_request(request).await; + let response: PutDeployResult = self.post_request(request).await?; let deploy_hash = response.deploy_hash; - let result = self.wait_for_deploy(deploy_hash).await; + let result = self.wait_for_deploy(deploy_hash).await?; self.process_execution(result, ContractId::Address(address), deploy_hash)?; Ok(self.get_proxy_result().await) } @@ -574,7 +554,7 @@ impl CasperClient { addr: Address, call_def: CallDef, timestamp: Timestamp - ) -> OdraResult { + ) -> Result { log::info(format!( "Calling {:?} directly with entrypoint \"{}\".", addr.to_formatted_string(), @@ -593,9 +573,9 @@ impl CasperClient { }; let deploy = self.new_deploy(session, self.gas, timestamp); let request = put_deploy_request(deploy); - let response: PutDeployResult = self.post_request(request).await; + let response: PutDeployResult = self.post_request(request).await?; let deploy_hash = response.deploy_hash; - let result = self.wait_for_deploy(deploy_hash).await; + let result = self.wait_for_deploy(deploy_hash).await?; self.process_execution(result, ContractId::Address(addr), deploy_hash) .map(|_| ().to_bytes().expect("Couldn't serialize (). This shouldn't happen.").into()) @@ -621,24 +601,26 @@ impl CasperClient { .stored_value } - async fn wait_for_deploy(&self, deploy_hash: DeployHash) -> ExecutionResult { + async fn wait_for_deploy(&self, deploy_hash: DeployHash) -> Result { let deploy_hash_str = format!("{:?}", deploy_hash.inner()); - let time_diff = Duration::from_secs(15); let final_result; loop { log::wait(format!( "Waiting {:?} for {:?}.", - &time_diff, &deploy_hash_str + &DEPLOY_WAIT_TIME, &deploy_hash_str )); - sleep(time_diff).await; - let result = self.get_deploy(deploy_hash).await.execution_info.unwrap(); - if result.execution_result.is_some() { - final_result = result.execution_result.unwrap(); + sleep(DEPLOY_WAIT_TIME).await; + let result = self.get_deploy(deploy_hash).await.execution_info; + if result.is_some() { + final_result = result + .ok_or(LivenetToDoError)? + .execution_result + .ok_or(LivenetToDoError)?; break; } } - final_result.clone() + Ok(final_result.clone()) } fn process_execution( @@ -646,7 +628,7 @@ impl CasperClient { result: ExecutionResult, called_contract_id: ContractId, deploy_hash: DeployHash - ) -> OdraResult<()> { + ) -> Result<()> { let deploy_hash_str = format!("{:?}", deploy_hash.inner()); match result { ExecutionResult::V1(r) => match r { @@ -663,7 +645,7 @@ impl CasperClient { "Deploy V1 {:?} failed with error: {:?}.", deploy_hash_str, error_msg )); - Err(odra_error) + Err(LivenetToDoError) } Success { .. } => { log::info(format!( @@ -686,7 +668,7 @@ impl CasperClient { "Deploy V2 {:?} failed with error: {:?}.", deploy_hash_str, e )); - Err(OdraError::ExecutionError(ExecutionError::UnexpectedError)) + Err(LivenetToDoError) } } } @@ -717,42 +699,31 @@ impl CasperClient { ) } - async fn safe_post_request(&self, request: Value) -> JsonRpc { + async fn safe_post_request(&self, request: Value) -> Result { let client = reqwest::Client::new(); let mut client = client.post(self.node_address_rpc()); if let Some(token) = &self.configuration.cspr_cloud_auth_token { client = client.header("Authorization", token); } - let response = client.json(&request).send().await; - let response = match response { - Ok(r) => r, - Err(e) => { - log::error(format!("Couldn't send request: {:?}", e)); - panic!("Couldn't send request") - } - }; - let json: JsonRpc = response.json().await.unwrap_or_else(|e| { - log::error(format!("Couldn't parse response: {:?}", e)); - panic!("Couldn't parse response") - }); - json - } - - async fn post_request(&self, request: Value) -> T { - let json = self.safe_post_request(request.clone()).await; - json.get_result() - .map(|result| { - serde_json::from_value::(result.clone()).unwrap_or_else(|e| { - log::error(format!("Couldn't parse result: {:?}", e)); - panic!("Couldn't parse result") - }) - }) - .unwrap_or_else(|| { - log::error(format!("Couldn't get result: {:?} - {:?}", json, request)); - panic!("Couldn't get result") - }) + let response = client + .json(&request) + .send() + .await + .map_err(|_| LivenetToDoError)?; + let json: JsonRpc = response.json().await.map_err(|_| LivenetToDoError)?; + Ok(json) + } + + async fn post_request(&self, request: Value) -> Result { + let json = self.safe_post_request(request.clone()).await?; + let result = json + .get_result() + .map(|result| serde_json::from_value::(result.clone())) + .unwrap() + .unwrap(); + Ok(result) } fn address_secret_key(&self, address: &Address) -> &SecretKey { diff --git a/odra-casper/rpc-client/src/error.rs b/odra-casper/rpc-client/src/error.rs new file mode 100644 index 00000000..2d63f44d --- /dev/null +++ b/odra-casper/rpc-client/src/error.rs @@ -0,0 +1,23 @@ +use odra_core::OdraError; +use odra_core::VmError::Other; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Livenet generic error")] + LivenetToDoError, + #[error("Livenet communication error")] + RpcCommunicationError +} + +// impl Into for Error { +// fn into(self) -> OdraError { +// OdraError::VmError(Other(self.to_string())) +// } +// } + +impl From for OdraError { + fn from(value: Error) -> Self { + value.into() + } +} diff --git a/odra-casper/rpc-client/src/lib.rs b/odra-casper/rpc-client/src/lib.rs index 6b7e1043..b267be9a 100644 --- a/odra-casper/rpc-client/src/lib.rs +++ b/odra-casper/rpc-client/src/lib.rs @@ -4,5 +4,6 @@ extern crate core; pub mod casper_client; +mod error; pub mod log; pub mod utils; From 1f4987f52c517b825cfc1b662853882c360676c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 7 Aug 2024 15:03:51 +0200 Subject: [PATCH 11/22] Moved handling Odra errors outside rpc client. --- core/src/contract_container.rs | 10 ++++- core/src/contract_register.rs | 7 ++++ odra-casper/livenet-env/Cargo.toml | 3 ++ .../src}/error.rs | 4 +- odra-casper/livenet-env/src/lib.rs | 1 + odra-casper/livenet-env/src/livenet_host.rs | 31 +++++++++++++--- odra-casper/rpc-client/Cargo.toml | 3 -- odra-casper/rpc-client/src/casper_client.rs | 37 +++---------------- odra-casper/rpc-client/src/error.rs | 5 ++- odra-vm/src/vm/odra_vm.rs | 2 +- 10 files changed, 58 insertions(+), 45 deletions(-) rename odra-casper/{rpc-client/src/casper_client => livenet-env/src}/error.rs (97%) diff --git a/core/src/contract_container.rs b/core/src/contract_container.rs index 1ccb9e56..dca47307 100644 --- a/core/src/contract_container.rs +++ b/core/src/contract_container.rs @@ -9,13 +9,15 @@ use casper_types::U512; /// The container validates a contract call definition before calling the entry point. #[derive(Clone)] pub struct ContractContainer { + contract_name: String, entry_points_caller: EntryPointsCaller } impl ContractContainer { /// Creates a new instance of `ContractContainer`. - pub fn new(entry_points_caller: EntryPointsCaller) -> Self { + pub fn new(name: &str, entry_points_caller: EntryPointsCaller) -> Self { Self { + contract_name: name.to_string(), entry_points_caller } } @@ -36,6 +38,10 @@ impl ContractContainer { } self.entry_points_caller.call(call_def) } + + pub fn name(&self) -> &str { + &self.contract_name + } } #[cfg(test)] @@ -85,6 +91,7 @@ mod tests { ))) }); Self { + contract_name: "empty".to_string(), entry_points_caller } } @@ -113,6 +120,7 @@ mod tests { }); Self { + contract_name: "with_entrypoints".to_string(), entry_points_caller } } diff --git a/core/src/contract_register.rs b/core/src/contract_register.rs index 40adb74b..e3aad343 100644 --- a/core/src/contract_register.rs +++ b/core/src/contract_register.rs @@ -28,4 +28,11 @@ impl ContractRegister { } Err(OdraError::VmError(VmError::InvalidContractAddress)) } + + pub fn get(&self, addr: &Address) -> Option<&str> { + match self.contracts.get(addr) { + Some(contract) => Some(contract.name()), + None => None + } + } } diff --git a/odra-casper/livenet-env/Cargo.toml b/odra-casper/livenet-env/Cargo.toml index 7122a68b..c437abf0 100644 --- a/odra-casper/livenet-env/Cargo.toml +++ b/odra-casper/livenet-env/Cargo.toml @@ -14,6 +14,9 @@ odra-casper-rpc-client = { workspace = true, features = ["std"]} blake2 = { workspace = true } log = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread"]} +anyhow = { workspace = true } +serde_json = { workspace = true } +odra-schema = { workspace = true } [lints.rust] missing_docs = "warn" diff --git a/odra-casper/rpc-client/src/casper_client/error.rs b/odra-casper/livenet-env/src/error.rs similarity index 97% rename from odra-casper/rpc-client/src/casper_client/error.rs rename to odra-casper/livenet-env/src/error.rs index 84f4a5a2..6fd6c3eb 100644 --- a/odra-casper/rpc-client/src/casper_client/error.rs +++ b/odra-casper/livenet-env/src/error.rs @@ -1,9 +1,9 @@ use anyhow::{anyhow, Result}; -use odra_core::{ExecutionError, OdraError}; +use odra_core::{ExecutionError, OdraError, OdraResult}; use serde_json::Value; use std::{fs, path::PathBuf}; -pub(crate) fn find(contract_name: &str, error_msg: &str) -> Result<(String, OdraError)> { +pub fn find(contract_name: &str, error_msg: &str) -> Result<(String, OdraError)> { if error_msg == "Out of gas error" { return Ok(("OutOfGas".to_string(), ExecutionError::OutOfGas.into())); } diff --git a/odra-casper/livenet-env/src/lib.rs b/odra-casper/livenet-env/src/lib.rs index 3a93f284..31a2a6bd 100644 --- a/odra-casper/livenet-env/src/lib.rs +++ b/odra-casper/livenet-env/src/lib.rs @@ -1,4 +1,5 @@ //! This crate provides a host environment for the livenet. +pub mod error; pub mod livenet_contract_env; pub mod livenet_host; diff --git a/odra-casper/livenet-env/src/livenet_host.rs b/odra-casper/livenet-env/src/livenet_host.rs index 3f697546..46692784 100644 --- a/odra-casper/livenet-env/src/livenet_host.rs +++ b/odra-casper/livenet-env/src/livenet_host.rs @@ -1,7 +1,8 @@ //! Livenet implementation of HostContext for HostEnv. +use crate::error; use crate::livenet_contract_env::LivenetContractEnv; -use odra_casper_rpc_client::casper_client::CasperClient; +use odra_casper_rpc_client::casper_client::{CasperClient, ContractId}; use odra_casper_rpc_client::log::info; use odra_core::callstack::{Callstack, CallstackElement}; use odra_core::casper_types::bytesrepr::ToBytes; @@ -10,7 +11,7 @@ use odra_core::entry_point_callback::EntryPointsCaller; use odra_core::{ casper_types::{bytesrepr::Bytes, PublicKey, RuntimeArgs, U512}, host::HostContext, - Address, CallDef, ContractEnv, GasReport + Address, CallDef, ContractEnv, GasReport, OdraError }; use odra_core::{prelude::*, EventError, OdraResult}; use odra_core::{ContractContainer, ContractRegister}; @@ -162,11 +163,16 @@ impl HostContext for LivenetHost { let address = { let mut client = self.casper_client.borrow_mut(); let rt = Runtime::new().unwrap(); - rt.block_on(async { + match rt.block_on(async { client .deploy_wasm(name, init_args, timestamp, wasm_bytes) .await - })? + }) { + Ok(addr) => addr, + Err(e) => { + todo!("Handle error: {:?}", e); + } + } }; self.register_contract(address, name.to_string(), entry_points_caller); Ok(address) @@ -181,7 +187,10 @@ impl HostContext for LivenetHost { self.contract_register .write() .expect("Couldn't write contract register.") - .add(address, ContractContainer::new(entry_points_caller)); + .add( + address, + ContractContainer::new(&contract_name, entry_points_caller) + ); self.casper_client .borrow_mut() .register_name(address, contract_name); @@ -220,6 +229,18 @@ impl HostContext for LivenetHost { } } +impl LivenetHost { + fn find_error(&self, contract_id: ContractId, error_msg: &str) -> Option<(String, OdraError)> { + match contract_id { + ContractId::Name(contract_name) => error::find(&contract_name, error_msg).ok(), + ContractId::Address(addr) => match self.contract_register.read().unwrap().get(&addr) { + Some(contract_name) => error::find(contract_name, error_msg).ok(), + None => None + } + } + } +} + fn find_wasm_file_path(wasm_file_name: &str) -> PathBuf { let mut path = PathBuf::from("wasm") .join(wasm_file_name) diff --git a/odra-casper/rpc-client/Cargo.toml b/odra-casper/rpc-client/Cargo.toml index 434e26a5..0020e931 100644 --- a/odra-casper/rpc-client/Cargo.toml +++ b/odra-casper/rpc-client/Cargo.toml @@ -10,7 +10,6 @@ repository = { workspace = true } [dependencies] odra-core = { workspace = true } -odra-schema = { workspace = true } casper-execution-engine = { workspace = true } casper-types = { workspace = true } casper-client = { workspace = true } @@ -32,8 +31,6 @@ bytes = "1.5.0" humantime = "2.1.0" tokio = { workspace = true, features = ["rt-multi-thread"]} base16 = { workspace = true } -snafu = { version = "0.8.4", no-default-features = true } -anyhow = { workspace = true } [features] default = [] diff --git a/odra-casper/rpc-client/src/casper_client.rs b/odra-casper/rpc-client/src/casper_client.rs index 9819d74e..5723718c 100644 --- a/odra-casper/rpc-client/src/casper_client.rs +++ b/odra-casper/rpc-client/src/casper_client.rs @@ -10,7 +10,7 @@ use std::time::Duration; use crate::casper_client::configuration::CasperClientConfiguration; use crate::error::Error; -use crate::error::Error::LivenetToDoError; +use crate::error::Error::{ExecutionError, LivenetToDoError}; use crate::log; use casper_client::cli::{ get_balance, get_deploy, get_dictionary_item, get_entity, get_node_status, get_state_root_hash, @@ -30,12 +30,11 @@ use odra_core::{ runtime_args, CLTyped, PublicKey, RuntimeArgs, SecretKey, U512 }, consts::*, - Address, CallDef, ExecutionError, OdraError + Address, CallDef, OdraError }; use tokio::time::sleep; pub mod configuration; -mod error; /// Environment variable holding a path to a secret key of a main account. pub const ENV_SECRET_KEY: &str = "ODRA_CASPER_LIVENET_SECRET_KEY_PATH"; @@ -56,7 +55,7 @@ pub const DEPLOY_WAIT_TIME: Duration = Duration::from_secs(5); pub type Result = core::result::Result; -enum ContractId { +pub enum ContractId { Name(String), Address(Address) } @@ -493,16 +492,6 @@ impl CasperClient { self.contracts.insert(address, contract_name); } - fn find_error(&self, contract_id: ContractId, error_msg: &str) -> Option<(String, OdraError)> { - match contract_id { - ContractId::Name(contract_name) => error::find(&contract_name, error_msg).ok(), - ContractId::Address(addr) => match self.contracts.get(&addr) { - Some(contract_name) => error::find(contract_name, error_msg).ok(), - None => None - } - } - } - /// Deploy the entrypoint call using getter_proxy. /// It runs the getter_proxy contract in an account context and stores the return value of the call /// in under the key RESULT_KEY. @@ -633,19 +622,11 @@ impl CasperClient { match result { ExecutionResult::V1(r) => match r { Failure { error_message, .. } => { - let (error_msg, odra_error) = - match self.find_error(called_contract_id, &error_message) { - Some((contract_error, odra_error)) => (contract_error, odra_error), - None => ( - error_message, - OdraError::ExecutionError(ExecutionError::UnexpectedError) - ) - }; log::error(format!( "Deploy V1 {:?} failed with error: {:?}.", - deploy_hash_str, error_msg + deploy_hash_str, error_message )); - Err(LivenetToDoError) + Err(ExecutionError { error_message }) } Success { .. } => { log::info(format!( @@ -663,13 +644,7 @@ impl CasperClient { )); Ok(()) } - Some(e) => { - log::error(format!( - "Deploy V2 {:?} failed with error: {:?}.", - deploy_hash_str, e - )); - Err(LivenetToDoError) - } + Some(error_message) => Err(ExecutionError { error_message }) } } } diff --git a/odra-casper/rpc-client/src/error.rs b/odra-casper/rpc-client/src/error.rs index 2d63f44d..e4e17fd8 100644 --- a/odra-casper/rpc-client/src/error.rs +++ b/odra-casper/rpc-client/src/error.rs @@ -1,5 +1,4 @@ use odra_core::OdraError; -use odra_core::VmError::Other; use thiserror::Error; #[derive(Debug, Error)] @@ -7,7 +6,9 @@ pub enum Error { #[error("Livenet generic error")] LivenetToDoError, #[error("Livenet communication error")] - RpcCommunicationError + RpcCommunicationError, + #[error("Livenet execution error")] + ExecutionError { error_message: String } } // impl Into for Error { diff --git a/odra-vm/src/vm/odra_vm.rs b/odra-vm/src/vm/odra_vm.rs index f8365970..fb67bcf3 100644 --- a/odra-vm/src/vm/odra_vm.rs +++ b/odra-vm/src/vm/odra_vm.rs @@ -43,7 +43,7 @@ impl OdraVm { let address = { self.state.write().unwrap().next_contract_address() }; // Register new contract under the new address. { - let contract = ContractContainer::new(entry_points_caller); + let contract = ContractContainer::new(name, entry_points_caller); self.contract_register .write() .unwrap() From 5b15f3270d01ecad49c5365f5e3ee4188cfdc657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 7 Aug 2024 15:55:41 +0200 Subject: [PATCH 12/22] Removed casper types through odra types from rpc client. --- odra-casper/livenet-env/src/error.rs | 7 ++-- odra-casper/rpc-client/src/casper_client.rs | 39 ++++++++------------- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/odra-casper/livenet-env/src/error.rs b/odra-casper/livenet-env/src/error.rs index 6fd6c3eb..e0463eb1 100644 --- a/odra-casper/livenet-env/src/error.rs +++ b/odra-casper/livenet-env/src/error.rs @@ -1,7 +1,9 @@ +use std::{fs, path::PathBuf}; + use anyhow::{anyhow, Result}; -use odra_core::{ExecutionError, OdraError, OdraResult}; use serde_json::Value; -use std::{fs, path::PathBuf}; + +use odra_core::{ExecutionError, OdraError}; pub fn find(contract_name: &str, error_msg: &str) -> Result<(String, OdraError)> { if error_msg == "Out of gas error" { @@ -107,6 +109,7 @@ fn get_internal_error_name(error_num: u16) -> (String, OdraError) { #[cfg(test)] mod test { use anyhow::Result; + use odra_core::{ExecutionError, OdraError}; #[test] diff --git a/odra-casper/rpc-client/src/casper_client.rs b/odra-casper/rpc-client/src/casper_client.rs index 5723718c..a85667a9 100644 --- a/odra-casper/rpc-client/src/casper_client.rs +++ b/odra-casper/rpc-client/src/casper_client.rs @@ -18,20 +18,15 @@ use casper_client::cli::{ }; use casper_client::rpcs::results::{GetDeployResult, PutDeployResult}; use casper_client::Verbosity; -use casper_types::bytesrepr::deserialize_from_slice; +use casper_types::bytesrepr::{deserialize_from_slice, Bytes, FromBytes, ToBytes}; use casper_types::execution::ExecutionResultV1::{Failure, Success}; use casper_types::StoredValue::CLValue; -use casper_types::{execution::ExecutionResult, EntityAddr}; -use casper_types::{Deploy, DeployHash, ExecutableDeployItem, StoredValue, TimeDiff, Timestamp}; -use odra_core::casper_types::{sign, URef}; -use odra_core::{ - casper_types::{ - bytesrepr::{Bytes, FromBytes, ToBytes}, - runtime_args, CLTyped, PublicKey, RuntimeArgs, SecretKey, U512 - }, - consts::*, - Address, CallDef, OdraError +use casper_types::{ + execution::ExecutionResult, runtime_args, sign, CLTyped, EntityAddr, PublicKey, RuntimeArgs, + SecretKey, URef, U512 }; +use casper_types::{Deploy, DeployHash, ExecutableDeployItem, StoredValue, TimeDiff, Timestamp}; +use odra_core::{consts::*, Address, CallDef, OdraError}; use tokio::time::sleep; pub mod configuration; @@ -473,11 +468,7 @@ impl CasperClient { let response: PutDeployResult = self.post_request(request).await?; let deploy_hash = response.deploy_hash; let result = self.wait_for_deploy(deploy_hash).await?; - self.process_execution( - result, - ContractId::Name(contract_name.to_string()), - deploy_hash - )?; + self.process_execution(result, deploy_hash)?; let address = self.get_contract_address(contract_name).await; log::info(format!( @@ -533,7 +524,7 @@ impl CasperClient { let response: PutDeployResult = self.post_request(request).await?; let deploy_hash = response.deploy_hash; let result = self.wait_for_deploy(deploy_hash).await?; - self.process_execution(result, ContractId::Address(address), deploy_hash)?; + self.process_execution(result, deploy_hash)?; Ok(self.get_proxy_result().await) } @@ -566,8 +557,11 @@ impl CasperClient { let deploy_hash = response.deploy_hash; let result = self.wait_for_deploy(deploy_hash).await?; - self.process_execution(result, ContractId::Address(addr), deploy_hash) - .map(|_| ().to_bytes().expect("Couldn't serialize (). This shouldn't happen.").into()) + self.process_execution(result, deploy_hash).map(|_| { + ().to_bytes() + .expect("Couldn't serialize (). This shouldn't happen.") + .into() + }) } async fn query_global_state(&self, key: &str, path: Option) -> StoredValue { @@ -612,12 +606,7 @@ impl CasperClient { Ok(final_result.clone()) } - fn process_execution( - &self, - result: ExecutionResult, - called_contract_id: ContractId, - deploy_hash: DeployHash - ) -> Result<()> { + fn process_execution(&self, result: ExecutionResult, deploy_hash: DeployHash) -> Result<()> { let deploy_hash_str = format!("{:?}", deploy_hash.inner()); match result { ExecutionResult::V1(r) => match r { From 6948b6fb40500f1278f71cbfec64699b52dbc9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 7 Aug 2024 16:21:32 +0200 Subject: [PATCH 13/22] More of the same. --- core/src/contract_container.rs | 1 + core/src/contract_register.rs | 1 + .../resources/test/cep18_schema.json | 0 odra-casper/livenet-env/src/error.rs | 3 +++ odra-casper/livenet-env/src/livenet_host.rs | 16 +++++++++---- odra-casper/rpc-client/src/casper_client.rs | 23 +++++++------------ 6 files changed, 24 insertions(+), 20 deletions(-) rename odra-casper/{rpc-client => livenet-env}/resources/test/cep18_schema.json (100%) diff --git a/core/src/contract_container.rs b/core/src/contract_container.rs index dca47307..026b2b07 100644 --- a/core/src/contract_container.rs +++ b/core/src/contract_container.rs @@ -39,6 +39,7 @@ impl ContractContainer { self.entry_points_caller.call(call_def) } + /// Returns the name of the contract. pub fn name(&self) -> &str { &self.contract_name } diff --git a/core/src/contract_register.rs b/core/src/contract_register.rs index e3aad343..10bb3273 100644 --- a/core/src/contract_register.rs +++ b/core/src/contract_register.rs @@ -29,6 +29,7 @@ impl ContractRegister { Err(OdraError::VmError(VmError::InvalidContractAddress)) } + /// Returns the name of the contract at the given address. pub fn get(&self, addr: &Address) -> Option<&str> { match self.contracts.get(addr) { Some(contract) => Some(contract.name()), diff --git a/odra-casper/rpc-client/resources/test/cep18_schema.json b/odra-casper/livenet-env/resources/test/cep18_schema.json similarity index 100% rename from odra-casper/rpc-client/resources/test/cep18_schema.json rename to odra-casper/livenet-env/resources/test/cep18_schema.json diff --git a/odra-casper/livenet-env/src/error.rs b/odra-casper/livenet-env/src/error.rs index e0463eb1..2e6a4ce5 100644 --- a/odra-casper/livenet-env/src/error.rs +++ b/odra-casper/livenet-env/src/error.rs @@ -1,3 +1,5 @@ +//! Module for handling Odra errors coming out of the Livenet execution. + use std::{fs, path::PathBuf}; use anyhow::{anyhow, Result}; @@ -5,6 +7,7 @@ use serde_json::Value; use odra_core::{ExecutionError, OdraError}; +/// Finds the error message in the contract schema. pub fn find(contract_name: &str, error_msg: &str) -> Result<(String, OdraError)> { if error_msg == "Out of gas error" { return Ok(("OutOfGas".to_string(), ExecutionError::OutOfGas.into())); diff --git a/odra-casper/livenet-env/src/livenet_host.rs b/odra-casper/livenet-env/src/livenet_host.rs index 46692784..76fe6861 100644 --- a/odra-casper/livenet-env/src/livenet_host.rs +++ b/odra-casper/livenet-env/src/livenet_host.rs @@ -2,7 +2,7 @@ use crate::error; use crate::livenet_contract_env::LivenetContractEnv; -use odra_casper_rpc_client::casper_client::{CasperClient, ContractId}; +use odra_casper_rpc_client::casper_client::CasperClient; use odra_casper_rpc_client::log::info; use odra_core::callstack::{Callstack, CallstackElement}; use odra_core::casper_types::bytesrepr::ToBytes; @@ -21,6 +21,14 @@ use std::sync::RwLock; use std::thread::sleep; use tokio::runtime::Runtime; +/// Enum representing a contract identifier used by Livenet Host. +pub enum ContractId { + /// Contract name. + Name(String), + /// Contract address. + Address(Address) +} + /// LivenetHost struct. pub struct LivenetHost { casper_client: Rc>, @@ -191,9 +199,6 @@ impl HostContext for LivenetHost { address, ContractContainer::new(&contract_name, entry_points_caller) ); - self.casper_client - .borrow_mut() - .register_name(address, contract_name); } fn contract_env(&self) -> ContractEnv { @@ -230,7 +235,8 @@ impl HostContext for LivenetHost { } impl LivenetHost { - fn find_error(&self, contract_id: ContractId, error_msg: &str) -> Option<(String, OdraError)> { + // TODO: Use it + fn _find_error(&self, contract_id: ContractId, error_msg: &str) -> Option<(String, OdraError)> { match contract_id { ContractId::Name(contract_name) => error::find(&contract_name, error_msg).ok(), ContractId::Address(addr) => match self.contract_register.read().unwrap().get(&addr) { diff --git a/odra-casper/rpc-client/src/casper_client.rs b/odra-casper/rpc-client/src/casper_client.rs index a85667a9..a08096e5 100644 --- a/odra-casper/rpc-client/src/casper_client.rs +++ b/odra-casper/rpc-client/src/casper_client.rs @@ -4,7 +4,6 @@ use itertools::Itertools; use jsonrpc_lite::JsonRpc; use serde::de::DeserializeOwned; use serde_json::{json, Value}; -use std::collections::BTreeMap; use std::time::Duration; use crate::casper_client::configuration::CasperClientConfiguration; @@ -26,7 +25,12 @@ use casper_types::{ SecretKey, URef, U512 }; use casper_types::{Deploy, DeployHash, ExecutableDeployItem, StoredValue, TimeDiff, Timestamp}; -use odra_core::{consts::*, Address, CallDef, OdraError}; +use odra_core::casper_event_standard::EVENTS_LENGTH; +use odra_core::consts::{ + AMOUNT_ARG, ARGS_ARG, ATTACHED_VALUE_ARG, ENTRY_POINT_ARG, EVENTS, PACKAGE_HASH_ARG, + RESULT_KEY, STATE_KEY +}; +use odra_core::{Address, CallDef}; use tokio::time::sleep; pub mod configuration; @@ -50,17 +54,11 @@ pub const DEPLOY_WAIT_TIME: Duration = Duration::from_secs(5); pub type Result = core::result::Result; -pub enum ContractId { - Name(String), - Address(Address) -} - /// Client for interacting with Casper node. pub struct CasperClient { configuration: CasperClientConfiguration, active_account: usize, - gas: U512, - contracts: BTreeMap + gas: U512 } impl CasperClient { @@ -69,8 +67,7 @@ impl CasperClient { CasperClient { configuration, active_account: 0, - gas: U512::zero(), - contracts: BTreeMap::new() + gas: U512::zero() } } @@ -479,10 +476,6 @@ impl CasperClient { Ok(address) } - pub fn register_name(&mut self, address: Address, contract_name: String) { - self.contracts.insert(address, contract_name); - } - /// Deploy the entrypoint call using getter_proxy. /// It runs the getter_proxy contract in an account context and stores the return value of the call /// in under the key RESULT_KEY. From 7873f8f33426ad5d9c13b8bae1eaabbc28f482f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Thu, 8 Aug 2024 12:08:08 +0200 Subject: [PATCH 14/22] Removed std from rpc client. --- odra-casper/rpc-client/Cargo.toml | 4 ++-- odra-casper/rpc-client/src/casper_client.rs | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/odra-casper/rpc-client/Cargo.toml b/odra-casper/rpc-client/Cargo.toml index 0020e931..3d9d9616 100644 --- a/odra-casper/rpc-client/Cargo.toml +++ b/odra-casper/rpc-client/Cargo.toml @@ -29,9 +29,9 @@ prettycli = "0.1.1" thiserror = "1.0.40" bytes = "1.5.0" humantime = "2.1.0" -tokio = { workspace = true, features = ["rt-multi-thread"]} base16 = { workspace = true } +tokio = { workspace = true, optional = true } [features] default = [] -std = ["casper-types/std-fs-io"] \ No newline at end of file +std = ["casper-types/std-fs-io", "tokio/rt"] \ No newline at end of file diff --git a/odra-casper/rpc-client/src/casper_client.rs b/odra-casper/rpc-client/src/casper_client.rs index a08096e5..3b191625 100644 --- a/odra-casper/rpc-client/src/casper_client.rs +++ b/odra-casper/rpc-client/src/casper_client.rs @@ -4,7 +4,6 @@ use itertools::Itertools; use jsonrpc_lite::JsonRpc; use serde::de::DeserializeOwned; use serde_json::{json, Value}; -use std::time::Duration; use crate::casper_client::configuration::CasperClientConfiguration; @@ -31,7 +30,6 @@ use odra_core::consts::{ RESULT_KEY, STATE_KEY }; use odra_core::{Address, CallDef}; -use tokio::time::sleep; pub mod configuration; @@ -50,7 +48,7 @@ pub const ENV_CSPR_CLOUD_AUTH_TOKEN: &str = "CSPR_CLOUD_AUTH_TOKEN"; /// Environment variable holding a path to an additional .env file. pub const ENV_LIVENET_ENV_FILE: &str = "ODRA_CASPER_LIVENET_ENV"; /// Time between retries when waiting for a deploy to be processed. -pub const DEPLOY_WAIT_TIME: Duration = Duration::from_secs(5); +pub const DEPLOY_WAIT_TIME: u64 = 5; pub type Result = core::result::Result; @@ -586,7 +584,16 @@ impl CasperClient { "Waiting {:?} for {:?}.", &DEPLOY_WAIT_TIME, &deploy_hash_str )); - sleep(DEPLOY_WAIT_TIME).await; + + #[cfg(feature = "std")] + { + tokio::time::sleep(std::time::Duration::from_secs(DEPLOY_WAIT_TIME)).await; + } + #[cfg(not(feature = "std"))] + { + // TODO: Implement sleep for no_std + } + let result = self.get_deploy(deploy_hash).await.execution_info; if result.is_some() { final_result = result From ab3660c0a8219e21a9d0041e6b5886aff8119705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Mon, 12 Aug 2024 13:04:38 +0200 Subject: [PATCH 15/22] Fixed discovering errors. --- Cargo.toml | 1 + core/src/address.rs | 11 +--- examples/bin/livenet_tests.rs | 19 +++---- .../balance_checker_schema.json | 4 +- .../cross_contract_schema.json | 4 +- .../dog_contract2_schema.json | 4 +- .../dog_contract3_schema.json | 4 +- .../dog_contract_schema.json | 4 +- .../host_contract_schema.json | 4 +- .../livenet_contract_schema.json | 18 ++++++- .../math_engine_schema.json | 4 +- .../mock_moderated_schema.json | 4 +- .../modules_contract_schema.json | 4 +- .../my_contract_schema.json | 4 +- .../nested_odra_types_contract_schema.json | 4 +- .../owned_contract_schema.json | 4 +- .../owned_token_schema.json | 2 +- .../party_contract_schema.json | 4 +- .../pauseable_counter_schema.json | 4 +- .../public_wallet_schema.json | 4 +- .../reentrancy_mock_schema.json | 4 +- .../signature_verifier_schema.json | 4 +- .../testing_contract_schema.json | 4 +- .../time_lock_wallet_schema.json | 4 +- .../token_manager_schema.json | 4 +- .../casper_contract_schemas/token_schema.json | 4 +- .../legacy/livenet_contract_schema.json | 8 +++ examples/src/features/livenet.rs | 16 +++++- odra-casper/livenet-env/src/error.rs | 5 +- odra-casper/livenet-env/src/livenet_host.rs | 52 ++++++++++++------- odra-casper/rpc-client/src/casper_client.rs | 13 +++-- odra-casper/rpc-client/src/error.rs | 12 +++-- odra-schema/Cargo.toml | 1 + odra-schema/src/lib.rs | 11 +++- 34 files changed, 160 insertions(+), 93 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 053a1723..0cdea337 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ base16 = "0.2" base64 = "0.22" serde-json-wasm = "1.0" anyhow = "1.0.86" +convert_case = "0.6.0" [patch.crates-io] casper-types = { version = "5.0.0", git = "https://github.com/casper-network/casper-node", branch = "rustSDK-feat-2.0" } diff --git a/core/src/address.rs b/core/src/address.rs index 0766316c..ef099f3c 100644 --- a/core/src/address.rs +++ b/core/src/address.rs @@ -6,7 +6,7 @@ use casper_types::system::Caller; use casper_types::{ account::AccountHash, bytesrepr::{self, FromBytes, ToBytes}, - AddressableEntityHash, CLType, CLTyped, EntityAddr, HashAddr, Key, PackageHash, PublicKey + CLType, CLTyped, EntityAddr, HashAddr, Key, PackageHash, PublicKey }; use serde::{Deserialize, Serialize}; @@ -233,15 +233,6 @@ impl ToString for Address { } } -impl From
for AddressableEntityHash { - fn from(value: Address) -> Self { - match value { - Address::Account(account) => AddressableEntityHash::new(account.value()), - Address::Contract(contract) => AddressableEntityHash::new(contract.value()) - } - } -} - impl Serialize for Address { fn serialize(&self, serializer: S) -> Result { let s = self.to_string(); diff --git a/examples/bin/livenet_tests.rs b/examples/bin/livenet_tests.rs index 75360935..6b601a29 100644 --- a/examples/bin/livenet_tests.rs +++ b/examples/bin/livenet_tests.rs @@ -1,7 +1,7 @@ //! This example demonstrates how to deploy and interact with a contract on the Livenet environment. use odra::casper_types::{U256, U512}; use odra::host::{Deployer, HostEnv, HostRef, HostRefLoader}; -use odra::Address; +use odra::{Address, ExecutionError}; use odra_examples::features::livenet::{LivenetContractHostRef, LivenetContractInitArgs}; use odra_modules::access::events::OwnershipTransferred; use odra_modules::erc20::{Erc20HostRef, Erc20InitArgs}; @@ -14,14 +14,14 @@ fn main() { println!("Block time: {}", env.block_time()); // Funds can be transferred - let another_account = env.get_account(1); - let another_account_balance = env.balance_of(&another_account); - env.transfer(another_account.clone(), U512::from(10_000_000_000u64)) - .unwrap(); - assert_eq!( - env.balance_of(&another_account), - another_account_balance + U512::from(10_000_000_000u64) - ); + // let another_account = env.get_account(1); + // let another_account_balance = env.balance_of(&another_account); + // env.transfer(another_account.clone(), U512::from(10_000_000_000u64)) + // .unwrap(); + // assert_eq!( + // env.balance_of(&another_account), + // another_account_balance + U512::from(10_000_000_000u64) + // ); // Contract can be deployed env.set_gas(30_000_000_000u64); @@ -36,6 +36,7 @@ fn main() { // let result = contract.try_push_on_stack(1).unwrap_err(); // assert_eq!(result, ExecutionError::OutOfGas.into()); contract.push_on_stack(1); + let r = contract.try_function_that_reverts(); // Set gas will be used for all subsequent calls env.set_gas(1_000_000_000u64); diff --git a/examples/resources/casper_contract_schemas/balance_checker_schema.json b/examples/resources/casper_contract_schemas/balance_checker_schema.json index 1656d279..58a1a31f 100644 --- a/examples/resources/casper_contract_schemas/balance_checker_schema.json +++ b/examples/resources/casper_contract_schemas/balance_checker_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "BalanceChecker", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/casper_contract_schemas/cross_contract_schema.json b/examples/resources/casper_contract_schemas/cross_contract_schema.json index db1cd131..34deeb9f 100644 --- a/examples/resources/casper_contract_schemas/cross_contract_schema.json +++ b/examples/resources/casper_contract_schemas/cross_contract_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "CrossContract", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/casper_contract_schemas/dog_contract2_schema.json b/examples/resources/casper_contract_schemas/dog_contract2_schema.json index 18ed1ac2..38d51248 100644 --- a/examples/resources/casper_contract_schemas/dog_contract2_schema.json +++ b/examples/resources/casper_contract_schemas/dog_contract2_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "DogContract2", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/casper_contract_schemas/dog_contract3_schema.json b/examples/resources/casper_contract_schemas/dog_contract3_schema.json index aae34e2d..424ecca9 100644 --- a/examples/resources/casper_contract_schemas/dog_contract3_schema.json +++ b/examples/resources/casper_contract_schemas/dog_contract3_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "DogContract3", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/casper_contract_schemas/dog_contract_schema.json b/examples/resources/casper_contract_schemas/dog_contract_schema.json index 4ec2dc25..5b57e58f 100644 --- a/examples/resources/casper_contract_schemas/dog_contract_schema.json +++ b/examples/resources/casper_contract_schemas/dog_contract_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "DogContract", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/casper_contract_schemas/host_contract_schema.json b/examples/resources/casper_contract_schemas/host_contract_schema.json index 438f891d..27c93a44 100644 --- a/examples/resources/casper_contract_schemas/host_contract_schema.json +++ b/examples/resources/casper_contract_schemas/host_contract_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "HostContract", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/casper_contract_schemas/livenet_contract_schema.json b/examples/resources/casper_contract_schemas/livenet_contract_schema.json index 66aebf27..60f44a59 100644 --- a/examples/resources/casper_contract_schemas/livenet_contract_schema.json +++ b/examples/resources/casper_contract_schemas/livenet_contract_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "LivenetContract", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [ { "struct": { @@ -55,6 +55,11 @@ "name": "RoleRenounceForAnotherAddress", "description": "The role cannot be renounced for another address.", "discriminant": 20004 + }, + { + "name": "SillyError", + "description": "Silly error.", + "discriminant": 1 } ], "entry_points": [ @@ -134,6 +139,15 @@ "return_ty": "Unit", "is_contract_context": true, "access": "public" + }, + { + "name": "function_that_reverts", + "description": "Function that reverts with a silly error.", + "is_mutable": true, + "arguments": [], + "return_ty": "Unit", + "is_contract_context": true, + "access": "public" } ], "events": [ diff --git a/examples/resources/casper_contract_schemas/math_engine_schema.json b/examples/resources/casper_contract_schemas/math_engine_schema.json index 95a4d8f2..d127265e 100644 --- a/examples/resources/casper_contract_schemas/math_engine_schema.json +++ b/examples/resources/casper_contract_schemas/math_engine_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "MathEngine", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/casper_contract_schemas/mock_moderated_schema.json b/examples/resources/casper_contract_schemas/mock_moderated_schema.json index c8879ab2..ee039e5b 100644 --- a/examples/resources/casper_contract_schemas/mock_moderated_schema.json +++ b/examples/resources/casper_contract_schemas/mock_moderated_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "MockModerated", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [ { "struct": { diff --git a/examples/resources/casper_contract_schemas/modules_contract_schema.json b/examples/resources/casper_contract_schemas/modules_contract_schema.json index 2aa84123..bf23e423 100644 --- a/examples/resources/casper_contract_schemas/modules_contract_schema.json +++ b/examples/resources/casper_contract_schemas/modules_contract_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "ModulesContract", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/casper_contract_schemas/my_contract_schema.json b/examples/resources/casper_contract_schemas/my_contract_schema.json index be523eb1..31934d08 100644 --- a/examples/resources/casper_contract_schemas/my_contract_schema.json +++ b/examples/resources/casper_contract_schemas/my_contract_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "MyContract", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [ { "struct": { diff --git a/examples/resources/casper_contract_schemas/nested_odra_types_contract_schema.json b/examples/resources/casper_contract_schemas/nested_odra_types_contract_schema.json index 9a12fd73..41ad38a2 100644 --- a/examples/resources/casper_contract_schemas/nested_odra_types_contract_schema.json +++ b/examples/resources/casper_contract_schemas/nested_odra_types_contract_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "NestedOdraTypesContract", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [ { "struct": { diff --git a/examples/resources/casper_contract_schemas/owned_contract_schema.json b/examples/resources/casper_contract_schemas/owned_contract_schema.json index 64c2c32e..461dcfe0 100644 --- a/examples/resources/casper_contract_schemas/owned_contract_schema.json +++ b/examples/resources/casper_contract_schemas/owned_contract_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "OwnedContract", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [ { diff --git a/examples/resources/casper_contract_schemas/owned_token_schema.json b/examples/resources/casper_contract_schemas/owned_token_schema.json index 520de42d..9a8a9786 100644 --- a/examples/resources/casper_contract_schemas/owned_token_schema.json +++ b/examples/resources/casper_contract_schemas/owned_token_schema.json @@ -1,6 +1,6 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, diff --git a/examples/resources/casper_contract_schemas/party_contract_schema.json b/examples/resources/casper_contract_schemas/party_contract_schema.json index 5884a280..c4ff1c67 100644 --- a/examples/resources/casper_contract_schemas/party_contract_schema.json +++ b/examples/resources/casper_contract_schemas/party_contract_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "PartyContract", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [ { "struct": { diff --git a/examples/resources/casper_contract_schemas/pauseable_counter_schema.json b/examples/resources/casper_contract_schemas/pauseable_counter_schema.json index 8b98da04..9ce84753 100644 --- a/examples/resources/casper_contract_schemas/pauseable_counter_schema.json +++ b/examples/resources/casper_contract_schemas/pauseable_counter_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "PauseableCounter", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [ { "struct": { diff --git a/examples/resources/casper_contract_schemas/public_wallet_schema.json b/examples/resources/casper_contract_schemas/public_wallet_schema.json index 5ba6e16e..5790106d 100644 --- a/examples/resources/casper_contract_schemas/public_wallet_schema.json +++ b/examples/resources/casper_contract_schemas/public_wallet_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "PublicWallet", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/casper_contract_schemas/reentrancy_mock_schema.json b/examples/resources/casper_contract_schemas/reentrancy_mock_schema.json index 0cfc1c9e..61fcc010 100644 --- a/examples/resources/casper_contract_schemas/reentrancy_mock_schema.json +++ b/examples/resources/casper_contract_schemas/reentrancy_mock_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "ReentrancyMock", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/casper_contract_schemas/signature_verifier_schema.json b/examples/resources/casper_contract_schemas/signature_verifier_schema.json index 373b94cf..5931e374 100644 --- a/examples/resources/casper_contract_schemas/signature_verifier_schema.json +++ b/examples/resources/casper_contract_schemas/signature_verifier_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "SignatureVerifier", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/casper_contract_schemas/testing_contract_schema.json b/examples/resources/casper_contract_schemas/testing_contract_schema.json index e7b22084..f69db0f0 100644 --- a/examples/resources/casper_contract_schemas/testing_contract_schema.json +++ b/examples/resources/casper_contract_schemas/testing_contract_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "TestingContract", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/casper_contract_schemas/time_lock_wallet_schema.json b/examples/resources/casper_contract_schemas/time_lock_wallet_schema.json index 03444f63..de3419c7 100644 --- a/examples/resources/casper_contract_schemas/time_lock_wallet_schema.json +++ b/examples/resources/casper_contract_schemas/time_lock_wallet_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "TimeLockWallet", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [ { "struct": { diff --git a/examples/resources/casper_contract_schemas/token_manager_schema.json b/examples/resources/casper_contract_schemas/token_manager_schema.json index ac88a1f4..04c7ff29 100644 --- a/examples/resources/casper_contract_schemas/token_manager_schema.json +++ b/examples/resources/casper_contract_schemas/token_manager_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "TokenManager", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/casper_contract_schemas/token_schema.json b/examples/resources/casper_contract_schemas/token_schema.json index fdd2cf55..5992ce40 100644 --- a/examples/resources/casper_contract_schemas/token_schema.json +++ b/examples/resources/casper_contract_schemas/token_schema.json @@ -1,11 +1,11 @@ { "casper_contract_schema_version": 1, - "toolchain": "rustc 1.79.0-nightly (3a36386dc 2024-04-25)", + "toolchain": "rustc 1.81.0-nightly (cf2df68d1 2024-07-01)", "authors": [], "repository": null, "homepage": null, "contract_name": "Token", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "types": [], "errors": [], "entry_points": [ diff --git a/examples/resources/legacy/livenet_contract_schema.json b/examples/resources/legacy/livenet_contract_schema.json index 9175e4cb..1797145f 100644 --- a/examples/resources/legacy/livenet_contract_schema.json +++ b/examples/resources/legacy/livenet_contract_schema.json @@ -113,6 +113,14 @@ "return_ty": "Unit", "ty": "Public", "attributes": [] + }, + { + "name": "function_that_reverts", + "args": [], + "is_mutable": true, + "return_ty": "Unit", + "ty": "Public", + "attributes": [] } ] } \ No newline at end of file diff --git a/examples/src/features/livenet.rs b/examples/src/features/livenet.rs index 490f3de2..0bfbb091 100644 --- a/examples/src/features/livenet.rs +++ b/examples/src/features/livenet.rs @@ -1,12 +1,14 @@ //! This is an example contract used to showcase and test Livenet Environment. +use crate::features::livenet::Error::SillyError; use odra::casper_types::U256; +use odra::module::Revertible; use odra::prelude::*; use odra::{Address, List, SubModule, UnwrapOrRevert, Var}; use odra_modules::access::Ownable; use odra_modules::erc20::Erc20ContractRef; /// Contract used by the Livenet examples. -#[odra::module] +#[odra::module(errors = Error)] pub struct LivenetContract { creator: Var
, ownable: SubModule, @@ -58,6 +60,18 @@ impl LivenetContract { Erc20ContractRef::new(self.env(), self.erc20_address.get().unwrap()) .transfer(&self.env().caller(), &1.into()); } + + /// Function that reverts with a silly error. + pub fn function_that_reverts(&mut self) { + self.revert(SillyError) + } +} + +/// Errors that can occur in the `LivenetContract` module. +#[odra::odra_error] +pub enum Error { + /// Silly error. + SillyError = 1 } #[cfg(test)] diff --git a/odra-casper/livenet-env/src/error.rs b/odra-casper/livenet-env/src/error.rs index 2e6a4ce5..ce5e53a0 100644 --- a/odra-casper/livenet-env/src/error.rs +++ b/odra-casper/livenet-env/src/error.rs @@ -35,10 +35,11 @@ pub fn find(contract_name: &str, error_msg: &str) -> Result<(String, OdraError)> .as_array() .ok_or_else(|| anyhow!("Couldn't get value"))?; - errors + let f = errors .iter() .find_map(|err| match_error(err, error_num)) - .ok_or_else(|| anyhow!("Couldn't find error")) + .ok_or_else(|| anyhow!("Couldn't find error")); + f } fn match_error(val: &Value, error_num: u16) -> Option<(String, OdraError)> { diff --git a/odra-casper/livenet-env/src/livenet_host.rs b/odra-casper/livenet-env/src/livenet_host.rs index 76fe6861..f94c6a4a 100644 --- a/odra-casper/livenet-env/src/livenet_host.rs +++ b/odra-casper/livenet-env/src/livenet_host.rs @@ -5,9 +5,9 @@ use crate::livenet_contract_env::LivenetContractEnv; use odra_casper_rpc_client::casper_client::CasperClient; use odra_casper_rpc_client::log::info; use odra_core::callstack::{Callstack, CallstackElement}; -use odra_core::casper_types::bytesrepr::ToBytes; use odra_core::casper_types::Timestamp; use odra_core::entry_point_callback::EntryPointsCaller; +use odra_core::ExecutionError::{UnexpectedError, User}; use odra_core::{ casper_types::{bytesrepr::Bytes, PublicKey, RuntimeArgs, U512}, host::HostContext, @@ -22,6 +22,7 @@ use std::thread::sleep; use tokio::runtime::Runtime; /// Enum representing a contract identifier used by Livenet Host. +#[derive(Debug)] pub enum ContractId { /// Contract name. Name(String), @@ -142,20 +143,24 @@ impl HostContext for LivenetHost { client .deploy_entrypoint_call_with_proxy(*address, call_def, timestamp) .await - .map_err(|e| e.into()) + .map_err(|e| { + self.map_error_code_to_odra_error( + ContractId::Address(*address), + &e.error_message() + ) + }) }), - false => { - rt.block_on(async { - client - .deploy_entrypoint_call(*address, call_def, timestamp) - .await - })?; - Ok( - ().to_bytes() - .expect("Couldn't serialize (). This shouldn't happen.") - .into() - ) - } + false => rt.block_on(async { + let r = client + .deploy_entrypoint_call(*address, call_def, timestamp) + .await; + r.map_err(|e| { + self.map_error_code_to_odra_error( + ContractId::Address(*address), + &e.error_message() + ) + }) + }) } } @@ -230,19 +235,30 @@ impl HostContext for LivenetHost { let rt = Runtime::new().unwrap(); let timestamp = Timestamp::now(); let client = self.casper_client.borrow_mut(); - Ok(rt.block_on(async { client.transfer(to, amount, timestamp).await })?) + Ok(rt + .block_on(async { client.transfer(to, amount, timestamp).await }) + .map_err(|e| { + self.map_error_code_to_odra_error( + ContractId::Address(client.caller()), + &e.error_message() + ) + })?) } } impl LivenetHost { - // TODO: Use it - fn _find_error(&self, contract_id: ContractId, error_msg: &str) -> Option<(String, OdraError)> { - match contract_id { + fn map_error_code_to_odra_error(&self, contract_id: ContractId, error_msg: &str) -> OdraError { + let found = match contract_id { ContractId::Name(contract_name) => error::find(&contract_name, error_msg).ok(), ContractId::Address(addr) => match self.contract_register.read().unwrap().get(&addr) { Some(contract_name) => error::find(contract_name, error_msg).ok(), None => None } + }; + + match found { + None => OdraError::ExecutionError(UnexpectedError), + Some((message, error)) => {} } } } diff --git a/odra-casper/rpc-client/src/casper_client.rs b/odra-casper/rpc-client/src/casper_client.rs index 3b191625..d249ad1b 100644 --- a/odra-casper/rpc-client/src/casper_client.rs +++ b/odra-casper/rpc-client/src/casper_client.rs @@ -548,11 +548,12 @@ impl CasperClient { let deploy_hash = response.deploy_hash; let result = self.wait_for_deploy(deploy_hash).await?; - self.process_execution(result, deploy_hash).map(|_| { + let p = self.process_execution(result, deploy_hash).map(|_| { ().to_bytes() .expect("Couldn't serialize (). This shouldn't happen.") .into() - }) + }); + p } async fn query_global_state(&self, key: &str, path: Option) -> StoredValue { @@ -633,7 +634,13 @@ impl CasperClient { )); Ok(()) } - Some(error_message) => Err(ExecutionError { error_message }) + Some(error_message) => { + log::error(format!( + "Deploy V1 {:?} failed with error: {:?}.", + deploy_hash_str, error_message + )); + Err(ExecutionError { error_message }) + } } } } diff --git a/odra-casper/rpc-client/src/error.rs b/odra-casper/rpc-client/src/error.rs index e4e17fd8..556c090a 100644 --- a/odra-casper/rpc-client/src/error.rs +++ b/odra-casper/rpc-client/src/error.rs @@ -1,4 +1,4 @@ -use odra_core::OdraError; +use odra_core::{ExecutionError, OdraError, VmError}; use thiserror::Error; #[derive(Debug, Error)] @@ -17,8 +17,12 @@ pub enum Error { // } // } -impl From for OdraError { - fn from(value: Error) -> Self { - value.into() +impl Error { + pub fn error_message(&self) -> String { + match self { + Error::LivenetToDoError => "Livenet generic error".to_string(), + Error::RpcCommunicationError => "Livenet communication error".to_string(), + Error::ExecutionError { error_message } => error_message.to_string() + } } } diff --git a/odra-schema/Cargo.toml b/odra-schema/Cargo.toml index e7448eb1..38c80995 100644 --- a/odra-schema/Cargo.toml +++ b/odra-schema/Cargo.toml @@ -13,6 +13,7 @@ casper-contract-schema = { workspace = true } casper-types = { workspace = true } odra-core = { workspace = true } num-traits = { workspace = true } +convert_case = { workspace = true } [lints.rust] missing_docs = "warn" diff --git a/odra-schema/src/lib.rs b/odra-schema/src/lib.rs index 845c0e81..7403156f 100644 --- a/odra-schema/src/lib.rs +++ b/odra-schema/src/lib.rs @@ -11,6 +11,8 @@ use casper_contract_schema::{ NamedCLType, StructMember, UserError }; +use convert_case::{Boundary, Case, Casing}; + use odra_core::args::EntrypointArgument; const CCSV: u8 = 1; @@ -225,7 +227,7 @@ pub fn find_schema_file_path( root_path: PathBuf ) -> Result { let mut path = root_path - .join(format!("{}_schema.json", contract_name.to_lowercase())) + .join(format!("{}_schema.json", camel_to_snake(contract_name))) .with_extension("json"); let mut checked_paths = vec![]; @@ -438,3 +440,10 @@ mod test { assert_eq!(schema.events.len(), 1); } } + +pub fn camel_to_snake(text: T) -> String { + text.to_string() + .from_case(Case::UpperCamel) + .without_boundaries(&[Boundary::UpperDigit, Boundary::LowerDigit]) + .to_case(Case::Snake) +} From eb78c435b9695829916e4a7546b379cf95d8bc99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Tue, 13 Aug 2024 10:44:04 +0200 Subject: [PATCH 16/22] PR Fixes --- .gitignore | 1 + Cargo.toml | 4 +- benchmark/Cargo.toml | 2 +- core/src/address.rs | 6 +- core/src/callstack.rs | 6 +- core/src/error.rs | 4 +- core/src/host.rs | 37 ++- core/src/host_env.rs | 313 ------------------ examples/bin/livenet_tests.rs | 34 +- examples/ourcoin/Cargo.toml | 1 + examples/ourcoin/bin/build_schema.rs | 61 +--- examples/ourcoin/wasm/OurToken.wasm | Bin 251253 -> 0 bytes examples/src/features/livenet.rs | 8 +- justfile | 17 +- modules/Cargo.toml | 2 +- modules/src/cep18_token.rs | 7 +- modules/src/cep78/tests/events.rs | 2 +- odra-casper/livenet-env/src/livenet_host.rs | 31 +- odra-casper/proxy-caller/src/lib.rs | 9 - odra-casper/rpc-client/src/casper_client.rs | 31 +- .../src/casper_client/configuration.rs | 27 +- odra-casper/rpc-client/src/error.rs | 19 +- .../test-vm/resources/proxy_caller.wasm | Bin 42817 -> 46013 bytes .../resources/proxy_caller_with_return.wasm | Bin 44418 -> 47621 bytes odra-casper/test-vm/src/casper_host.rs | 9 +- odra-casper/test-vm/src/vm/casper_vm.rs | 11 +- odra-casper/wasm-env/Cargo.toml | 3 +- odra-casper/wasm-env/src/lib.rs | 4 - odra-schema/src/lib.rs | 15 +- odra-vm/src/odra_vm_host.rs | 2 +- odra-vm/src/vm/odra_vm.rs | 6 +- odra-vm/src/vm/odra_vm_state.rs | 16 +- odra-vm/src/vm/utils.rs | 1 + 33 files changed, 140 insertions(+), 549 deletions(-) delete mode 100644 core/src/host_env.rs delete mode 100755 examples/ourcoin/wasm/OurToken.wasm diff --git a/.gitignore b/.gitignore index db82c1e0..c137a61a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ Cargo.lock odra-casper/**/target/ examples/.builder_casper examples/wasm +examples/ourcoin/wasm examples/.env examples/.env.testnet examples/.env.integration diff --git a/Cargo.toml b/Cargo.toml index 0cdea337..78d0d95a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ members = [ "odra-test", "odra-build", ] -exclude = ["odra-casper/proxy-caller", "templates/blank", "templates/full", "templates/workspace"] +exclude = ["odra-casper/proxy-caller", "templates/blank", "templates/full", "templates/workspace", "tests"] resolver = "2" [workspace.package] @@ -57,6 +57,8 @@ base64 = "0.22" serde-json-wasm = "1.0" anyhow = "1.0.86" convert_case = "0.6.0" +ink_allocator = "4.2.1" +lazy_static = "1.5.0" [patch.crates-io] casper-types = { version = "5.0.0", git = "https://github.com/casper-network/casper-node", branch = "rustSDK-feat-2.0" } diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index e492b1f7..190737ae 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" odra = { path = "../odra" } odra-test = { path = "../odra-test", optional = true } odra-modules = { path = "../modules" } -base64 = { workspace = true, features = ["alloc"] } +base64 = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] serde_json = { workspace = true } diff --git a/core/src/address.rs b/core/src/address.rs index ef099f3c..8828d2d5 100644 --- a/core/src/address.rs +++ b/core/src/address.rs @@ -113,9 +113,7 @@ impl Address { pub fn to_formatted_string(&self) -> String { match self { Address::Account(_) => self.to_entity_addr().to_formatted_string(), - Address::Contract(package_hash) => { - PackageHash::new(package_hash.value()).to_formatted_string() - } + Address::Contract(package_hash) => package_hash.to_formatted_string() } } } @@ -300,7 +298,7 @@ const fn hex_char_to_value(c: u8) -> Result { mod tests { use super::*; use casper_types::system::Caller; - use casper_types::EraId; + use casper_types::{AddressableEntityHash, EraId}; // TODO: casper-types > 1.5.0 will have prefix fixed. const PACKAGE_HASH: &str = diff --git a/core/src/callstack.rs b/core/src/callstack.rs index a6425d97..c079fe2e 100644 --- a/core/src/callstack.rs +++ b/core/src/callstack.rs @@ -114,7 +114,7 @@ impl Callstack { #[cfg(test)] mod tests { - use casper_types::{account::AccountHash, PackageHash, RuntimeArgs}; + use casper_types::{account::AccountHash, RuntimeArgs}; use super::*; @@ -210,14 +210,14 @@ mod tests { fn mock_contract_element() -> CallstackElement { CallstackElement::new_contract_call( - Address::Contract(PackageHash::from_formatted_str(PACKAGE_HASH).unwrap()), + Address::new(PACKAGE_HASH).unwrap(), CallDef::new("a", false, RuntimeArgs::default()) ) } fn mock_contract_element_with_value(amount: U512) -> CallstackElement { CallstackElement::new_contract_call( - Address::Contract(PackageHash::from_formatted_str(PACKAGE_HASH).unwrap()), + Address::new(PACKAGE_HASH).unwrap(), CallDef::new("a", false, RuntimeArgs::default()).with_amount(amount) ) } diff --git a/core/src/error.rs b/core/src/error.rs index f35ef2c7..412649e0 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -252,7 +252,9 @@ pub enum EventError { /// Could not extract event data. CouldntExtractEventData, /// Contract doesn't support CES events. - ContractDoesntSupportEvents + ContractDoesntSupportEvents, + /// Tried to query event for a non-contract entity. + TriedToQueryEventForNonContract } /// Represents the result of a contract call. diff --git a/core/src/host.rs b/core/src/host.rs index cabc3be4..9329cd1f 100644 --- a/core/src/host.rs +++ b/core/src/host.rs @@ -165,7 +165,7 @@ pub trait HostContext { fn get_event(&self, contract_address: &Address, index: u32) -> Result; /// Returns the number of emitted events for the specified contract address. - fn get_events_count(&self, contract_address: &Address) -> u32; + fn get_events_count(&self, contract_address: &Address) -> Result; /// Calls a contract at the specified address with the given call definition. fn call_contract( @@ -296,12 +296,11 @@ impl HostEnv { contract_name: String, entry_points_caller: EntryPointsCaller ) { + let events_count = self.events_count(&address); let backend = self.backend.borrow(); backend.register_contract(address, contract_name, entry_points_caller); self.deployed_contracts.borrow_mut().push(address); - self.events_count - .borrow_mut() - .insert(address, backend.get_events_count(&address)); + self.events_count.borrow_mut().insert(address, events_count); } /// Calls a contract at the specified address with the given call definition. @@ -340,7 +339,9 @@ impl HostEnv { .for_each(|contract_address| { let events_count = binding.get_mut(contract_address).unwrap(); let old_events_last_id = *events_count; - let new_events_count = backend.get_events_count(contract_address); + let new_events_count = backend + .get_events_count(contract_address) + .unwrap_or_default(); let mut events = vec![]; for event_id in old_events_last_id..new_events_count { let event = backend.get_event(contract_address, event_id).unwrap(); @@ -416,9 +417,9 @@ impl HostEnv { /// Returns the names of all events emitted by the specified contract. pub fn event_names(&self, contract_address: &T) -> Vec { - let backend = self.backend.borrow(); - let events_count = backend.get_events_count(contract_address.address()); + let events_count = self.events_count(contract_address); + let backend = self.backend.borrow(); (0..events_count) .map(|event_id| { backend @@ -433,7 +434,9 @@ impl HostEnv { pub fn events(&self, contract_address: &T) -> Vec { let backend = self.backend.borrow(); let contract_address = contract_address.address(); - let events_count = backend.get_events_count(contract_address); + let events_count = backend + .get_events_count(contract_address) + .unwrap_or_default(); (0..events_count) .map(|event_id| { backend @@ -449,9 +452,11 @@ impl HostEnv { } /// Returns the number of events emitted by the specified contract. - pub fn events_count(&self, contract_address: &T) -> u32 { + pub fn events_count(&self, address: &T) -> u32 { let backend = self.backend.borrow(); - backend.get_events_count(contract_address.address()) + backend + .get_events_count(address.address()) + .unwrap_or_default() } /// Returns true if the specified event was emitted by the specified contract. @@ -462,6 +467,7 @@ impl HostEnv { ) -> bool { let contract_address = contract_address.address(); let events_count = self.events_count(contract_address); + let event_bytes = Bytes::from( event .to_bytes() @@ -487,6 +493,7 @@ impl HostEnv { event_name: T ) -> bool { let events_count = self.events_count(contract_address); + (0..events_count) .map(|event_id| { self.get_event_bytes(contract_address, event_id) @@ -662,7 +669,7 @@ mod test { let mut ctx = MockHostContext::new(); ctx.expect_register_contract().returning(|_, _, _| ()); - ctx.expect_get_events_count().returning(|_| 0); + ctx.expect_get_events_count().returning(|_| Ok(0)); // check if TestRef::new() is called exactly once let instance_ctx = MockTestRef::new_context(); @@ -752,7 +759,7 @@ mod test { let mut ctx = MockHostContext::new(); // there are 2 events emitted by the contract - ctx.expect_get_events_count().returning(|_| 2); + ctx.expect_get_events_count().returning(|_| Ok(2)); // get_event() at index 0 will return an invalid event ctx.expect_get_event() .with(predicate::always(), predicate::eq(0)) @@ -790,7 +797,7 @@ mod test { let mut ctx = MockHostContext::new(); // there are 2 events emitted by the contract - ctx.expect_get_events_count().returning(|_| 2); + ctx.expect_get_events_count().returning(|_| Ok(2)); // get_event() at index 0 will return an invalid event ctx.expect_get_event() .with(predicate::always(), predicate::eq(0)) @@ -817,7 +824,7 @@ mod test { let mut ctx = MockHostContext::new(); // there are 2 events emitted by the contract - ctx.expect_get_events_count().returning(|_| 2); + ctx.expect_get_events_count().returning(|_| Ok(2)); // get_event() at index 0 panics ctx.expect_get_event() .with(predicate::always(), predicate::eq(0)) @@ -833,7 +840,7 @@ mod test { let addr = Address::Account(AccountHash::new([0; 32])); let mut ctx = MockHostContext::new(); - ctx.expect_get_events_count().returning(|_| 1); + ctx.expect_get_events_count().returning(|_| Ok(1)); ctx.expect_get_event() .returning(|_, _| Ok(TestEv {}.to_bytes().unwrap().into())); diff --git a/core/src/host_env.rs b/core/src/host_env.rs deleted file mode 100644 index 7ab859db..00000000 --- a/core/src/host_env.rs +++ /dev/null @@ -1,313 +0,0 @@ -use crate::call_result::CallResult; -use crate::call_result::ContractCallResult; -use crate::casper_types::bytesrepr::{Bytes, FromBytes, ToBytes}; -use crate::casper_types::{CLTyped, RuntimeArgs, U512}; -use crate::consts::{ALLOW_KEY_OVERRIDE_ARG, IS_UPGRADABLE_ARG, PACKAGE_HASH_KEY_NAME_ARG}; -use crate::entry_point_callback::EntryPointsCaller; -use crate::error::EventError; -use crate::host_context::HostContext; -use crate::utils::extract_event_name; -use crate::{prelude::*, OdraResult}; -use crate::{Address, OdraError, VmError}; -use crate::{CallDef, ContractEnv}; -use casper_event_standard::EventInstance; -use casper_types::PublicKey; - -/// Represents the host environment for executing smart contracts. -/// -/// It provides methods for interacting with the underlying host context and managing -/// the execution of contracts. -#[derive(Clone)] -pub struct HostEnv { - backend: Rc>, - last_call_result: Rc>>, - deployed_contracts: Rc>>, - events_count: Rc>> // contract_address -> events_count -} - -impl HostEnv { - /// Creates a new `HostEnv` instance with the specified backend. - pub fn new(backend: Rc>) -> HostEnv { - HostEnv { - backend, - last_call_result: RefCell::new(None).into(), - deployed_contracts: RefCell::new(vec![]).into(), - events_count: Rc::new(RefCell::new(Default::default())) - } - } - - /// Returns the account address at the specified index. - pub fn get_account(&self, index: usize) -> Address { - let backend = self.backend.borrow(); - backend.get_account(index) - } - - /// Sets the caller address for the current contract execution. - pub fn set_caller(&self, address: Address) { - let backend = self.backend.borrow(); - backend.set_caller(address) - } - - /// Advances the block time by the specified time difference. - pub fn advance_block_time(&self, time_diff: u64) { - let backend = self.backend.borrow(); - backend.advance_block_time(time_diff) - } - - /// Returns the current block time. - pub fn block_time(&self) -> u64 { - let backend = self.backend.borrow(); - backend.block_time() - } - - /// Registers a new contract with the specified name, initialization arguments, and entry points caller. - pub fn new_contract( - &self, - name: &str, - init_args: Option, - entry_points_caller: EntryPointsCaller - ) -> Address { - let backend = self.backend.borrow(); - - let mut args = match init_args { - None => RuntimeArgs::new(), - Some(args) => args - }; - args.insert(IS_UPGRADABLE_ARG, false).unwrap(); - args.insert(ALLOW_KEY_OVERRIDE_ARG, true).unwrap(); - args.insert(PACKAGE_HASH_KEY_NAME_ARG, format!("{}_package_hash", name)) - .unwrap(); - - let deployed_contract = backend.new_contract(name, args, entry_points_caller); - - self.deployed_contracts.borrow_mut().push(deployed_contract); - self.events_count.borrow_mut().insert(deployed_contract, 0); - deployed_contract - } - - /// Registers an existing contract with the specified address and entry points caller. - /// Similar to `new_contract`, but skips the deployment phase. - pub fn register_contract(&self, address: Address, entry_points_caller: EntryPointsCaller) { - let backend = self.backend.borrow(); - backend.register_contract(address, entry_points_caller); - self.deployed_contracts.borrow_mut().push(address); - self.events_count - .borrow_mut() - .insert(address, self.events_count(&address)); - } - - /// Calls a contract at the specified address with the given call definition. - pub fn call_contract( - &self, - address: Address, - call_def: CallDef - ) -> OdraResult { - let backend = self.backend.borrow(); - - let use_proxy = T::cl_type() != <()>::cl_type() || !call_def.amount().is_zero(); - let call_result = backend.call_contract(&address, call_def, use_proxy); - - 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; - 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).unwrap(); - events.push(event); - } - - events_map.insert(*contract_address, events); - - *events_count = new_events_count; - }); - - self.last_call_result.replace(Some(CallResult::new( - address, - backend.caller(), - backend.last_call_gas_cost(), - call_result.clone(), - events_map - ))); - - call_result.map(|bytes| { - T::from_bytes(&bytes) - .map(|(obj, _)| obj) - .map_err(|_| OdraError::VmError(VmError::Deserialization)) - })? - } - - /// Returns the gas cost of the last contract call. - pub fn contract_env(&self) -> ContractEnv { - self.backend.borrow().contract_env() - } - - /// Prints the gas report for the current contract execution. - pub fn print_gas_report(&self) { - let backend = self.backend.borrow(); - backend.print_gas_report() - } - - /// Returns the CSPR balance of the specified address. - pub fn balance_of(&self, address: &Address) -> U512 { - let backend = self.backend.borrow(); - backend.balance_of(address) - } - - /// Retrieves an event with the specified index from the specified contract. - /// - /// # Returns - /// - /// Returns the event as an instance of the specified type, or an error if the event - /// couldn't be retrieved or parsed. - pub fn get_event( - &self, - contract_address: &Address, - index: i32 - ) -> Result { - let backend = self.backend.borrow(); - let events_count = self.events_count(contract_address); - let event_absolute_position = crate::utils::event_absolute_position(events_count, index) - .ok_or(EventError::IndexOutOfBounds)?; - - let bytes = backend.get_event(contract_address, event_absolute_position)?; - T::from_bytes(&bytes) - .map_err(|_| EventError::Parsing) - .map(|r| r.0) - } - - /// Retrieves a raw event (serialized) with the specified index from the specified contract. - pub fn get_event_bytes( - &self, - contract_address: &Address, - index: u32 - ) -> Result { - let backend = self.backend.borrow(); - backend.get_event(contract_address, index) - } - - /// Returns the names of all events emitted by the specified contract. - pub fn event_names(&self, contract_address: &Address) -> Vec { - let backend = self.backend.borrow(); - let events_count = backend.get_events_count(contract_address); - - (0..events_count) - .map(|event_id| { - backend - .get_event(contract_address, event_id) - .and_then(|bytes| extract_event_name(&bytes)) - .unwrap_or_else(|e| panic!("Couldn't extract event name: {:?}", e)) - }) - .collect() - } - - /// Returns all events emitted by the specified contract. - pub fn events(&self, contract_address: &Address) -> Vec { - let backend = self.backend.borrow(); - let events_count = backend.get_events_count(contract_address); - (0..events_count) - .map(|event_id| { - backend - .get_event(contract_address, event_id) - .unwrap_or_else(|e| { - panic!( - "Couldn't get event at address {:?} with id {}: {:?}", - &contract_address, event_id, e - ) - }) - }) - .collect() - } - - /// Returns the number of events emitted by the specified contract. - pub fn events_count(&self, contract_address: &Address) -> u32 { - let backend = self.backend.borrow(); - backend.get_events_count(contract_address) - } - - /// Returns true if the specified event was emitted by the specified contract. - pub fn emitted_event( - &self, - contract_address: &Address, - event: &T - ) -> bool { - let events_count = self.events_count(contract_address); - let event_bytes = Bytes::from( - event - .to_bytes() - .unwrap_or_else(|_| panic!("Couldn't serialize event")) - ); - (0..events_count) - .map(|event_id| { - self.get_event_bytes(contract_address, event_id) - .unwrap_or_else(|e| { - panic!( - "Couldn't get event at address {:?} with id {}: {:?}", - &contract_address, event_id, e - ) - }) - }) - .any(|bytes| bytes == event_bytes) - } - - /// Returns true if an event with the specified name was emitted by the specified contract. - pub fn emitted>(&self, contract_address: &Address, event_name: T) -> bool { - let events_count = self.events_count(contract_address); - (0..events_count) - .map(|event_id| { - self.get_event_bytes(contract_address, event_id) - .unwrap_or_else(|e| { - panic!( - "Couldn't get event at address {:?} with id {}: {:?}", - &contract_address, event_id, e - ) - }) - }) - .any(|bytes| { - extract_event_name(&bytes) - .unwrap_or_else(|e| panic!("Couldn't extract event name: {:?}", e)) - .as_str() - == event_name.as_ref() - }) - } - - /// Returns the last call result for the specified contract. - pub fn last_call_result(&self, contract_address: Address) -> ContractCallResult { - self.last_call_result - .borrow() - .clone() - .unwrap() - .contract_last_call(contract_address) - } - - /// Signs the specified message with the private key of the specified address. - pub fn sign_message(&self, message: &Bytes, address: &Address) -> Bytes { - let backend = self.backend.borrow(); - backend.sign_message(message, address) - } - - /// Returns the public key associated with the specified address. - pub fn public_key(&self, address: &Address) -> PublicKey { - let backend = self.backend.borrow(); - backend.public_key(address) - } - - /// Returns the caller address for the current contract execution. - pub fn caller(&self) -> Address { - let backend = self.backend.borrow(); - backend.caller() - } - - /// Sets the gas limit for the current contract execution. - pub fn set_gas(&self, gas: u64) { - let backend = self.backend.borrow(); - backend.set_gas(gas) - } -} diff --git a/examples/bin/livenet_tests.rs b/examples/bin/livenet_tests.rs index 6b601a29..945fa321 100644 --- a/examples/bin/livenet_tests.rs +++ b/examples/bin/livenet_tests.rs @@ -1,10 +1,12 @@ //! This example demonstrates how to deploy and interact with a contract on the Livenet environment. use odra::casper_types::{U256, U512}; use odra::host::{Deployer, HostEnv, HostRef, HostRefLoader}; -use odra::{Address, ExecutionError}; -use odra_examples::features::livenet::{LivenetContractHostRef, LivenetContractInitArgs}; +use odra::Address; +use odra_examples::features::livenet::{ + LivenetContract, LivenetContractHostRef, LivenetContractInitArgs +}; use odra_modules::access::events::OwnershipTransferred; -use odra_modules::erc20::{Erc20HostRef, Erc20InitArgs}; +use odra_modules::erc20::{Erc20, Erc20HostRef, Erc20InitArgs}; fn main() { let env = odra_casper_livenet_env::env(); @@ -14,14 +16,14 @@ fn main() { println!("Block time: {}", env.block_time()); // Funds can be transferred - // let another_account = env.get_account(1); - // let another_account_balance = env.balance_of(&another_account); - // env.transfer(another_account.clone(), U512::from(10_000_000_000u64)) - // .unwrap(); - // assert_eq!( - // env.balance_of(&another_account), - // another_account_balance + U512::from(10_000_000_000u64) - // ); + let another_account = env.get_account(1); + let another_account_balance = env.balance_of(&another_account); + env.transfer(another_account.clone(), U512::from(10_000_000_000u64)) + .unwrap(); + assert_eq!( + env.balance_of(&another_account), + another_account_balance + U512::from(10_000_000_000u64) + ); // Contract can be deployed env.set_gas(30_000_000_000u64); @@ -36,7 +38,7 @@ fn main() { // let result = contract.try_push_on_stack(1).unwrap_err(); // assert_eq!(result, ExecutionError::OutOfGas.into()); contract.push_on_stack(1); - let r = contract.try_function_that_reverts(); + let _ = contract.try_function_that_reverts(); // Set gas will be used for all subsequent calls env.set_gas(1_000_000_000u64); @@ -82,7 +84,7 @@ fn deploy_new(env: &HostEnv) -> (LivenetContractHostRef, Erc20HostRef) { let init_args = LivenetContractInitArgs { erc20_address: *erc20_contract.address() }; - let livenet_contract = LivenetContractHostRef::deploy(env, init_args); + let livenet_contract = LivenetContract::deploy(env, init_args); erc20_contract.transfer(livenet_contract.address(), &1000.into()); (livenet_contract, erc20_contract) } @@ -93,8 +95,8 @@ fn load( erc20_address: Address ) -> (LivenetContractHostRef, Erc20HostRef) { ( - LivenetContractHostRef::load(env, contract_address), - Erc20HostRef::load(env, erc20_address) + LivenetContract::load(env, contract_address), + Erc20::load(env, erc20_address) ) } @@ -113,5 +115,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) } diff --git a/examples/ourcoin/Cargo.toml b/examples/ourcoin/Cargo.toml index 6ccd04fe..65c692a8 100644 --- a/examples/ourcoin/Cargo.toml +++ b/examples/ourcoin/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" odra = { path = "../../odra", features = [], default-features = false } odra-modules = { path = "../../modules", features = [], default-features = false } odra-casper-livenet-env = { path = "../../odra-casper/livenet-env", optional = true } +odra-build = { path = "../../odra-build", features = [], default-features = false } [dev-dependencies] odra-test = { path = "../../odra-test", features = [], default-features = false } diff --git a/examples/ourcoin/bin/build_schema.rs b/examples/ourcoin/bin/build_schema.rs index 8605a467..99c62b52 100644 --- a/examples/ourcoin/bin/build_schema.rs +++ b/examples/ourcoin/bin/build_schema.rs @@ -1,4 +1,6 @@ #![doc = "Binary for building schema definitions from odra contracts."] +#[allow(unused_imports, clippy::single_component_path_imports)] +use ourcoin; #[cfg(not(target_arch = "wasm32"))] extern "Rust" { @@ -8,60 +10,7 @@ extern "Rust" { #[cfg(not(target_arch = "wasm32"))] fn main() { - let module = std::env::var("ODRA_MODULE").expect("ODRA_MODULE environment variable is not set"); - let module = to_snake_case(&module); - - let contract_schema = unsafe { crate::casper_contract_schema() }; - let module_schema = unsafe { crate::module_schema() }; - - write_schema_file( - "resources/casper_contract_schemas", - &module, - contract_schema - .as_json() - .expect("Failed to convert schema to JSON") - ); - - write_schema_file( - "resources/legacy", - &module, - module_schema - .as_json() - .expect("Failed to convert schema to JSON") - ); -} - -fn write_schema_file(path: &str, module: &str, json: String) { - if !std::path::Path::new(path).exists() { - std::fs::create_dir_all(path).expect("Failed to create resources directory"); - } - let filename = format!("{}/{}_schema.json", path, module); - let mut schema_file = std::fs::File::create(filename).expect("Failed to create schema file"); - - std::io::Write::write_all(&mut schema_file, &json.into_bytes()) - .expect("Failed to write to schema file"); -} - -fn to_snake_case(s: &str) -> String { - let mut result = String::with_capacity(s.len()); - let mut chars = s.chars().peekable(); - let mut is_first = true; - - while let Some(c) = chars.next() { - if c.is_uppercase() { - if !is_first { - if let Some(next) = chars.peek() { - if next.is_lowercase() { - result.push('_'); - } - } - } - result.push(c.to_lowercase().next().unwrap()); - } else { - result.push(c); - } - is_first = false; - } - - result + odra_build::schema(unsafe { crate::module_schema() }, unsafe { + crate::casper_contract_schema() + }); } diff --git a/examples/ourcoin/wasm/OurToken.wasm b/examples/ourcoin/wasm/OurToken.wasm deleted file mode 100755 index e260182661f1a740afd5fd2695928badb1dabd7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 251253 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 Kle5EcivJfWP-#a1 diff --git a/examples/src/features/livenet.rs b/examples/src/features/livenet.rs index 0bfbb091..5e3a5919 100644 --- a/examples/src/features/livenet.rs +++ b/examples/src/features/livenet.rs @@ -76,15 +76,15 @@ pub enum Error { #[cfg(test)] mod tests { - use crate::features::livenet::{LivenetContractHostRef, LivenetContractInitArgs}; + use crate::features::livenet::{LivenetContract, LivenetContractInitArgs}; use alloc::string::ToString; use odra::host::{Deployer, HostRef}; - use odra_modules::erc20::{Erc20HostRef, Erc20InitArgs}; + use odra_modules::erc20::{Erc20, Erc20InitArgs}; #[test] fn livenet_contract_test() { let test_env = odra_test::env(); - let mut erc20 = Erc20HostRef::deploy( + let mut erc20 = Erc20::deploy( &test_env, Erc20InitArgs { name: "TestToken".to_string(), @@ -93,7 +93,7 @@ mod tests { initial_supply: Some(100_000.into()) } ); - let mut livenet_contract = LivenetContractHostRef::deploy( + let mut livenet_contract = LivenetContract::deploy( &test_env, LivenetContractInitArgs { erc20_address: *erc20.address() diff --git a/justfile b/justfile index dea4016c..6cf0e314 100644 --- a/justfile +++ b/justfile @@ -6,29 +6,18 @@ BINARYEN_CHECKSUM := "c55b74f3109cdae97490faf089b0286d3bba926bb6ea5ed00c8c784fc5 default: just --list -# TODO: reenable this cd odra-casper/proxy-caller && cargo clippy --target=wasm32-unknown-unknown -- -D warnings -A clippy::single-component-path-imports clippy: cargo clippy --all-targets -- -D warnings - cd examples && cargo clippy --all-targets -- -D warnings - cd modules && cargo clippy --all-targets -- -D warnings - cd benchmark && cargo clippy --all-targets -- -D warnings + cd odra-casper/proxy-caller && cargo clippy --target=wasm32-unknown-unknown -- -D warnings -# TODO: reenable this cd odra-casper/proxy-caller && cargo fmt lint: clippy cargo fmt - cd examples && cargo fmt - cd modules && cargo fmt - cd benchmark && cargo fmt + cd odra-casper/proxy-caller && cargo fmt check-lint: clippy cargo fmt -- --check + cargo check --all-targets cd odra-casper/proxy-caller && cargo fmt -- --check - cd modules && cargo fmt -- --check - cd modules && cargo check --all-targets - cd examples && cargo fmt -- --check - cd examples && cargo check --all-targets - cd benchmark && cargo fmt -- --check - cd benchmark && cargo check --all-targets --features=benchmark install-cargo-odra: rustup toolchain install stable diff --git a/modules/Cargo.toml b/modules/Cargo.toml index b23419d3..c86fb048 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -14,7 +14,7 @@ serde = { workspace = true, default-features = false } serde_json = { workspace = true, default-features = false } serde-json-wasm = { workspace = true } base16 = { workspace = true } -base64 = { workspace = true, features = ["alloc"] } +base64 = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] odra-build = { path = "../odra-build" } diff --git a/modules/src/cep18_token.rs b/modules/src/cep18_token.rs index e0b5ffd2..1868f400 100644 --- a/modules/src/cep18_token.rs +++ b/modules/src/cep18_token.rs @@ -364,13 +364,12 @@ pub(crate) mod tests { use alloc::vec; use crate::cep18::utils::Cep18Modality; + use crate::cep18_token::{Cep18, Cep18InitArgs}; use odra::casper_types::account::AccountHash; - use odra::casper_types::contracts::ContractPackageHash; + use odra::casper_types::PackageHash; use odra::host::{Deployer, HostEnv, HostRef}; use odra::Address; - use crate::cep18_token::{Cep18, Cep18InitArgs}; - use super::Cep18HostRef; pub const TOKEN_NAME: &str = "Plascoin"; @@ -408,7 +407,7 @@ pub(crate) mod tests { pub fn invert_address(address: Address) -> Address { match address { - Address::Account(hash) => Address::Contract(ContractPackageHash::new(hash.value())), + Address::Account(hash) => Address::Contract(PackageHash::new(hash.value())), Address::Contract(hash) => Address::Account(AccountHash(hash.value())) } } diff --git a/modules/src/cep78/tests/events.rs b/modules/src/cep78/tests/events.rs index 15c9477c..b39076c3 100644 --- a/modules/src/cep78/tests/events.rs +++ b/modules/src/cep78/tests/events.rs @@ -35,5 +35,5 @@ fn should_not_record_events_in_no_events_mode() { let expected_balance = 1u64; assert_eq!(actual_balance, expected_balance); - assert!(env.events_count(contract.address()) == 0); + assert_eq!(0, env.events_count(contract.address())); } diff --git a/odra-casper/livenet-env/src/livenet_host.rs b/odra-casper/livenet-env/src/livenet_host.rs index f94c6a4a..1c6f1442 100644 --- a/odra-casper/livenet-env/src/livenet_host.rs +++ b/odra-casper/livenet-env/src/livenet_host.rs @@ -4,6 +4,7 @@ use crate::error; use crate::livenet_contract_env::LivenetContractEnv; use odra_casper_rpc_client::casper_client::CasperClient; use odra_casper_rpc_client::log::info; +use odra_casper_rpc_client::utils::find_wasm_file_path; use odra_core::callstack::{Callstack, CallstackElement}; use odra_core::casper_types::Timestamp; use odra_core::entry_point_callback::EntryPointsCaller; @@ -16,7 +17,6 @@ use odra_core::{ use odra_core::{prelude::*, EventError, OdraResult}; use odra_core::{ContractContainer, ContractRegister}; use std::fs; -use std::path::PathBuf; use std::sync::RwLock; use std::thread::sleep; use tokio::runtime::Runtime; @@ -107,11 +107,11 @@ impl HostContext for LivenetHost { .map_err(|_| EventError::CouldntExtractEventData) } - fn get_events_count(&self, contract_address: &Address) -> u32 { + fn get_events_count(&self, contract_address: &Address) -> Result { let rt = Runtime::new().unwrap(); let client = self.casper_client.borrow(); rt.block_on(async { client.events_count(contract_address).await }) - .unwrap_or_default() + .ok_or(EventError::CouldntExtractEventData) } fn call_contract( @@ -235,14 +235,13 @@ impl HostContext for LivenetHost { let rt = Runtime::new().unwrap(); let timestamp = Timestamp::now(); let client = self.casper_client.borrow_mut(); - Ok(rt - .block_on(async { client.transfer(to, amount, timestamp).await }) + rt.block_on(async { client.transfer(to, amount, timestamp).await }) .map_err(|e| { self.map_error_code_to_odra_error( ContractId::Address(client.caller()), &e.error_message() ) - })?) + }) } } @@ -258,25 +257,7 @@ impl LivenetHost { match found { None => OdraError::ExecutionError(UnexpectedError), - Some((message, error)) => {} - } - } -} - -fn find_wasm_file_path(wasm_file_name: &str) -> PathBuf { - let mut path = PathBuf::from("wasm") - .join(wasm_file_name) - .with_extension("wasm"); - let mut checked_paths = vec![]; - for _ in 0..2 { - if path.exists() && path.is_file() { - info(format!("Found wasm under {:?}.", path)); - return path; - } else { - checked_paths.push(path.clone()); - path = path.parent().unwrap().to_path_buf(); + Some((_, error)) => OdraError::ExecutionError(User(error.code())) } } - odra_casper_rpc_client::log::error(format!("Could not find wasm under {:?}.", checked_paths)); - panic!("Wasm not found"); } diff --git a/odra-casper/proxy-caller/src/lib.rs b/odra-casper/proxy-caller/src/lib.rs index 10f3e1d7..a974e3a3 100644 --- a/odra-casper/proxy-caller/src/lib.rs +++ b/odra-casper/proxy-caller/src/lib.rs @@ -2,7 +2,6 @@ #![doc = "It allows calling other contracts and saving the return values to the named key"] #![doc = "of the Proxy Caller."] #![no_std] -#![feature(core_intrinsics)] extern crate alloc; use core::mem::MaybeUninit; @@ -185,11 +184,3 @@ fn to_ptr(t: T) -> (*const u8, usize, Vec) { let size = bytes.len(); (ptr, size, bytes) } - -/// Panic handler for the WASM target architecture. -#[cfg(target_arch = "wasm32")] -#[panic_handler] -#[no_mangle] -pub fn panic(_info: &core::panic::PanicInfo) -> ! { - core::intrinsics::abort(); -} diff --git a/odra-casper/rpc-client/src/casper_client.rs b/odra-casper/rpc-client/src/casper_client.rs index d249ad1b..8d6e1399 100644 --- a/odra-casper/rpc-client/src/casper_client.rs +++ b/odra-casper/rpc-client/src/casper_client.rs @@ -8,7 +8,7 @@ use serde_json::{json, Value}; use crate::casper_client::configuration::CasperClientConfiguration; use crate::error::Error; -use crate::error::Error::{ExecutionError, LivenetToDoError}; +use crate::error::Error::{Execution, LivenetToDo}; use crate::log; use casper_client::cli::{ get_balance, get_deploy, get_dictionary_item, get_entity, get_node_status, get_state_root_hash, @@ -173,7 +173,7 @@ impl CasperClient { let public_key = &PublicKey::from(secret_key); let signature = sign(message, secret_key, public_key) .to_bytes() - .map_err(|_| LivenetToDoError)?; + .map_err(|_| LivenetToDo)?; Ok(Bytes::from(signature)) } @@ -264,10 +264,10 @@ impl CasperClient { self.configuration.verbosity() ) .await - .map_err(|_| LivenetToDoError)? + .map_err(|_| LivenetToDo)? .result .last_added_block_info - .ok_or(LivenetToDoError)? + .ok_or(LivenetToDo)? .timestamp .millis(); Ok(block_time) @@ -342,13 +342,13 @@ impl CasperClient { ) .await; - r.map_err(|_| LivenetToDoError)? + r.map_err(|_| LivenetToDo)? .result .stored_value .into_cl_value() - .ok_or(LivenetToDoError)? + .ok_or(LivenetToDo)? .into_t() - .map_err(|_| LivenetToDoError) + .map_err(|_| LivenetToDo) } /// Query the node for the transaction state. @@ -548,12 +548,11 @@ impl CasperClient { let deploy_hash = response.deploy_hash; let result = self.wait_for_deploy(deploy_hash).await?; - let p = self.process_execution(result, deploy_hash).map(|_| { + self.process_execution(result, deploy_hash).map(|_| { ().to_bytes() .expect("Couldn't serialize (). This shouldn't happen.") .into() - }); - p + }) } async fn query_global_state(&self, key: &str, path: Option) -> StoredValue { @@ -598,9 +597,9 @@ impl CasperClient { let result = self.get_deploy(deploy_hash).await.execution_info; if result.is_some() { final_result = result - .ok_or(LivenetToDoError)? + .ok_or(LivenetToDo)? .execution_result - .ok_or(LivenetToDoError)?; + .ok_or(LivenetToDo)?; break; } } @@ -616,7 +615,7 @@ impl CasperClient { "Deploy V1 {:?} failed with error: {:?}.", deploy_hash_str, error_message )); - Err(ExecutionError { error_message }) + Err(Execution { error_message }) } Success { .. } => { log::info(format!( @@ -639,7 +638,7 @@ impl CasperClient { "Deploy V1 {:?} failed with error: {:?}.", deploy_hash_str, error_message )); - Err(ExecutionError { error_message }) + Err(Execution { error_message }) } } } @@ -682,8 +681,8 @@ impl CasperClient { .json(&request) .send() .await - .map_err(|_| LivenetToDoError)?; - let json: JsonRpc = response.json().await.map_err(|_| LivenetToDoError)?; + .map_err(|_| LivenetToDo)?; + let json: JsonRpc = response.json().await.map_err(|_| LivenetToDo)?; Ok(json) } diff --git a/odra-casper/rpc-client/src/casper_client/configuration.rs b/odra-casper/rpc-client/src/casper_client/configuration.rs index e521fbc5..24c23678 100644 --- a/odra-casper/rpc-client/src/casper_client/configuration.rs +++ b/odra-casper/rpc-client/src/casper_client/configuration.rs @@ -3,6 +3,7 @@ use crate::casper_client::{ ENV_ACCOUNT_PREFIX, ENV_CHAIN_NAME, ENV_CSPR_CLOUD_AUTH_TOKEN, ENV_EVENTS_ADDRESS, ENV_LIVENET_ENV_FILE, ENV_NODE_ADDRESS, ENV_SECRET_KEY }; +use crate::utils::{get_env_variable, get_optional_env_variable}; use casper_client::Verbosity; use odra_core::casper_types::SecretKey; #[cfg(feature = "std")] @@ -33,9 +34,9 @@ impl CasperClientConfiguration { // Load .env dotenv::dotenv().ok(); - let node_address = Self::get_env_variable(ENV_NODE_ADDRESS); - let chain_name = Self::get_env_variable(ENV_CHAIN_NAME); - let events_url = Self::get_env_variable(ENV_EVENTS_ADDRESS); + let node_address = get_env_variable(ENV_NODE_ADDRESS); + let chain_name = get_env_variable(ENV_CHAIN_NAME); + let events_url = get_env_variable(ENV_EVENTS_ADDRESS); let (secret_keys, secret_key_paths) = Self::secret_keys_from_env(); CasperClientConfiguration { @@ -44,7 +45,7 @@ impl CasperClientConfiguration { chain_name, secret_keys, secret_key_paths, - cspr_cloud_auth_token: Self::get_optional_env_variable(ENV_CSPR_CLOUD_AUTH_TOKEN), + cspr_cloud_auth_token: get_optional_env_variable(ENV_CSPR_CLOUD_AUTH_TOKEN), events_url } } @@ -55,11 +56,11 @@ impl CasperClientConfiguration { fn secret_keys_from_env() -> (Vec, Vec) { let mut secret_keys = vec![]; let mut secret_key_paths = vec![]; - let file_name = Self::get_env_variable(ENV_SECRET_KEY); + let file_name = get_env_variable(ENV_SECRET_KEY); secret_keys.push(SecretKey::from_file(file_name.clone()).unwrap_or_else(|_| { panic!( "Couldn't load secret key from file {:?}", - Self::get_env_variable(ENV_SECRET_KEY) + get_env_variable(ENV_SECRET_KEY) ) })); secret_key_paths.push(file_name); @@ -75,20 +76,6 @@ impl CasperClientConfiguration { (secret_keys, secret_key_paths) } - fn get_env_variable(name: &str) -> String { - std::env::var(name).unwrap_or_else(|err| { - crate::log::error(format!( - "{} must be set. Have you setup your .env file?", - name - )); - panic!("{}", err) - }) - } - - fn get_optional_env_variable(name: &str) -> Option { - std::env::var(name).ok() - } - pub fn verbosity(&self) -> u64 { Verbosity::Low as u64 } diff --git a/odra-casper/rpc-client/src/error.rs b/odra-casper/rpc-client/src/error.rs index 556c090a..9dcd491f 100644 --- a/odra-casper/rpc-client/src/error.rs +++ b/odra-casper/rpc-client/src/error.rs @@ -1,28 +1,21 @@ -use odra_core::{ExecutionError, OdraError, VmError}; use thiserror::Error; #[derive(Debug, Error)] pub enum Error { #[error("Livenet generic error")] - LivenetToDoError, + LivenetToDo, #[error("Livenet communication error")] - RpcCommunicationError, + RpcCommunicationFailure, #[error("Livenet execution error")] - ExecutionError { error_message: String } + Execution { error_message: String } } -// impl Into for Error { -// fn into(self) -> OdraError { -// OdraError::VmError(Other(self.to_string())) -// } -// } - impl Error { pub fn error_message(&self) -> String { match self { - Error::LivenetToDoError => "Livenet generic error".to_string(), - Error::RpcCommunicationError => "Livenet communication error".to_string(), - Error::ExecutionError { error_message } => error_message.to_string() + Error::LivenetToDo => "Livenet generic error".to_string(), + Error::RpcCommunicationFailure => "Livenet communication error".to_string(), + Error::Execution { error_message } => error_message.to_string() } } } diff --git a/odra-casper/test-vm/resources/proxy_caller.wasm b/odra-casper/test-vm/resources/proxy_caller.wasm index c4e18a14ae8c80ce7d9bb173ffaa4e5d631f1037..d89c711e83bacddf1fb23075f2f96259763f7b81 100755 GIT binary patch delta 10670 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 C%p42= diff --git a/odra-casper/test-vm/resources/proxy_caller_with_return.wasm b/odra-casper/test-vm/resources/proxy_caller_with_return.wasm index a4d7a046c2dc9ebd389012eabadeadf9d6aee84b..c161783faf954d8ea3b5bf9ec8e2b2354bdbfe4d 100755 GIT binary patch delta 11018 zcmcIqdvsORncsV#b04|q-sA+3+~i)8b8aFekPrkyAS7@P0eOT+cq8(DOyPnk4-r(j z;f(=Ax~*gDV=dHKORLmb&FH8_jMH|s<8;Ppb!^94v9;FDXsc_fRf}bQ-#+I)k|6rW zOtS9T=j`+C@B8+*zvtd(|Lfw=N1}wS-hLftjPbq2t3*1@R&o5a^r@0T!k0cZyzEr_ zdM-{qS;vE?-j=*T?&90ihxh@`eZCN*tDo}#*F}&qp>xi3uAzqkr4Fom0KW+5LI}TGs?G(hF)eQ&WGskKe%i_48?* z75huIGr<;>wm@jgB*G2}|SqI}edt43DV*A1@#cguJ5pYgbS+BeROXMp`4u<(C_x5$+Ln|zu4!e84L+qeK>+Z`Zmp9Dr# z#SP0x)Cw#gQ8R#$Rf`P?L7m(Z)Y&yFIFK7-iVSxGCX*|7%#+;(L2&Vtg7`q-V3wY$ z7A(Qc{+NsD=wSRW1?8A$S!mLLUv?(T!osjLfLJ=AH3!KXLIVwYVPK}*jLB1>>-Zjd zG1P%;hta~v%X7xi_@smeXwBS^(4C8XtJ8Qj_(GQw&2y1zev_;Vp9dp_kramPj(m+F z+vU5FLzt_DNpf)IVcSz^0mJ=;gdyk=)pA79rHH;=Sj}&dk)lKOSq$DQpn_&}bnKc6 zw2me9?6vP;U;-BM|OOV5`~0r*=JN-o$yltEyiKA9=L`;!|l! zoOgN3c?J6CM9gN9#Cq{+@h(pYC+fbxTT7V}0&K7?@M6>txCIKuk_3jYIg?mQEJG?3AZV`}3H5qO{dkfSdpm0Z7aW zIIM{=D2(o@tSl`X%&(EjvIZd8S!UyUcUcXt|5VmcvmwFVIZln};MP|y^aNMhWll<9 z=@lN+XKs*ItbUx*z#zR2SwWJw0dhn;ZuuQcwLZs8Rk0#)?pWp?MlCn@%A>J+O0xn9 zCRj!u=NZYPIerTaxeXa!J|sBpa zZAacWju!U$aqhyx!qvi}g&5t1ZE31#ot#^&ELGuAg#+tLl%qF8QP3nEd1_4S*Y(dc z(*aD#9LkI{r2X!S^D6X#FR~cGuND?8v69BQ{cyVuwFq|-{ z1Ux6y$>Nr$E|2Z+R^U+@6HlcoTjtw$LlV`QGF{HN^Yzu>&J9nAUhLn zFy)-bI5Q1?gfjwe$(ZZfg0(5(NhWHOs+cKQiH7Tp%UmYw@D;Zvk{nuQ=K9nv>Xqr3v6gP zHjsZP!qqM&Y zlwKX?&|y+yXqM+?l_}+%Gt`DW7A`^aD7P7z6-iv_9ux#2F{^;8A6(_>pzyRVNFo|Q z7n)3vreG`ewo+-bbz_c-oZfl?OeY1$lGqxSE!bn(Tmx0}gSNszsl#0?#BbdR(jxT- z(q1o(F#41NQ5-DBElmsSPcy{ijE^aNyTb5QL3=0$f7F>w_?7R*ok${IVACQDJVCcE zbS5JN249X_7yeV+klUpe!{1fI(=*|*7{<~YbkXo?@t*4}(mvt}(kW(0e=NO;4ML+e z!PEH24MoxTl&YHEC?q##itaz?OuFMdQcgN$`zf>VD>B59V$ISh!vYnv0YdkYx+{`V zkTmL;5mJIN8_Sqc28FNyOBmk<^KfOBhyjin8h*#*@rvPYl1H&UCVx~>9apJb0X-&A zFClX($jhLD)-=9S&b8*oCzFlEZG^vSVc9<9E0!;11*{;VhkVu=h1BjtD~@ZSU0y{v zUC-~>3bU#mgz1O1jzEU!*ye88Zg&H`I582|xrqT~O-iK*9vPlyIwm^Y@qwWif>^1EOJcl z2x4A1FJR@7LHb-e5E*8pIgym}bEe{9215R>^2(ucOOI$_Bx!zP%F^oCI(Hnz^#UV# zfxcUtjjl@Y)pBvwP?THyt45$A5J^iVw^%f9ZcqTqRtZngqo28zZ&j5v=Y~ zA2AFc4H>Jt-xK4X3}lGP}_7 z+kvcDJXEr~`oUpTJ~?-AS2_WA@Y8}9&~hV{gk7uB02#+Fd3()p%;MRa!F-B*yQZ3t zm64&t(i?0)cnnh`~!Y}6SG;~;TS0i=_~|GMFmubm^xg^jky(zHvnL^X-* zH4xn9HKy&O$P6fyXYY?6&zH!W=C*iG5C8;F6OiAf1vuFpgS9`} zya-p(vTl&;GZ3ZTp-}Y>n;wJruil&pQ1q1tT1NBj@~M_2zfOMEQjak;6DC)ZxtX&d zYypo1U8P!{Ql1t8cb-?aAjdo7T&1*?sP;M&c)NVo zsQ~pioOU1^KB=URJULQQs-zJmO|Y&u#t?Xk*{mrzmcp@h(h7`w8P~Y1Z?%fuodMg6 zZ`di2t6B+vN9&5oBtNyhu>|66kkp+Li+RurBrBAnP*DL>^(^>Oure`u$mpJk0$D`8 z27>P(ZG2H`Hc#%E9P2eO$3fqL1w96)iK61x);z8dZ{Xb!M2UUm!F}0x;q}}s3cx6) zn<1tcgJF=utDHLoe6Bd$@<%&!u+hc4zK2};AP!v$FBks_LuBL3gDw0${#{^eXk_GP|Hn4fV8^3|)IO$)GzfCP^A5|Cmb1=Gz1 z)CA21DfA(I=+oy_|0WG357c`MjJ{_dJ%-H%a^F-5)4O8Y5L{cPRmB&=+9^o;r$)-c zB^2XO`>8t6oGJHAv-m=J|Fn`E*wvAN;nH4U|1qr!*gjF$*mSy2s_S}n?Nryt)%6v1 z{Y+iUXAoqox^7U{6YBc?8N}Ov&8WpS&{j@~U(_aCGf#s{&8=w3X%ij$*m5 ztrXp_x0M0plePx_Ojqs9`GPN!9kZ`0Q;LpSa~d2fnwhpwBwa<3FU_7!S^+1CF7@w`&` zX?t^D`jh7pvhU2T`*LJ&&h3M2alhO|5c6p&i{;Jpo+w{O85ZRh*h&xg&&(Amz6&yI z{%-(6I}092zbptGCSmECwV;TH^I_zMg)>6)!x7J%z%e-Gzb8+Zv=ByM=rohNaTcL74pJ0 z#R#^AONY5xu|+#cL0SoJ&NjBVyH_au-3o{{4iU@e$aPB>^B3jWr9)vqzh8O?=2o

9$CHv*9*%hs-7kC&J~#v;gy-YgW09h5P-QARN5{_AqLE}ZrKrY6<-gmxjbKGzARt9T%30^ zx7gY$u$f$2hjd}f+L)SsJKv`8ez^AXcxC&#AUL9p>MTcF)X2T-zGCDX>w=*;<&(t4 z9{Hzrg9}$s+C-}jT;{d1a{XLGl}%Jr8h_W7UVk>oZ4+*D z5}Ya!?APp(r?)la?<$<%HpJakAZ11xs&*B8dAkag+iMHR!j0pC`{e2q1Lc#B7f8vQ7SV$8vFk%VjrKkeF`0aYMlx7^i}k@838YPn!2Mbm!139)ky? zq!}e6GZosDxu$F5PM;7zIw)`0b%K9GuG%qo{Km`in0=YgLL$ffG)%r1ulfLKso9|ZNUB$&@o&{jBH z6qGO%3dEmG0j?O!n-)&H)wHN~^J?_;m%wBJ@V*C`kVl5j2lF>W^T>q0bha+1lnsMZ zH9qF$^{5h+s>KriDUmRuM*!*Yxm$;+n$O`jQhjA;sF#sVBgVmnLT7z@#-}&KoGEgs zt=0dByg8SL<8uLcC}w=l-Q790CoP}bQ>H#RP$3fKA^42TKkQkA951=IO^b&S5L){e zLQl+%9;?Pf^6|ZGjVc46xUBpUZA>8-ru;{zJqR*=93*H62XqvR!X0uGn~?L3NOv@n z$Ap*!@dNX5GZ@-Uh40XmJdAxo?(bcd%HL!2G$w-`ZTW@h@{S}5(fd27P(0T$A7=ID z{wv5QRPT%TEjbtLTMLM%_RX^}H%dX2nPLuz@)@OYY+5e4yrW9a-X90@P5Ub#3cu{` zD3K5CZ|XrJn5k-I>4715;~bb)hH1f2Gj(jHCBv0(G?&V=2MY0-z)J^AQR(KXB1sXW zj^T`ka>L_?U1x!k&hi1C-bzR4T!<#|o`NK;rd`hB5YR=x2dw7?ffeSkB2u=3B zBl8fWGCD6mJ3?&QM`sUl*;M`{<5zy*=fOT=zw2lP#Qx+_@B0H+I-eeOwDyE9pK}I5 z@Jnx-GsR2Du!gjAf`m+EQUPcJC1ko4z(G>qTq`hKZo7(k2 z>H+02r^^MMOG0!$Hh^-GRr1Ns_RLm{m&YNJV<6QT%Fyg%wC_FP*py{G7e>t-`iB!Y z_ZSnAN9iE(4_dTikx}~lHGK4>PW0R(^Ngfv0h}7OPH!HYl)p~Hj<1BAj~!nch`Ye@ zz2gb=6rE@d=SlvwTydgpcwcLRg=mzGcqT>Ga*`{{I>6mT5#j^WvXYg8S-0k37GlM>WXuu&O2heTYIPT*i(o38Px(PMPomx@{oWVNy zqyx7gT8$`*NmQZ1ljU{}aGS7eEXrUg*-!XLsYp@PCNJrl4L%%ghn@DL#lh)C{Gnv$ zDd5L@W-~2G{A1d9ayR3U@>o>!s{*51Q~_bJ#v*QBjjBw2GJqIctg{H1gD?~oG_(!4 z3)^IBKN8{qg#qs2DZWq273oMipGb%&GlmvEO82C|3i7#1{d@LnQl$Kc(lu|%{vZAA~hKX?HKk>x~ zXwccNh0OErbKK52 z?wd=^k^A02)3yHo&$TF>N~p72xJcYYUHvg$ma86EE>0ei&plvK^VS0^sBs>wre@QF zW5prf_0WS)>sJ{-U~zbdW8BEzAJ4n*#Qj?#PHwK78Lx%pv4?MsY78I9;HP^(A6h?t zvgOf4nz1{X$*#cTBWNRNH*Z|CZS}U@_1kx>tKYtDZT;%ao3CFxeB1UH@ji<8>o{Wt zX!Fqq@JxR}{D=&-^p|-i_z->ue#8pVG4a#sQixxX*HJ9rdpIU59$7YT6*t*6=xqkM yR9<5x;HuZCX)9MxDAu8^>B*wH>>vs~uO@RO+Jbta)_WHBPCHozCyu=iHkM0j7V< zgvH)xzrOu_-~JxIz3<-7wcS70a>d$Pwn`y{+%>LWPb3neU*f-ZBsWsvK{Fu}{a&v5 zLkRc#Nasc=j=WYQ{YTD{4A33&mc-_`^m;QiiP9%!rj#Dx4@pH@%R-;fJi;d={xspy zH0kyDGBy4b8Y*;q@h34NoF4FmFDWuGjA{7F_5@5_{dv6dCaGy&U5atOd_5uU!Eu8l z!bk{v&yZ6e9-`ygRTKPD*wT7S9ltFc;ImX_j0>S^7t^gg(>DiYlUBOW23C zxTRUT*{(HPo_QgGhR2p>gvvZ~wFt! zDV^{gm913iuO3V7dOuV9x}T|)1g4Zm49m;AWmsO`%>Y4Bu5F5G+$k-MJBN1$CdrKJ zvkg~(zs^`SC1MAo9y<`#3=6Er%QX>`<^)UHrtJY4bb3^$K&NmzsXVg?g2zi*CVp|C zxgdz8I@Vbp-p4GWk&;OU4;T<#nqm57$s)=$qH=($jX{(z7_()J{>7*aSHyIz(9N!x z$Nsat*`D48X3?eeb!D(zuBN`=8R)1f6vvXUhyH>keUy{6mpO;z5}dizp3Aa8;e#xu z5O6ouM>j>6+H_oYxm-t$*=ZD#Lmy@rNt=F~y%ls;nUmA#1jo{O%xsjk!=IS9OBosN z8J{cjYt$q;pO3@-CmFXthNamdvxM%QkSDL8lN0vI(&5;|=QTN*{(jPp(xTb9xw4Fw z=9bDev@>@`CM(r8z-M^)bnc6wOq=t=Q0V^rxs!NIYaab)TRe|G%Wrj-N!6??m?Yb& ztzfcjrmY1Hpzw4-6y>`G6)69`prN8GCLL+!hdi($Z@K1)No7N3MGWT&^YX+^uh~V5 z3+rYoI}7k|rWIfX8kXPYts<6BaS*|LGhQmP!M1Ig1A=R5ZlG5SAA#HSgmWSK4dJ_B z?7s^C9Fo5sN#y847|+AM6SjzITC)v9{^|hr6qU$9x~=HihQXm`oU4e(q@^#wA=^;t zG)zG++p%V7!uUHWbzTo4-1V}U*I!Y5?MzCwi zw*ux4l2udsBS9Feum|yX$22&Tu)K}JgJ{5{p;q0lhZ>DDn&waFyGn$8Mr@ABQelf4 zF?#lPR57BQUNe2Lw0cA%J8JgRv8oXq@zUx!brBHP8wK0p8R4AG1T!k2IJKNNGt0Td z4HNHFSIaK?U3H@eeIHS6O+IX>y{1}*>9(3EhECMf%DMFYnhLpteqWOt!=XC_AP|Jq z?NHqQNSHecu)}+ZqX>powO3R|EKf)eLbE=#yIvy(oMlkrdxEtH^*gb1XKf67K3ZFe zaQJ?0eL~rtGE0`iRghz67U%@r?MFY)U0!c6l61{MVVMX@ZhV*@lGr4vNK4iTy1nkS zKVoZ9uum(iXVzR&rTRq_OHb4nz@y)&p9$UlXZ@7Le11xda11fr%dy)F(KY*hp8)%*EN}ufN`0dW8PAjAbcIEZVi1rr4}6PQzy%6s+?Lbi)qQ!szg`R2Y!O={vj)< z_(5A4AJgVzN%EWF@{6|M`F@y&IyNUfSiWrJuhE11-##b7LG0$dTkX8tZ0%P2tHBY$ zS<0vr904(wSQJXRhT19|!@knuKs2h1*$rK@l=7KbIOk|8v!Q;o2tATT&_GP8;iS7M z@xt_e!y3fj@7f!3D=`hOtzseSAU8~NfSOv0hu@jj>~q2f$H*{+W-pWVG%&kgP8oi8 z_IL>r|8VuM3(Au8reZeTE-?T=(DrD1OOFn%wHahArDW+%Uc3(XeTq>k+F&L2= zOotIuA)bRRj3?--c?)F|-80YDzBoW5^9oy4G?h_~TpUsTd}f+0rfDX?gnybA96bPz z`2oz+J3<`v?LgcXz%2Dt)HVt7G_*CzIdpSd2UPuLTYiM251h&im4X_S<6}GYAWv~T z`n+u!+@fRt05kIK`2~n=Kb${R_K|FFme){AdmVDgk$==P=Kuv2*8|4&7Bi3J-DT_ ztm*uf;K^N?V#c8gNE~3K2v;#@S~vh4A``!0{pikP*JoU8eM0#$GaJ>FeT5meAnYKS z@jf_o4=Ut&4gqe`R4fAirUcFa+mJSWJdhd(y-d)(=HJI+q zjY(&w;vPHE6qP|A7(@kt1Jvd*o45*?O>y)g(~s$c4Lf}p=PSsCYZw%r*T>DEq6c(< z0BJmy0ieh3qtmB73Ao*w-ZGT?dQ0Uj`c`iNzTfM$@@K^~l|+5>L-~kRfREq{`Eu6L zB|N2YNh!)^Rc=t_kSb59@?%wgp~|9bcxJ0AH>vUgRlcmspQ-YzYnX-7rPU~VmKJeL zxp`?ZobbrfT6B#pEe)sfmz_86CZW-BYF^eLUm4!9Y>6gk(+`$k z$s7D>c`kz1TPyOZc0~gl2%r|VPc18Fjir2OB`-d^vSxH~#i}ujuN^5x6l;IPrvt=-S4!yi)VP<16<6FhL(1$rFIkI=pAN{~&zwyxCKiPI><=P>lYCp|TLX8Ua17&O+r_1oyZ@ zLp?}t|zr0@1`YpFInouFc4)psjg1A7cg*y zsOLcfY|LKND&?-x^k1PnFAxg1$0T0^>;M4Mf8E`X{tnQ1Pnq)$z@{FKY3d!o>jEr( z?YQTjY9DiA9HdkCv?bls>$<1Ds~Trgzw2t0+jiwcfT3M=a1`X^#S1Q%lT(ARyVN_~ z0@xsj?1*7Jh}X~rXF3s;xW`>ZD-zeqTj@KAV(jrrVpGNfexBjEv~X}LTDsfXH&~f- z20@H3Rk)c6QxT1u3x>Zx=+(48?xr8@en588-(>VObzhFd)L=S?-ANAVH-A-KHpTTW zJ#I_&D#1Yq5hRd22HSzLjeGi`pGWrej*r;5F2YvQGGh0_9Fvz|07s-An*Nze_pV8U zg-J+;s1!iX0gQuBCihSdo(#Ku3r|jg=P6L>1*Ey;WJJ)hu+hi5Ou!sm$m_#~o=ekk zH%#9n5LGlB5{!D81QO0g(JOey+piJ}Zk3#pP|T-^?#@X~VCPTYCez)9U)7wKr7^0> zyri@arwnMpsajvCo;Cp4qJCvs;TW!6K_m-55N+YUD#vYP)OjBW$8diJAVx+*4BbKN z_cbM!z;`)O;9&n>knbp>#@o4MpZyZQIi&7s6xm;(Uf)$h#Pgj>X7l$i1G;={e@E%q zcRR)uFdDPlW!!k0LI*mgspQWo*$G8BE?`@RJ|4d0Yj|Vyf;eAdIR?g|Lv1r~+k*!N zr*oi$%2a%U>p0lO7HS_|DaZJ;U;0u!yDuNqHe>p#^JKb>u9p(YpH&yTtcs<_V zL`-KmII_oyQ26zLQO=sfiyT3nc=77tE!ZJ;WVwY*r&j9ru6ULvVtv0sU2|Oo z1`|=2cNaVP9R%jF{PCDR7Coh$(Pg;n*aAk|Cyy0^#n+B49~Z%)D2O)S(rQFrR8r6W z9Gd#j)EVcaHKb`=t@&C2|Kb-B#&9XXrhcz-vJwNfc^7#qIQI|>l6yR&k{GMiu29-< zIo^`4{X@ssfVJNq@54K(3MABZB8HyrCt8DP`h1+;KG9J<);D1AI>QOqLD=|rC^v~y z)pJYU@Gl;|!sq_{;g}-wuW&4i@*iL0Fy*kY`|1jnoLN3+pkwIL^vH#!n>+^YS8Rp{DnPH z9SVmqe>~6(u%_8~&zOknV{8Dcv9y*xIb925r9NFR=i{}@kxKJWoBTX>mKoWC%`dX@ zG7CTFWgBVllM7`b{rE|nWx@8ML4%?p#!d9nGjD0<_6^_kwHeyFEc|+U--xiQAl4>f z^hoJ?ijF5`AdEPaV#X^pFhY*|rd3BIrhV3v-5N?sZEfhs{C+XnOq{Yvqj; z{^nOCe* zu|fzjOA677I@z9!c12oy-UaQ>3))?&_VDS~Vp8kY=*-{7Dz|8+2!l*M{(JF%%MI(c zuibuU-P$c%wym$bW&3*Pt9JV>)beVq=4DLJ!L;{KhfqI^x&ZYvpq}1-ird54-}!_5 EA0vEA3;+NC diff --git a/odra-casper/test-vm/src/casper_host.rs b/odra-casper/test-vm/src/casper_host.rs index 94d11235..1fce4682 100644 --- a/odra-casper/test-vm/src/casper_host.rs +++ b/odra-casper/test-vm/src/casper_host.rs @@ -61,16 +61,13 @@ impl HostContext for CasperHost { fn get_event(&self, contract_address: &Address, index: u32) -> Result { if !contract_address.is_contract() { - panic!("Events can be retrieved only for contracts, not for accounts.") + return Err(EventError::TriedToQueryEventForNonContract); } self.vm.borrow().get_event(contract_address, index) } - fn get_events_count(&self, contract_address: &Address) -> u32 { - if !contract_address.is_contract() { - panic!("Events can be retrieved only for contracts, not for accounts.") - } - self.vm.borrow().get_events_count(contract_address) + fn get_events_count(&self, address: &Address) -> Result { + self.vm.borrow().get_events_count(address) } fn call_contract( diff --git a/odra-casper/test-vm/src/vm/casper_vm.rs b/odra-casper/test-vm/src/vm/casper_vm.rs index 2701a013..d544ba7d 100644 --- a/odra-casper/test-vm/src/vm/casper_vm.rs +++ b/odra-casper/test-vm/src/vm/casper_vm.rs @@ -132,11 +132,12 @@ impl CasperVm { } /// Gets the count of events for the given contract address. - pub fn get_events_count(&self, contract_address: &Address) -> u32 { - let package_hash = contract_address - .as_package_hash() - .expect("Events can only be queried for contracts"); - self.events_length(*package_hash) + pub fn get_events_count(&self, contract_address: &Address) -> Result { + let package_hash = contract_address.as_package_hash(); + if package_hash.is_none() { + return Err(EventError::TriedToQueryEventForNonContract); + } + Ok(self.events_length(*package_hash.unwrap())) } /// Attaches a value to the next call. diff --git a/odra-casper/wasm-env/Cargo.toml b/odra-casper/wasm-env/Cargo.toml index b53ba85d..c47ef2ff 100644 --- a/odra-casper/wasm-env/Cargo.toml +++ b/odra-casper/wasm-env/Cargo.toml @@ -9,12 +9,11 @@ homepage = { workspace = true } repository = { workspace = true } [dependencies] -lazy_static = { version = "1.4.0", features = [ "spin_no_std" ] } +lazy_static = { workspace = true, features = [ "spin_no_std" ] } odra-core = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dependencies] casper-contract = { workspace = true, default-features = false } -ink_allocator = { version = "4.2.1", default-features = false } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] casper-contract = { workspace = true, default-features = false, features = ["test-support"] } diff --git a/odra-casper/wasm-env/src/lib.rs b/odra-casper/wasm-env/src/lib.rs index 54bf7639..56c775d7 100644 --- a/odra-casper/wasm-env/src/lib.rs +++ b/odra-casper/wasm-env/src/lib.rs @@ -16,7 +16,3 @@ mod wasm_contract_env; pub use casper_contract; pub use wasm_contract_env::WasmContractEnv; - -#[cfg(all(target_arch = "wasm32", not(feature = "disable-allocator")))] -#[allow(unused_imports)] -use ink_allocator; diff --git a/odra-schema/src/lib.rs b/odra-schema/src/lib.rs index 7403156f..e2e8c467 100644 --- a/odra-schema/src/lib.rs +++ b/odra-schema/src/lib.rs @@ -279,6 +279,14 @@ fn call_method( } } +/// Converts a string from camel case to snake case. +pub fn camel_to_snake(text: T) -> String { + text.to_string() + .from_case(Case::UpperCamel) + .without_boundaries(&[Boundary::UpperDigit, Boundary::LowerDigit]) + .to_case(Case::Snake) +} + #[cfg(test)] mod test { use odra_core::{args::Maybe, Address}; @@ -440,10 +448,3 @@ mod test { assert_eq!(schema.events.len(), 1); } } - -pub fn camel_to_snake(text: T) -> String { - text.to_string() - .from_case(Case::UpperCamel) - .without_boundaries(&[Boundary::UpperDigit, Boundary::LowerDigit]) - .to_case(Case::Snake) -} diff --git a/odra-vm/src/odra_vm_host.rs b/odra-vm/src/odra_vm_host.rs index 2e3f1aab..ff85bcdd 100644 --- a/odra-vm/src/odra_vm_host.rs +++ b/odra-vm/src/odra_vm_host.rs @@ -49,7 +49,7 @@ impl HostContext for OdraVmHost { self.vm.borrow().get_event(contract_address, index) } - fn get_events_count(&self, contract_address: &Address) -> u32 { + fn get_events_count(&self, contract_address: &Address) -> Result { self.vm.borrow().get_events_count(contract_address) } diff --git a/odra-vm/src/vm/odra_vm.rs b/odra-vm/src/vm/odra_vm.rs index fb67bcf3..cc96f284 100644 --- a/odra-vm/src/vm/odra_vm.rs +++ b/odra-vm/src/vm/odra_vm.rs @@ -229,7 +229,7 @@ impl OdraVm { } /// Gets the number of events emitted by the given address from the global state. - pub fn get_events_count(&self, address: &Address) -> u32 { + pub fn get_events_count(&self, address: &Address) -> Result { self.state.read().unwrap().get_events_count(address) } @@ -602,7 +602,7 @@ mod tests { // given an empty instance let instance = OdraVm::default(); - let first_contract_address = utils::account_address_from_str("abc"); + let first_contract_address = utils::contract_address_from_u32(123); // put a contract on stack push_address(&instance, &first_contract_address); @@ -611,7 +611,7 @@ mod tests { instance.emit_event(&first_event); instance.emit_event(&second_event); - let second_contract_address = utils::account_address_from_str("bca"); + let second_contract_address = utils::contract_address_from_u32(321); // put a next contract on stack push_address(&instance, &second_contract_address); diff --git a/odra-vm/src/vm/odra_vm_state.rs b/odra-vm/src/vm/odra_vm_state.rs index 5a09d2f1..05189e76 100644 --- a/odra-vm/src/vm/odra_vm_state.rs +++ b/odra-vm/src/vm/odra_vm_state.rs @@ -86,6 +86,9 @@ impl OdraVmState { } pub fn get_event(&self, address: &Address, index: u32) -> Result { + if !address.is_contract() { + return Err(EventError::ContractDoesntSupportEvents); + } let events = self.events.get(address); if events.is_none() { return Err(EventError::IndexOutOfBounds); @@ -97,10 +100,15 @@ 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 get_events_count(&self, address: &Address) -> Result { + if !address.is_contract() { + return Err(EventError::ContractDoesntSupportEvents); + } + let events = self.events.get(address); + if events.is_none() { + return Err(EventError::CouldntExtractEventData); + } + Ok(events.unwrap().len() as u32) } pub fn attach_value(&mut self, amount: U512) { diff --git a/odra-vm/src/vm/utils.rs b/odra-vm/src/vm/utils.rs index a5ba66e1..d05fae2c 100644 --- a/odra-vm/src/vm/utils.rs +++ b/odra-vm/src/vm/utils.rs @@ -1,6 +1,7 @@ use odra_core::casper_types::PackageHash; use odra_core::{casper_types::account::AccountHash, Address}; +#[cfg(test)] pub fn account_address_from_str(str: &str) -> Address { use odra_core::casper_types::account::{ ACCOUNT_HASH_FORMATTED_STRING_PREFIX, ACCOUNT_HASH_LENGTH From 681c83e14be75a67fb5fe9f04b68e1e387d04809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Zieli=C5=84ski?= Date: Wed, 14 Aug 2024 11:18:27 +0200 Subject: [PATCH 17/22] Allocators setup --- .gitignore | 1 + Cargo.toml | 9 ++++----- modules/Cargo.toml | 2 +- modules/src/erc20.rs | 20 ++++++++++++++++++++ modules/src/lib.rs | 2 +- odra-casper/wasm-env/Cargo.toml | 1 + odra-casper/wasm-env/src/lib.rs | 12 ++++++++++++ 7 files changed, 40 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index c137a61a..edea2c89 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,6 @@ examples/.env.testnet examples/.env.integration modules/.builder_casper modules/wasm +benchmark/wasm benchmark/gas_report.json .DS_Store \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 78d0d95a..1ff464eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,12 +52,11 @@ mockall = { version = "0.12.1" } sha3 = "0.10" hex = "0.4" tokio = "1.38" -base16 = "0.2" -base64 = "0.22" -serde-json-wasm = "1.0" -anyhow = "1.0.86" +base16 = { version = "0.2", default-features = false } +base64 = { version = "0.22", default-features = false, features = ["alloc"] } +# serde-json-wasm = "1.0" +anyhow = { version = "1.0.86", default-features = false } convert_case = "0.6.0" -ink_allocator = "4.2.1" lazy_static = "1.5.0" [patch.crates-io] diff --git a/modules/Cargo.toml b/modules/Cargo.toml index c86fb048..5964ca50 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["wasm", "webassembly", "blockchain"] odra = { path = "../odra", default-features = false } serde = { workspace = true, default-features = false } serde_json = { workspace = true, default-features = false } -serde-json-wasm = { workspace = true } +# serde-json-wasm = { workspace = true } base16 = { workspace = true } base64 = { workspace = true } diff --git a/modules/src/erc20.rs b/modules/src/erc20.rs index b000ec77..eec50cce 100644 --- a/modules/src/erc20.rs +++ b/modules/src/erc20.rs @@ -4,6 +4,26 @@ use crate::erc20::events::*; use odra::prelude::*; use odra::{casper_types::U256, Address, Mapping, Var}; +/// A panic handler for use in a `no_std` environment which simply aborts the process. +// #[panic_handler] +// #[no_mangle] +// pub fn panic(_info: &core::panic::PanicInfo) -> ! { +// #[cfg(feature = "test-support")] +// crate::contract_api::runtime::print(&alloc::format!("{}", _info)); +// core::intrinsics::abort(); +// } + +// /// An out-of-memory allocation error handler for use in a `no_std` environment which simply aborts +// /// the process. +// #[alloc_error_handler] +// #[no_mangle] +// pub fn oom(_: core::alloc::Layout) -> ! { +// core::intrinsics::abort(); +// } + +// #[lang = "eh_personality"] +// extern "C" fn eh_personality() {} + /// ERC20 token module #[odra::module(events = [Approval, Transfer], errors = Error)] pub struct Erc20 { diff --git a/modules/src/lib.rs b/modules/src/lib.rs index 48555704..b9edc6a0 100644 --- a/modules/src/lib.rs +++ b/modules/src/lib.rs @@ -7,7 +7,7 @@ extern crate alloc; pub mod access; pub mod cep18; pub mod cep18_token; -pub mod cep78; +// pub mod cep78; pub mod erc1155; pub mod erc1155_receiver; pub mod erc1155_token; diff --git a/odra-casper/wasm-env/Cargo.toml b/odra-casper/wasm-env/Cargo.toml index c47ef2ff..48693a90 100644 --- a/odra-casper/wasm-env/Cargo.toml +++ b/odra-casper/wasm-env/Cargo.toml @@ -14,6 +14,7 @@ odra-core = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dependencies] casper-contract = { workspace = true, default-features = false } +ink_allocator = { version = "5.0.0", default-features = false } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] casper-contract = { workspace = true, default-features = false, features = ["test-support"] } diff --git a/odra-casper/wasm-env/src/lib.rs b/odra-casper/wasm-env/src/lib.rs index 56c775d7..408f08f9 100644 --- a/odra-casper/wasm-env/src/lib.rs +++ b/odra-casper/wasm-env/src/lib.rs @@ -16,3 +16,15 @@ mod wasm_contract_env; pub use casper_contract; pub use wasm_contract_env::WasmContractEnv; + +#[cfg(all(target_arch = "wasm32", not(feature = "disable-allocator")))] +#[allow(unused_imports)] +use ink_allocator; + +/// Panic handler for the WASM target architecture. +#[cfg(target_arch = "wasm32")] +#[panic_handler] +#[no_mangle] +pub fn panic(_info: &core::panic::PanicInfo) -> ! { + core::intrinsics::abort(); +} From 1a5fd169ae49ac72e6359fe3584bbb52514b454f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 14 Aug 2024 13:21:52 +0200 Subject: [PATCH 18/22] Allocator fix for examples. --- Cargo.toml | 2 +- examples/Cargo.toml | 2 +- examples/src/features/mod.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1ff464eb..640b45fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ serde = { version = "1.0.195", default-features = false } serde_json = { version = "1.0.113", default-features = false } num-traits = { version = "0.2.14", default-features = false } mockall = { version = "0.12.1" } -sha3 = "0.10" +sha3 = { version = "0.10", default-features = false } hex = "0.4" tokio = "1.38" base16 = { version = "0.2", default-features = false } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index da677948..daea215b 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" [dependencies] odra = { path = "../odra", features = [], default-features = false } odra-modules = { path = "../modules", features = [], default-features = false } -sha3 = { workspace = true } odra-casper-livenet-env = { path = "../odra-casper/livenet-env", optional = true } +sha3 = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] odra-build = { path = "../odra-build" } diff --git a/examples/src/features/mod.rs b/examples/src/features/mod.rs index 9731a0bc..e596abbb 100644 --- a/examples/src/features/mod.rs +++ b/examples/src/features/mod.rs @@ -1,5 +1,5 @@ //! Module containing examples of various Odra features. -pub mod access_control; +// pub mod access_control; pub mod collecting_events; pub mod cross_calls; pub mod custom_types; From cd951350218c3f8b984d3fb9d9a3a4187f73ba1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 14 Aug 2024 14:38:27 +0200 Subject: [PATCH 19/22] Reenable cep78. --- Cargo.toml | 2 +- examples/src/features/mod.rs | 2 +- modules/Cargo.toml | 2 +- modules/src/lib.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 640b45fd..5b590e0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ hex = "0.4" tokio = "1.38" base16 = { version = "0.2", default-features = false } base64 = { version = "0.22", default-features = false, features = ["alloc"] } -# serde-json-wasm = "1.0" +serde-json-wasm = { version = "1.0", default-features = false } anyhow = { version = "1.0.86", default-features = false } convert_case = "0.6.0" lazy_static = "1.5.0" diff --git a/examples/src/features/mod.rs b/examples/src/features/mod.rs index e596abbb..9731a0bc 100644 --- a/examples/src/features/mod.rs +++ b/examples/src/features/mod.rs @@ -1,5 +1,5 @@ //! Module containing examples of various Odra features. -// pub mod access_control; +pub mod access_control; pub mod collecting_events; pub mod cross_calls; pub mod custom_types; diff --git a/modules/Cargo.toml b/modules/Cargo.toml index 5964ca50..c86fb048 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["wasm", "webassembly", "blockchain"] odra = { path = "../odra", default-features = false } serde = { workspace = true, default-features = false } serde_json = { workspace = true, default-features = false } -# serde-json-wasm = { workspace = true } +serde-json-wasm = { workspace = true } base16 = { workspace = true } base64 = { workspace = true } diff --git a/modules/src/lib.rs b/modules/src/lib.rs index b9edc6a0..48555704 100644 --- a/modules/src/lib.rs +++ b/modules/src/lib.rs @@ -7,7 +7,7 @@ extern crate alloc; pub mod access; pub mod cep18; pub mod cep18_token; -// pub mod cep78; +pub mod cep78; pub mod erc1155; pub mod erc1155_receiver; pub mod erc1155_token; From dd3bbf57acca641ff04e46739e52ade006686d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Mon, 26 Aug 2024 11:33:24 +0200 Subject: [PATCH 20/22] Use std in client. Update chainspec to be compatible with newest version of Condor. --- core/src/contract_env.rs | 1 - odra-casper/livenet-env/Cargo.toml | 2 +- odra-casper/rpc-client/Cargo.toml | 8 ++------ odra-casper/rpc-client/src/casper_client.rs | 14 +++----------- .../rpc-client/src/casper_client/configuration.rs | 3 --- odra-casper/test-vm/resources/chainspec.toml | 6 ++++-- 6 files changed, 10 insertions(+), 24 deletions(-) diff --git a/core/src/contract_env.rs b/core/src/contract_env.rs index 9370c40c..bb9c3270 100644 --- a/core/src/contract_env.rs +++ b/core/src/contract_env.rs @@ -109,7 +109,6 @@ impl ContractEnv { /// Sets the value associated with the given named key in the contract storage. pub fn set_named_value>(&self, name: U, value: T) { let key = name.as_ref(); - // todo: map errors to correct Odra errors let cl_value = CLValue::from_t(value) .map_err(|e| match e { CLValueError::Serialization(_) => OdraError::VmError(Serialization), diff --git a/odra-casper/livenet-env/Cargo.toml b/odra-casper/livenet-env/Cargo.toml index c437abf0..5aa95423 100644 --- a/odra-casper/livenet-env/Cargo.toml +++ b/odra-casper/livenet-env/Cargo.toml @@ -10,7 +10,7 @@ repository = { workspace = true } [dependencies] odra-core = { workspace = true } -odra-casper-rpc-client = { workspace = true, features = ["std"]} +odra-casper-rpc-client = { workspace = true } blake2 = { workspace = true } log = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread"]} diff --git a/odra-casper/rpc-client/Cargo.toml b/odra-casper/rpc-client/Cargo.toml index 3d9d9616..9aed3171 100644 --- a/odra-casper/rpc-client/Cargo.toml +++ b/odra-casper/rpc-client/Cargo.toml @@ -11,7 +11,7 @@ repository = { workspace = true } [dependencies] odra-core = { workspace = true } casper-execution-engine = { workspace = true } -casper-types = { workspace = true } +casper-types = { workspace = true, features = ["std-fs-io"] } casper-client = { workspace = true } derive_more = { version = "1.0.0-beta.7", features = ["from"]} @@ -30,8 +30,4 @@ thiserror = "1.0.40" bytes = "1.5.0" humantime = "2.1.0" base16 = { workspace = true } -tokio = { workspace = true, optional = true } - -[features] -default = [] -std = ["casper-types/std-fs-io", "tokio/rt"] \ No newline at end of file +tokio = { workspace = true, features = ["rt"] } diff --git a/odra-casper/rpc-client/src/casper_client.rs b/odra-casper/rpc-client/src/casper_client.rs index 8d6e1399..c581a470 100644 --- a/odra-casper/rpc-client/src/casper_client.rs +++ b/odra-casper/rpc-client/src/casper_client.rs @@ -556,9 +556,8 @@ impl CasperClient { } async fn query_global_state(&self, key: &str, path: Option) -> StoredValue { - // Todo: set rpc id to a random number query_global_state( - "", + &self.rpc_id(), self.node_address(), Verbosity::Low as u64, "", @@ -585,16 +584,10 @@ impl CasperClient { &DEPLOY_WAIT_TIME, &deploy_hash_str )); - #[cfg(feature = "std")] - { - tokio::time::sleep(std::time::Duration::from_secs(DEPLOY_WAIT_TIME)).await; - } - #[cfg(not(feature = "std"))] - { - // TODO: Implement sleep for no_std - } + tokio::time::sleep(std::time::Duration::from_secs(DEPLOY_WAIT_TIME)).await; let result = self.get_deploy(deploy_hash).await.execution_info; + if result.is_some() { final_result = result .ok_or(LivenetToDo)? @@ -717,7 +710,6 @@ impl CasperClient { } } -#[cfg(feature = "std")] impl Default for CasperClient { fn default() -> Self { Self::new(CasperClientConfiguration::from_env()) diff --git a/odra-casper/rpc-client/src/casper_client/configuration.rs b/odra-casper/rpc-client/src/casper_client/configuration.rs index 24c23678..5fb0c891 100644 --- a/odra-casper/rpc-client/src/casper_client/configuration.rs +++ b/odra-casper/rpc-client/src/casper_client/configuration.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "std")] use crate::casper_client::{ ENV_ACCOUNT_PREFIX, ENV_CHAIN_NAME, ENV_CSPR_CLOUD_AUTH_TOKEN, ENV_EVENTS_ADDRESS, ENV_LIVENET_ENV_FILE, ENV_NODE_ADDRESS, ENV_SECRET_KEY @@ -6,7 +5,6 @@ use crate::casper_client::{ use crate::utils::{get_env_variable, get_optional_env_variable}; use casper_client::Verbosity; use odra_core::casper_types::SecretKey; -#[cfg(feature = "std")] use std::path::PathBuf; #[derive(Debug)] @@ -20,7 +18,6 @@ pub struct CasperClientConfiguration { pub cspr_cloud_auth_token: Option } -#[cfg(feature = "std")] impl CasperClientConfiguration { pub fn from_env() -> Self { // Check for additional .env file diff --git a/odra-casper/test-vm/resources/chainspec.toml b/odra-casper/test-vm/resources/chainspec.toml index a939119a..6883bdce 100644 --- a/odra-casper/test-vm/resources/chainspec.toml +++ b/odra-casper/test-vm/resources/chainspec.toml @@ -170,7 +170,7 @@ block_max_approval_count = 2600 # Maximum block size in bytes including transactions contained by the block. 0 means unlimited. max_block_size = 10_485_760 # The upper limit of total gas of all transactions in a block. -block_gas_limit = 10_000_000_000_000 +block_gas_limit = 3_300_000_000_000 # The minimum amount in motes for a valid native transfer. native_transfer_minimum_motes = 2_500_000_000 # The maximum value to which `transaction_acceptor.timestamp_leeway` can be set in the config.toml file. @@ -193,7 +193,7 @@ max_timestamp_leeway = '5 seconds' # [4] -> The maximum number of transactions the lane can contain native_mint_lane = [0, 1024, 1024, 65_000_000_000, 650] native_auction_lane = [1, 2048, 2048, 362_500_000_000, 145] -wasm_lanes = [[2, 1_048_576, 2048, 1_000_000_000_000, 2], [3, 262_144, 1024, 100_000_000_000, 3], [4, 131_072, 1024, 50_000_000_000, 5], [5, 8_192, 512, 1_500_000_000, 15]] +wasm_lanes = [[2, 1_048_576, 2048, 1_000_000_000_000, 1], [3, 344_064, 1024, 500_000_000_000, 3], [4, 172_032, 1024, 50_000_000_000, 7], [5, 12_288, 512, 1_500_000_000, 15]] [transactions.deploy] # The maximum number of Motes allowed to be spent during payment. 0 means unlimited. @@ -341,6 +341,8 @@ read_era_id = 10_000 activate_bid = 10_000 redelegate = 2_500_000_000 change_bid_public_key = 5_000_000_000 +add_reservations = 2_500_000_000 +cancel_reservations = 2_500_000_000 [system_costs.mint_costs] mint = 2_500_000_000 From 4a54ec6fffe848380eb82107e894cd192da93ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 18 Sep 2024 15:48:02 +0200 Subject: [PATCH 21/22] Native events - working, before refactoring. Upgraded casper node and chainspec. --- core/src/contract_context.rs | 7 ++ core/src/contract_env.rs | 8 ++ core/src/host.rs | 52 ++++++++ examples/src/features/events.rs | 39 +++++- .../livenet-env/src/livenet_contract_env.rs | 4 + odra-casper/livenet-env/src/livenet_host.rs | 8 ++ odra-casper/test-vm/resources/chainspec.toml | 5 +- odra-casper/test-vm/src/casper_host.rs | 11 ++ odra-casper/test-vm/src/vm/casper_vm.rs | 118 ++++++++++++++++-- odra-casper/wasm-env/src/consts.rs | 3 + odra-casper/wasm-env/src/host_functions.rs | 20 ++- odra-casper/wasm-env/src/wasm_contract_env.rs | 4 + odra-vm/src/odra_vm_contract_env.rs | 4 + odra-vm/src/odra_vm_host.rs | 8 ++ odra-vm/src/vm/odra_vm.rs | 15 +++ odra-vm/src/vm/odra_vm_state.rs | 44 ++++++- 16 files changed, 332 insertions(+), 18 deletions(-) diff --git a/core/src/contract_context.rs b/core/src/contract_context.rs index 3bf42c86..3c32d1b6 100644 --- a/core/src/contract_context.rs +++ b/core/src/contract_context.rs @@ -111,6 +111,13 @@ pub trait ContractContext { /// * `event` - The event data to emit. fn emit_event(&self, event: &Bytes); + /// Emits an event with the specified event data using native mechanism. + /// + /// # Arguments + /// + /// * `event` - The event data to emit. + fn emit_native_event(&self, event: &Bytes); + /// Transfers tokens to the specified address. /// /// # Arguments diff --git a/core/src/contract_env.rs b/core/src/contract_env.rs index bb9c3270..bc232acf 100644 --- a/core/src/contract_env.rs +++ b/core/src/contract_env.rs @@ -222,6 +222,14 @@ impl ContractEnv { backend.emit_event(&bytes.into()) } + /// Emits an event with the specified data using the native mechanism. + pub fn emit_native_event(&self, event: T) { + let backend = self.backend.borrow(); + let result = event.to_bytes().map_err(ExecutionError::from); + let bytes = result.unwrap_or_revert(self); + backend.emit_native_event(&bytes.into()) + } + /// Verifies the signature of a message using the specified signature, public key, and message. /// /// # Arguments diff --git a/core/src/host.rs b/core/src/host.rs index 9329cd1f..b0f7470b 100644 --- a/core/src/host.rs +++ b/core/src/host.rs @@ -164,9 +164,15 @@ pub trait HostContext { /// Returns the event bytes for the specified contract address and index. fn get_event(&self, contract_address: &Address, index: u32) -> Result; + /// Returns the native event bytes for the specified contract address and index. + fn get_native_event(&self, contract_address: &Address, index: u32) -> Result; + /// Returns the number of emitted events for the specified contract address. fn get_events_count(&self, contract_address: &Address) -> Result; + /// Returns the number of emitted native events for the specified contract address. + fn get_native_events_count(&self, contract_address: &Address) -> Result; + /// Calls a contract at the specified address with the given call definition. fn call_contract( &self, @@ -415,6 +421,16 @@ impl HostEnv { backend.get_event(contract_address.address(), index) } + /// Retrieves a raw native event (serialized) with the specified index from the specified contract. + pub fn get_native_event_bytes( + &self, + contract_address: &T, + index: u32 + ) -> Result { + let backend = self.backend.borrow(); + backend.get_native_event(contract_address.address(), index) + } + /// Returns the names of all events emitted by the specified contract. pub fn event_names(&self, contract_address: &T) -> Vec { let events_count = self.events_count(contract_address); @@ -459,6 +475,14 @@ impl HostEnv { .unwrap_or_default() } + /// Returns the number of native events emitted by the specified contract. + pub fn native_events_count(&self, address: &T) -> u32 { + let backend = self.backend.borrow(); + backend + .get_native_events_count(address.address()) + .unwrap_or_default() + } + /// Returns true if the specified event was emitted by the specified contract. pub fn emitted_event( &self, @@ -473,6 +497,7 @@ impl HostEnv { .to_bytes() .unwrap_or_else(|_| panic!("Couldn't serialize event")) ); + (0..events_count) .map(|event_id| { self.get_event_bytes(contract_address, event_id) @@ -486,6 +511,33 @@ impl HostEnv { .any(|bytes| bytes == event_bytes) } + /// Returns true if the specified event was emitted by the specified contract. + pub fn emitted_native_event( + &self, + contract_address: &R, + event: &T + ) -> bool { + let contract_address = contract_address.address(); + let events_count = self.native_events_count(contract_address); + + let event_bytes = Bytes::from( + event + .to_bytes() + .unwrap_or_else(|_| panic!("Couldn't serialize event")) + ); + (0..events_count) + .map(|event_id| { + self.get_native_event_bytes(contract_address, event_id) + .unwrap_or_else(|e| { + panic!( + "Couldn't get event at address {:?} with id {}: {:?}", + &contract_address, event_id, e + ) + }) + }) + .any(|bytes| bytes == event_bytes) + } + /// Returns true if an event with the specified name was emitted by the specified contract. pub fn emitted, R: Addressable>( &self, diff --git a/examples/src/features/events.rs b/examples/src/features/events.rs index 4b36d039..653c4fc6 100644 --- a/examples/src/features/events.rs +++ b/examples/src/features/events.rs @@ -15,6 +15,14 @@ pub struct PartyStarted { pub block_time: u64 } +#[odra::event] +pub struct NativePartyStarted { + /// Address of the caller. + pub caller: Address, + /// Block time when the contract was initialized. + pub block_time: u64 +} + #[odra::module] impl PartyContract { /// Initializes the contract. @@ -23,24 +31,47 @@ impl PartyContract { caller: self.env().caller(), block_time: self.env().get_block_time() }); + self.env().emit_native_event(NativePartyStarted { + caller: self.env().caller(), + block_time: self.env().get_block_time() + }); + } + + pub fn emit(&mut self) { + self.env().emit_event(PartyStarted { + caller: self.env().caller(), + block_time: self.env().get_block_time() + }); + self.env().emit_native_event(NativePartyStarted { + caller: self.env().caller(), + block_time: self.env().get_block_time() + }); } } #[cfg(test)] mod tests { - use super::{PartyContract, PartyStarted}; + use super::{NativePartyStarted, PartyContract, PartyStarted}; use odra::host::{Deployer, NoArgs}; #[test] fn test_party() { let test_env = odra_test::env(); - let party_contract = PartyContract::deploy(&test_env, NoArgs); - test_env.emitted_event( + let mut party_contract = PartyContract::deploy(&test_env, NoArgs); + party_contract.emit(); + assert!(test_env.emitted_event( &party_contract, &PartyStarted { caller: test_env.get_account(0), block_time: 0 } - ); + )); + assert!(test_env.emitted_native_event( + &party_contract, + &NativePartyStarted { + caller: test_env.get_account(0), + block_time: 0 + } + )); } } diff --git a/odra-casper/livenet-env/src/livenet_contract_env.rs b/odra-casper/livenet-env/src/livenet_contract_env.rs index 405ece76..f56a4a0c 100644 --- a/odra-casper/livenet-env/src/livenet_contract_env.rs +++ b/odra-casper/livenet-env/src/livenet_contract_env.rs @@ -113,6 +113,10 @@ impl ContractContext for LivenetContractEnv { panic!("Cannot emit event in LivenetEnv") } + fn emit_native_event(&self, event: &Bytes) { + panic!("Cannot emit native event in LivenetEnv") + } + fn transfer_tokens(&self, _to: &Address, _amount: &U512) { panic!("Cannot transfer tokens in LivenetEnv") } diff --git a/odra-casper/livenet-env/src/livenet_host.rs b/odra-casper/livenet-env/src/livenet_host.rs index 1c6f1442..e05a8dbd 100644 --- a/odra-casper/livenet-env/src/livenet_host.rs +++ b/odra-casper/livenet-env/src/livenet_host.rs @@ -107,6 +107,10 @@ impl HostContext for LivenetHost { .map_err(|_| EventError::CouldntExtractEventData) } + fn get_native_event(&self, _contract_address: &Address, _index: u32) -> Result { + todo!("get_native_event not implemented for LivenetHost") + } + fn get_events_count(&self, contract_address: &Address) -> Result { let rt = Runtime::new().unwrap(); let client = self.casper_client.borrow(); @@ -114,6 +118,10 @@ impl HostContext for LivenetHost { .ok_or(EventError::CouldntExtractEventData) } + fn get_native_events_count(&self, _contract_address: &Address) -> Result { + todo!("get_native_events_count not implemented for LivenetHost") + } + fn call_contract( &self, address: &Address, diff --git a/odra-casper/test-vm/resources/chainspec.toml b/odra-casper/test-vm/resources/chainspec.toml index 6883bdce..fec815c6 100644 --- a/odra-casper/test-vm/resources/chainspec.toml +++ b/odra-casper/test-vm/resources/chainspec.toml @@ -11,7 +11,7 @@ hard_reset = false # in contract-runtime for computing genesis post-state hash. # # If it is an integer, it represents an era ID, meaning the protocol version becomes active at the start of this era. -activation_point = '${TIMESTAMP}' +activation_point = "2024-09-17T09:51:21.872206621Z" [network] # Human readable name for convenience; the genesis_hash is the true identifier. The name influences the genesis hash by @@ -272,7 +272,8 @@ size_multiplier = 100 [wasm.host_function_costs] add = { cost = 5_800, arguments = [0, 0, 0, 0] } add_associated_key = { cost = 9_000, arguments = [0, 0, 0] } -add_contract_version = { cost = 200, arguments = [0, 0, 0, 0, 0, 0, 0, 0, 30_000, 0, 0] } +add_contract_version = { cost = 200, arguments = [0, 0, 0, 0, 120_000, 0, 0, 0, 0, 0] } +add_package_version = { cost = 200, arguments = [0, 0, 0, 0, 120_000, 0, 0, 0, 30_000, 0, 0] } blake2b = { cost = 200, arguments = [0, 0, 0, 0] } call_contract = { cost = 4_500, arguments = [0, 0, 0, 0, 0, 420, 0] } call_versioned_contract = { cost = 4_500, arguments = [0, 0, 0, 0, 0, 0, 0, 420, 0] } diff --git a/odra-casper/test-vm/src/casper_host.rs b/odra-casper/test-vm/src/casper_host.rs index 1fce4682..c08e5bf3 100644 --- a/odra-casper/test-vm/src/casper_host.rs +++ b/odra-casper/test-vm/src/casper_host.rs @@ -66,10 +66,21 @@ impl HostContext for CasperHost { self.vm.borrow().get_event(contract_address, index) } + fn get_native_event(&self, contract_address: &Address, index: u32) -> Result { + if !contract_address.is_contract() { + return Err(EventError::TriedToQueryEventForNonContract); + } + self.vm.borrow().get_native_event(contract_address, index) + } + fn get_events_count(&self, address: &Address) -> Result { self.vm.borrow().get_events_count(address) } + fn get_native_events_count(&self, contract_address: &Address) -> Result { + self.vm.borrow().get_native_events_count(contract_address) + } + fn call_contract( &self, address: &Address, diff --git a/odra-casper/test-vm/src/vm/casper_vm.rs b/odra-casper/test-vm/src/vm/casper_vm.rs index d544ba7d..a5df41ba 100644 --- a/odra-casper/test-vm/src/vm/casper_vm.rs +++ b/odra-casper/test-vm/src/vm/casper_vm.rs @@ -1,12 +1,10 @@ -use odra_core::casper_types::{ - AddressableEntity, AddressableEntityHash, GenesisConfig, GenesisConfigBuilder, Package, - PackageHash, ProtocolVersion -}; +use odra_core::casper_types::{AddressableEntity, AddressableEntityHash, EntityAddr, GenesisConfig, GenesisConfigBuilder, HashAddr, Package, PackageHash, ProtocolVersion}; use odra_core::consts::*; use odra_core::prelude::*; use odra_core::OdraResult; use std::cell::RefCell; use std::env; +use std::hash::Hash; use std::path::PathBuf; use casper_engine_test_support::{ @@ -43,11 +41,13 @@ use odra_core::{ CallDef, ContractEnv }; use odra_core::{Address, ExecutionError, OdraError, VmError}; +use odra_core::casper_types::contract_messages::MessagePayload; /// Casper virtual machine utilizing [LmdbWasmTestBuilder]. pub struct CasperVm { accounts: Vec

, key_pairs: BTreeMap, + messages: BTreeMap>, active_account: Address, context: LmdbWasmTestBuilder, block_time: u64, @@ -131,6 +131,21 @@ impl CasperVm { // } } + /// Gets the native event at the specified index for the given contract address. + /// + /// TODO: Support negative index + /// The index may be negative, in which case it is interpreted as an offset from the end of the event list. + /// + /// Returns [EventError::IndexOutOfBounds] if the index is out of bounds. + pub fn get_native_event(&self, contract_address: &Address, index: u32) -> Result { + let messages = self.messages.get(contract_address).ok_or(EventError::IndexOutOfBounds)?; + let message = messages.get(index as usize).ok_or(EventError::IndexOutOfBounds)?; + match message { + MessagePayload::String(_) => { Err(EventError::CouldntExtractEventData)} + MessagePayload::Bytes(b) => {Ok(b.clone())} + } + } + /// Gets the count of events for the given contract address. pub fn get_events_count(&self, contract_address: &Address) -> Result { let package_hash = contract_address.as_package_hash(); @@ -140,6 +155,12 @@ impl CasperVm { Ok(self.events_length(*package_hash.unwrap())) } + /// Gets the count of native events for the given contract address. + pub fn get_native_events_count(&self, contract_address: &Address) -> Result { + let messages = self.messages.get(contract_address).ok_or(EventError::IndexOutOfBounds)?; + Ok(messages.len() as u32) + } + /// Attaches a value to the next call. pub fn attach_value(&mut self, amount: U512) { self.attached_value = amount; @@ -206,6 +227,8 @@ impl CasperVm { call_def: call_def.clone() }); + self.collect_messages(); + self.attached_value = U512::zero(); if let Some(error) = self.context.get_error() { let odra_error = parse_error(error); @@ -216,6 +239,87 @@ impl CasperVm { } } + fn collect_messages(&mut self) { + let messages = self.context.get_last_exec_result().unwrap(); + let messages = messages.messages(); + messages.iter().for_each(|message| { + let payload = message.payload().clone(); + let addressable_entity = self.get_addressable_entity_from_entity_addr(message.entity_hash()); + let address = Address::Contract(addressable_entity.package_hash()); + let contract_hash = message.entity_hash().value(); + self.messages + .entry(address.into()) + .or_insert_with(Vec::new) + .push(payload); + }); + } + + fn get_addressable_entity(&self, address: &Address) -> AddressableEntity { + let query_result = self.context.query(None, + Key::Package(address.value()), + &[]).unwrap(); + let entity_hash = if let StoredValue::Package(package) = query_result { + package.current_entity_hash() + } else { + panic!( + "Stored value is not an adressable entity: {:?}", + query_result + ); + }.unwrap(); + + self.context.get_addressable_entity(entity_hash).unwrap() + } + + fn get_addressable_entity_from_entity_addr(&self, entity_addr: &EntityAddr) -> AddressableEntity { + let query_result = self.context.query(None, + Key::AddressableEntity(*entity_addr), + &[]).unwrap(); + let addressable_entity = if let StoredValue::AddressableEntity(entity) = query_result { + entity + } else { + panic!( + "Stored value is not an adressable entity: {:?}", + query_result + ); + }; + addressable_entity + } + + fn get_addressable_entity_hash(&self, address: &Address) -> AddressableEntityHash { + let query_result = self.context.query(None, + Key::Package(address.value()), + &[]).unwrap(); + let entity_hash = if let StoredValue::Package(package) = query_result { + package.current_entity_hash() + } else { + panic!( + "Stored value is not an adressable entity: {:?}", + query_result + ); + }.unwrap(); + + entity_hash + } + fn get_messages(&self, address: &Address) { + let entity = self.get_addressable_entity(address); + let (topic_name, message_topic_hash) = entity + .message_topics() + .iter() + .next() + .expect("should have at least one topic"); + + let entity_hash = self.get_addressable_entity_hash(address).value(); + + let q = self.context.query( + None, + Key::message_topic( + EntityAddr::SmartContract(entity_hash), + message_topic_hash.clone() + ), + &[] + ).unwrap(); + } + /// Creates a new contract with the specified name, initialization arguments, and entry points caller. pub fn new_contract( &mut self, @@ -398,9 +502,6 @@ impl CasperVm { } } - fn get_contract_hash(&self, package_hash: &PackageHash) -> ContractHash { - ContractHash::new(package_hash.value()) - } fn genesis_accounts( key_pairs: &BTreeMap @@ -463,7 +564,8 @@ impl CasperVm { attached_value: U512::zero(), gas_used: BTreeMap::new(), gas_report: GasReport::default(), - key_pairs + key_pairs, + messages: Default::default(), } } diff --git a/odra-casper/wasm-env/src/consts.rs b/odra-casper/wasm-env/src/consts.rs index 4f462485..93bb6a79 100644 --- a/odra-casper/wasm-env/src/consts.rs +++ b/odra-casper/wasm-env/src/consts.rs @@ -19,6 +19,9 @@ pub const CONSTRUCTOR_GROUP_NAME: &str = "constructor_group"; /// The key under which the events are stored. pub const EVENTS: &str = casper_event_standard::EVENTS_DICT; +/// The topic name used by Odra for native events. +pub const NATIVE_EVENT_TOPIC: &str = "EVENTS"; + /// The key under which the events length is stored. pub const EVENTS_LENGTH: &str = casper_event_standard::EVENTS_LENGTH; diff --git a/odra-casper/wasm-env/src/host_functions.rs b/odra-casper/wasm-env/src/host_functions.rs index c0aceddd..c4cf09ee 100644 --- a/odra-casper/wasm-env/src/host_functions.rs +++ b/odra-casper/wasm-env/src/host_functions.rs @@ -20,6 +20,8 @@ use casper_contract::{ unwrap_or_revert::UnwrapOrRevert }; use core::mem::MaybeUninit; +use casper_contract::contract_api::runtime::emit_message; +use casper_contract::ext_ffi::casper_emit_message; use odra_core::casper_types::addressable_entity::NamedKeys; use odra_core::casper_types::bytesrepr::deserialize; use odra_core::casper_types::contracts::ContractVersion; @@ -36,8 +38,9 @@ use odra_core::{ casper_event_standard::{self, Schema, Schemas} }; use odra_core::{prelude::*, Address, CallDef, ExecutionError}; - +use odra_core::casper_types::contract_messages::{MessagePayload, MessageTopicOperation}; use crate::consts; +use crate::consts::NATIVE_EVENT_TOPIC; lazy_static::lazy_static! { static ref STATE: URef = { @@ -81,6 +84,11 @@ pub fn install_contract( // Prepare named keys. let named_keys = initial_named_keys(events); + // Prepare message topic + let mut mesage_topics = BTreeMap::new(); + mesage_topics.insert(NATIVE_EVENT_TOPIC.to_string(), MessageTopicOperation::Add); + + // Create new contract. let access_uref_key = format!("{}_access_token", package_hash_key); if is_upgradable { @@ -90,7 +98,7 @@ pub fn install_contract( Some(named_keys), Some(package_hash_key.clone()), Some(access_uref_key), - None + Some(mesage_topics) ); } else { // TODO: Handle message topics @@ -99,7 +107,7 @@ pub fn install_contract( Some(named_keys), Some(package_hash_key.clone()), Some(access_uref_key), - None + Some(mesage_topics) ); } @@ -349,6 +357,12 @@ pub fn emit_event(event: &Bytes) { casper_event_standard::emit_bytes(event.clone()) } +/// Emits a native event. +pub fn emit_native_event(event: &Bytes) { + let payload = MessagePayload::Bytes(event.clone()); + emit_message(NATIVE_EVENT_TOPIC, &payload).unwrap_or_revert(); +} + /// Gets the immediate session caller of the current execution. /// /// This function ensures that only session code can execute this function, and disallows stored diff --git a/odra-casper/wasm-env/src/wasm_contract_env.rs b/odra-casper/wasm-env/src/wasm_contract_env.rs index 4f676c09..faa3086d 100644 --- a/odra-casper/wasm-env/src/wasm_contract_env.rs +++ b/odra-casper/wasm-env/src/wasm_contract_env.rs @@ -70,6 +70,10 @@ impl ContractContext for WasmContractEnv { host_functions::emit_event(event); } + fn emit_native_event(&self, event: &Bytes) { + host_functions::emit_native_event(event); + } + fn transfer_tokens(&self, to: &Address, amount: &U512) { host_functions::transfer_tokens(to, amount); } diff --git a/odra-vm/src/odra_vm_contract_env.rs b/odra-vm/src/odra_vm_contract_env.rs index 1ed21779..a7ae6c3e 100644 --- a/odra-vm/src/odra_vm_contract_env.rs +++ b/odra-vm/src/odra_vm_contract_env.rs @@ -73,6 +73,10 @@ impl ContractContext for OdraVmContractEnv { self.vm.borrow().emit_event(event); } + fn emit_native_event(&self, event: &Bytes) { + self.vm.borrow().emit_native_event(event); + } + fn transfer_tokens(&self, to: &Address, amount: &U512) { self.vm.borrow().transfer_tokens(to, amount) } diff --git a/odra-vm/src/odra_vm_host.rs b/odra-vm/src/odra_vm_host.rs index ff85bcdd..f44e380b 100644 --- a/odra-vm/src/odra_vm_host.rs +++ b/odra-vm/src/odra_vm_host.rs @@ -49,10 +49,18 @@ impl HostContext for OdraVmHost { self.vm.borrow().get_event(contract_address, index) } + fn get_native_event(&self, contract_address: &Address, index: u32) -> Result { + self.vm.borrow().get_native_event(contract_address, index) + } + fn get_events_count(&self, contract_address: &Address) -> Result { self.vm.borrow().get_events_count(contract_address) } + fn get_native_events_count(&self, contract_address: &Address) -> Result { + self.vm.borrow().get_native_events_count(contract_address) + } + fn call_contract( &self, address: &Address, diff --git a/odra-vm/src/vm/odra_vm.rs b/odra-vm/src/vm/odra_vm.rs index cc96f284..35efee6d 100644 --- a/odra-vm/src/vm/odra_vm.rs +++ b/odra-vm/src/vm/odra_vm.rs @@ -223,16 +223,31 @@ impl OdraVm { self.state.write().unwrap().emit_event(event_data); } + /// Writes an event data to the global state and marks it as native. + pub fn emit_native_event(&self, event_data: &Bytes) { + self.state.write().unwrap().emit_native_event(event_data); + } + /// Gets the event emitted by the given address at the given index from the global state. pub fn get_event(&self, address: &Address, index: u32) -> Result { self.state.read().unwrap().get_event(address, index) } + /// Gets the native event emitted by the given address at the given index from the global state. + pub fn get_native_event(&self, address: &Address, index: u32) -> Result { + self.state.read().unwrap().get_native_event(address, index) + } + /// Gets the number of events emitted by the given address from the global state. pub fn get_events_count(&self, address: &Address) -> Result { self.state.read().unwrap().get_events_count(address) } + /// Gets the number of events emitted by the given address from the global state. + pub fn get_native_events_count(&self, address: &Address) -> Result { + self.state.read().unwrap().get_native_events_count(address) + } + /// Attaches the given amount of tokens to the current call from the global state. 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 05189e76..c8dd63d3 100644 --- a/odra-vm/src/vm/odra_vm_state.rs +++ b/odra-vm/src/vm/odra_vm_state.rs @@ -18,6 +18,7 @@ pub struct OdraVmState { storage: Storage, callstack: Callstack, events: BTreeMap>, + native_events: BTreeMap>, contract_counter: u32, pub error: Option, block_time: u64, @@ -85,6 +86,19 @@ impl OdraVmState { } } + pub fn emit_native_event(&mut self, event_data: &Bytes) { + let contract_address = self.callstack.current().address(); + #[allow(clippy::manual_inspect)] + let events = self.native_events.get_mut(contract_address).map(|events| { + events.push(event_data.clone()); + events + }); + if events.is_none() { + self.native_events + .insert(*contract_address, vec![event_data.clone()]); + } + } + pub fn get_event(&self, address: &Address, index: u32) -> Result { if !address.is_contract() { return Err(EventError::ContractDoesntSupportEvents); @@ -93,13 +107,29 @@ impl OdraVmState { if events.is_none() { return Err(EventError::IndexOutOfBounds); } - let events: &Vec = events.unwrap(); + let events = events.unwrap(); let event = events .get(index as usize) .ok_or(EventError::IndexOutOfBounds)?; Ok(event.clone()) } + pub fn get_native_event(&self, address: &Address, index: u32) -> Result { + if !address.is_contract() { + return Err(EventError::ContractDoesntSupportEvents); + } + let events = self.native_events.get(address); + if events.is_none() { + return Err(EventError::IndexOutOfBounds); + } + let events = events.unwrap(); + let event = events + .get(index as usize) + .ok_or(EventError::IndexOutOfBounds)?; + Ok(event.clone()) + } + + // TODO: Reduce duplication pub fn get_events_count(&self, address: &Address) -> Result { if !address.is_contract() { return Err(EventError::ContractDoesntSupportEvents); @@ -111,6 +141,17 @@ impl OdraVmState { Ok(events.unwrap().len() as u32) } + pub fn get_native_events_count(&self, address: &Address) -> Result { + if !address.is_contract() { + return Err(EventError::ContractDoesntSupportEvents); + } + let events = self.native_events.get(address); + if events.is_none() { + return Err(EventError::CouldntExtractEventData); + } + Ok(events.unwrap().len() as u32) + } + pub fn attach_value(&mut self, amount: U512) { self.callstack.attach_value(amount); } @@ -240,6 +281,7 @@ impl Default for OdraVmState { storage: Storage::new(balances), callstack: Default::default(), events: Default::default(), + native_events: Default::default(), contract_counter: 0, error: None, block_time: 0, From 07fca5a7be0ec460e789adaca083d10dfbe3c608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Thu, 19 Sep 2024 17:12:18 +0200 Subject: [PATCH 22/22] Native events. --- core/src/call_result.rs | 93 +++++++++++++- core/src/host.rs | 121 +++++++++++++----- examples/src/features/events.rs | 25 +++- .../livenet-env/src/livenet_contract_env.rs | 2 +- odra-casper/livenet-env/src/livenet_host.rs | 6 +- odra-casper/test-vm/src/casper_host.rs | 6 +- odra-casper/test-vm/src/vm/casper_vm.rs | 108 +++++++++------- odra-casper/wasm-env/src/host_functions.rs | 11 +- odra-vm/src/odra_vm_host.rs | 6 +- 9 files changed, 286 insertions(+), 92 deletions(-) diff --git a/core/src/call_result.rs b/core/src/call_result.rs index fde736ee..211cadfb 100644 --- a/core/src/call_result.rs +++ b/core/src/call_result.rs @@ -14,7 +14,8 @@ pub(crate) struct CallResult { caller: Address, gas_used: u64, result: OdraResult, - events: BTreeMap> + events: BTreeMap>, + native_events: BTreeMap> } impl CallResult { @@ -24,14 +25,16 @@ impl CallResult { caller: Address, gas_used: u64, result: OdraResult, - events: BTreeMap> + events: BTreeMap>, + native_events: BTreeMap> ) -> Self { Self { contract_address, caller, gas_used, result, - events + events, + native_events } } @@ -79,7 +82,7 @@ impl CallResult { self.contract_address } - /// Returns the names of the events emitted by the contract at the given address. + /// Returns the names of the events emitted in the call. pub fn event_names(&self, contract_address: &Address) -> Vec { self.events .get(contract_address) @@ -89,18 +92,38 @@ impl CallResult { .collect() } - /// Returns the events emitted by the contract at the given address. + pub fn native_event_names(&self, contract_address: &Address) -> Vec { + self.native_events + .get(contract_address) + .unwrap_or(&vec![]) + .iter() + .map(|event_bytes| extract_event_name(event_bytes).unwrap()) + .collect() + } + + /// Returns the events emitted by the contract in this call. pub fn contract_events(&self, contract_address: &Address) -> Vec { self.events.get(contract_address).unwrap_or(&vec![]).clone() } - /// Checks if the specified event has been emitted by the contract at the given address. + /// Returns the native events emitted by the contract in this call. + pub fn contract_native_events(&self, contract_address: &Address) -> Vec { + self.events.get(contract_address).unwrap_or(&vec![]).clone() + } + + /// Checks if the specified event has been emitted by the contract during the call. pub fn emitted(&self, contract_address: &Address, event_name: &str) -> bool { self.event_names(contract_address) .contains(&event_name.to_string()) } - /// Checks if the specified event instance has been emitted by the contract at the given address. + /// Checks if the specified native event has been emitted by the contract during the call. + pub fn emitted_native(&self, contract_address: &Address, event_name: &str) -> bool { + self.native_event_names(contract_address) + .contains(&event_name.to_string()) + } + + /// Checks if the specified event instance has been emitted by the contract during the call. pub fn emitted_event( &self, contract_address: &Address, @@ -110,6 +133,16 @@ impl CallResult { .contains(&Bytes::from(event.to_bytes().unwrap())) } + /// Checks if the specified native event instance has been emitted by the contract during the call. + pub fn emitted_native_event( + &self, + contract_address: &Address, + event: &T + ) -> bool { + self.contract_native_events(contract_address) + .contains(&Bytes::from(event.to_bytes().unwrap())) + } + /// Returns a wrapper [ContractCallResult] object containing the current `CallResult` and the given contract address. pub fn contract_last_call(self, contract_address: Address) -> ContractCallResult { ContractCallResult { @@ -186,6 +219,15 @@ impl ContractCallResult { self.call_result.event_names(&self.contract_address) } + /// Returns the names of the native events emitted by the contract call. + /// + /// # Returns + /// + /// A vector containing the names of the events emitted by the contract call. + pub fn native_event_names(&self) -> Vec { + self.call_result.native_event_names(&self.contract_address) + } + /// Returns the events emitted by the contract call. /// /// # Returns @@ -195,6 +237,16 @@ impl ContractCallResult { self.call_result.contract_events(&self.contract_address) } + /// Returns the native events emitted by the contract call. + /// + /// # Returns + /// + /// A vector containing the events emitted by the contract call. + pub fn native_events(&self) -> Vec { + self.call_result + .contract_native_events(&self.contract_address) + } + /// Checks if an event with the specified name was emitted by the contract call. /// /// # Arguments @@ -208,6 +260,19 @@ impl ContractCallResult { self.call_result.emitted(&self.contract_address, event_name) } + /// Checks if a native event with the specified name was emitted by the contract call. + /// + /// # Arguments + /// + /// * `event_name` - The name of the event to check. + /// + /// # Returns + /// + /// `true` if the event was emitted, otherwise `false`. + pub fn emitted_native(&self, event_name: &str) -> bool { + self.call_result + .emitted_native(&self.contract_address, event_name) + } /// Checks if the specified event instance was emitted by the contract call. /// /// # Arguments @@ -221,4 +286,18 @@ impl ContractCallResult { self.call_result .emitted_event(&self.contract_address, event) } + + /// Checks if the specified native event instance was emitted by the contract call. + /// + /// # Arguments + /// + /// * `event` - The event instance to check. + /// + /// # Returns + /// + /// `true` if the event was emitted, otherwise `false`. + pub fn emitted_native_event(&self, event: &T) -> bool { + self.call_result + .emitted_native_event(&self.contract_address, event) + } } diff --git a/core/src/host.rs b/core/src/host.rs index b0f7470b..c6164e80 100644 --- a/core/src/host.rs +++ b/core/src/host.rs @@ -165,7 +165,8 @@ pub trait HostContext { fn get_event(&self, contract_address: &Address, index: u32) -> Result; /// Returns the native event bytes for the specified contract address and index. - fn get_native_event(&self, contract_address: &Address, index: u32) -> Result; + fn get_native_event(&self, contract_address: &Address, index: u32) + -> Result; /// Returns the number of emitted events for the specified contract address. fn get_events_count(&self, contract_address: &Address) -> Result; @@ -225,7 +226,8 @@ pub struct HostEnv { backend: Rc>, last_call_result: Rc>>, deployed_contracts: Rc>>, - events_count: Rc>> // contract_address -> events_count + events_count: Rc>>, // contract_address -> events_count + native_events_count: Rc>> // contract_address -> events_count } impl HostEnv { @@ -235,7 +237,8 @@ impl HostEnv { backend, last_call_result: RefCell::new(None).into(), deployed_contracts: RefCell::new(vec![]).into(), - events_count: Rc::new(RefCell::new(Default::default())) + events_count: Rc::new(RefCell::new(Default::default())), + native_events_count: Rc::new(RefCell::new(Default::default())) } } @@ -276,21 +279,20 @@ impl HostEnv { let backend = self.backend.borrow(); let mut init_args = init_args; - init_args.insert(consts::IS_UPGRADABLE_ARG, false).unwrap(); - init_args - .insert(consts::ALLOW_KEY_OVERRIDE_ARG, true) - .unwrap(); - init_args - .insert( - consts::PACKAGE_HASH_KEY_NAME_ARG, - format!("{}_package_hash", name) - ) - .unwrap(); + init_args.insert(consts::IS_UPGRADABLE_ARG, false)?; + init_args.insert(consts::ALLOW_KEY_OVERRIDE_ARG, true)?; + init_args.insert( + consts::PACKAGE_HASH_KEY_NAME_ARG, + format!("{}_package_hash", name) + )?; let deployed_contract = backend.new_contract(name, init_args, entry_points_caller)?; self.deployed_contracts.borrow_mut().push(deployed_contract); self.events_count.borrow_mut().insert(deployed_contract, 0); + self.native_events_count + .borrow_mut() + .insert(deployed_contract, 0); Ok(deployed_contract) } @@ -303,10 +305,14 @@ impl HostEnv { entry_points_caller: EntryPointsCaller ) { let events_count = self.events_count(&address); + let native_events_count = self.native_events_count(&address); let backend = self.backend.borrow(); backend.register_contract(address, contract_name, entry_points_caller); self.deployed_contracts.borrow_mut().push(address); self.events_count.borrow_mut().insert(address, events_count); + self.native_events_count + .borrow_mut() + .insert(address, native_events_count); } /// Calls a contract at the specified address with the given call definition. @@ -336,27 +342,30 @@ impl HostEnv { let call_result = backend.call_contract(&address, call_def, use_proxy); let mut events_map: BTreeMap> = BTreeMap::new(); - let mut binding = self.events_count.borrow_mut(); + let mut native_events_map: BTreeMap> = BTreeMap::new(); // 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; - let new_events_count = backend - .get_events_count(contract_address) - .unwrap_or_default(); - let mut events = vec![]; - for event_id in old_events_last_id..new_events_count { - let event = backend.get_event(contract_address, event_id).unwrap(); - events.push(event); - } - + let events = self.last_events(contract_address); + let native_events = self.last_native_events(contract_address); + // let events_count = events_count_binding.get_mut(contract_address).unwrap(); + // let old_events_last_id = *events_count; + // let new_events_count = backend + // .get_events_count(contract_address) + // .unwrap_or_default(); + // let mut events = vec![]; + // for event_id in old_events_last_id..new_events_count { + // let event = backend.get_event(contract_address, event_id).unwrap(); + // events.push(event); + // } + // events_map.insert(*contract_address, events); - - *events_count = new_events_count; + native_events_map.insert(*contract_address, native_events); + // + // *events_count = new_events_count; }); let last_call_gas_cost = backend.last_call_gas_cost(); @@ -366,7 +375,8 @@ impl HostEnv { backend.caller(), last_call_gas_cost, call_result.clone(), - events_map + events_map, + native_events_map ))); call_result @@ -411,6 +421,29 @@ impl HostEnv { .map(|r| r.0) } + /// Retrieves a native event with the specified index from the specified contract. + /// + /// # Returns + /// + /// Returns the event as an instance of the specified type, or an error if the event + /// couldn't be retrieved or parsed. + pub fn get_native_event( + &self, + contract_address: &R, + index: i32 + ) -> Result { + let contract_address = contract_address.address(); + let backend = self.backend.borrow(); + let events_count = self.native_events_count(contract_address); + let event_absolute_position = crate::utils::event_absolute_position(events_count, index) + .ok_or(EventError::IndexOutOfBounds)?; + + let bytes = backend.get_native_event(contract_address, event_absolute_position)?; + T::from_bytes(&bytes) + .map_err(|_| EventError::Parsing) + .map(|r| r.0) + } + /// Retrieves a raw event (serialized) with the specified index from the specified contract. pub fn get_event_bytes( &self, @@ -519,7 +552,6 @@ impl HostEnv { ) -> bool { let contract_address = contract_address.address(); let events_count = self.native_events_count(contract_address); - let event_bytes = Bytes::from( event .to_bytes() @@ -609,6 +641,36 @@ impl HostEnv { let backend = self.backend.borrow(); backend.transfer(to, amount) } + + fn last_events(&self, contract_address: &Address) -> Vec { + let mut old_count_binding = self.events_count.borrow_mut(); + let old_count = *old_count_binding.get(contract_address).unwrap(); + let new_count = self.events_count(contract_address); + let mut events = vec![]; + for count in old_count..new_count { + let event = self.get_event_bytes(contract_address, count).unwrap(); + events.push(event); + } + + old_count_binding.insert(*contract_address, new_count); + events + } + + fn last_native_events(&self, contract_address: &Address) -> Vec { + let mut old_count_binding = self.native_events_count.borrow_mut(); + let old_count = *old_count_binding.get(contract_address).unwrap(); + let new_count = self.native_events_count(contract_address); + let mut events = vec![]; + for count in old_count..new_count { + let event = self + .get_native_event_bytes(contract_address, count) + .unwrap(); + events.push(event); + } + + old_count_binding.insert(*contract_address, new_count); + events + } } #[cfg(test)] @@ -722,6 +784,7 @@ mod test { let mut ctx = MockHostContext::new(); ctx.expect_register_contract().returning(|_, _, _| ()); ctx.expect_get_events_count().returning(|_| Ok(0)); + ctx.expect_get_native_events_count().returning(|_| Ok(0)); // check if TestRef::new() is called exactly once let instance_ctx = MockTestRef::new_context(); diff --git a/examples/src/features/events.rs b/examples/src/features/events.rs index 653c4fc6..e9cf84f6 100644 --- a/examples/src/features/events.rs +++ b/examples/src/features/events.rs @@ -15,6 +15,7 @@ pub struct PartyStarted { pub block_time: u64 } +/// Native version of the above. #[odra::event] pub struct NativePartyStarted { /// Address of the caller. @@ -37,6 +38,7 @@ impl PartyContract { }); } + /// Emits the events. pub fn emit(&mut self) { self.env().emit_event(PartyStarted { caller: self.env().caller(), @@ -58,7 +60,6 @@ mod tests { fn test_party() { let test_env = odra_test::env(); let mut party_contract = PartyContract::deploy(&test_env, NoArgs); - party_contract.emit(); assert!(test_env.emitted_event( &party_contract, &PartyStarted { @@ -73,5 +74,27 @@ mod tests { block_time: 0 } )); + assert_eq!(test_env.events_count(&party_contract), 1); + assert_eq!(test_env.native_events_count(&party_contract), 1); + test_env.advance_block_time(42); + test_env.set_caller(test_env.get_account(1)); + party_contract.emit(); + + assert!(test_env.emitted_event( + &party_contract, + &PartyStarted { + caller: test_env.get_account(1), + block_time: 42 + } + )); + assert!(test_env.emitted_native_event( + &party_contract, + &NativePartyStarted { + caller: test_env.get_account(1), + block_time: 42 + } + )); + assert_eq!(test_env.events_count(&party_contract), 2); + assert_eq!(test_env.native_events_count(&party_contract), 2); } } diff --git a/odra-casper/livenet-env/src/livenet_contract_env.rs b/odra-casper/livenet-env/src/livenet_contract_env.rs index f56a4a0c..c3627cea 100644 --- a/odra-casper/livenet-env/src/livenet_contract_env.rs +++ b/odra-casper/livenet-env/src/livenet_contract_env.rs @@ -113,7 +113,7 @@ impl ContractContext for LivenetContractEnv { panic!("Cannot emit event in LivenetEnv") } - fn emit_native_event(&self, event: &Bytes) { + fn emit_native_event(&self, _event: &Bytes) { panic!("Cannot emit native event in LivenetEnv") } diff --git a/odra-casper/livenet-env/src/livenet_host.rs b/odra-casper/livenet-env/src/livenet_host.rs index e05a8dbd..1928e866 100644 --- a/odra-casper/livenet-env/src/livenet_host.rs +++ b/odra-casper/livenet-env/src/livenet_host.rs @@ -107,7 +107,11 @@ impl HostContext for LivenetHost { .map_err(|_| EventError::CouldntExtractEventData) } - fn get_native_event(&self, _contract_address: &Address, _index: u32) -> Result { + fn get_native_event( + &self, + _contract_address: &Address, + _index: u32 + ) -> Result { todo!("get_native_event not implemented for LivenetHost") } diff --git a/odra-casper/test-vm/src/casper_host.rs b/odra-casper/test-vm/src/casper_host.rs index c08e5bf3..89cdfdf1 100644 --- a/odra-casper/test-vm/src/casper_host.rs +++ b/odra-casper/test-vm/src/casper_host.rs @@ -66,7 +66,11 @@ impl HostContext for CasperHost { self.vm.borrow().get_event(contract_address, index) } - fn get_native_event(&self, contract_address: &Address, index: u32) -> Result { + fn get_native_event( + &self, + contract_address: &Address, + index: u32 + ) -> Result { if !contract_address.is_contract() { return Err(EventError::TriedToQueryEventForNonContract); } diff --git a/odra-casper/test-vm/src/vm/casper_vm.rs b/odra-casper/test-vm/src/vm/casper_vm.rs index a5df41ba..015d2b83 100644 --- a/odra-casper/test-vm/src/vm/casper_vm.rs +++ b/odra-casper/test-vm/src/vm/casper_vm.rs @@ -1,4 +1,7 @@ -use odra_core::casper_types::{AddressableEntity, AddressableEntityHash, EntityAddr, GenesisConfig, GenesisConfigBuilder, HashAddr, Package, PackageHash, ProtocolVersion}; +use odra_core::casper_types::{ + AddressableEntity, AddressableEntityHash, EntityAddr, GenesisConfig, GenesisConfigBuilder, + HashAddr, Package, PackageHash, ProtocolVersion +}; use odra_core::consts::*; use odra_core::prelude::*; use odra_core::OdraResult; @@ -24,6 +27,7 @@ use std::rc::Rc; use odra_core::casper_types::account::{Account, AccountHash}; use odra_core::casper_types::addressable_entity::NamedKeys; use odra_core::casper_types::bytesrepr::{Bytes, ToBytes}; +use odra_core::casper_types::contract_messages::MessagePayload; use odra_core::casper_types::contracts::ContractHash; use odra_core::casper_types::{ bytesrepr::FromBytes, CLTyped, GenesisAccount, PublicKey, RuntimeArgs, U512 @@ -41,7 +45,6 @@ use odra_core::{ CallDef, ContractEnv }; use odra_core::{Address, ExecutionError, OdraError, VmError}; -use odra_core::casper_types::contract_messages::MessagePayload; /// Casper virtual machine utilizing [LmdbWasmTestBuilder]. pub struct CasperVm { @@ -137,12 +140,21 @@ impl CasperVm { /// The index may be negative, in which case it is interpreted as an offset from the end of the event list. /// /// Returns [EventError::IndexOutOfBounds] if the index is out of bounds. - pub fn get_native_event(&self, contract_address: &Address, index: u32) -> Result { - let messages = self.messages.get(contract_address).ok_or(EventError::IndexOutOfBounds)?; - let message = messages.get(index as usize).ok_or(EventError::IndexOutOfBounds)?; + pub fn get_native_event( + &self, + contract_address: &Address, + index: u32 + ) -> Result { + let messages = self + .messages + .get(contract_address) + .ok_or(EventError::IndexOutOfBounds)?; + let message = messages + .get(index as usize) + .ok_or(EventError::IndexOutOfBounds)?; match message { - MessagePayload::String(_) => { Err(EventError::CouldntExtractEventData)} - MessagePayload::Bytes(b) => {Ok(b.clone())} + MessagePayload::String(_) => Err(EventError::CouldntExtractEventData), + MessagePayload::Bytes(b) => Ok(b.clone()) } } @@ -157,7 +169,10 @@ impl CasperVm { /// Gets the count of native events for the given contract address. pub fn get_native_events_count(&self, contract_address: &Address) -> Result { - let messages = self.messages.get(contract_address).ok_or(EventError::IndexOutOfBounds)?; + let messages = self + .messages + .get(contract_address) + .ok_or(EventError::IndexOutOfBounds)?; Ok(messages.len() as u32) } @@ -244,20 +259,19 @@ impl CasperVm { let messages = messages.messages(); messages.iter().for_each(|message| { let payload = message.payload().clone(); - let addressable_entity = self.get_addressable_entity_from_entity_addr(message.entity_hash()); + let addressable_entity = + self.get_addressable_entity_from_entity_addr(message.entity_hash()); let address = Address::Contract(addressable_entity.package_hash()); let contract_hash = message.entity_hash().value(); - self.messages - .entry(address.into()) - .or_insert_with(Vec::new) - .push(payload); + self.messages.entry(address).or_default().push(payload); }); } fn get_addressable_entity(&self, address: &Address) -> AddressableEntity { - let query_result = self.context.query(None, - Key::Package(address.value()), - &[]).unwrap(); + let query_result = self + .context + .query(None, Key::Package(address.value()), &[]) + .unwrap(); let entity_hash = if let StoredValue::Package(package) = query_result { package.current_entity_hash() } else { @@ -265,59 +279,63 @@ impl CasperVm { "Stored value is not an adressable entity: {:?}", query_result ); - }.unwrap(); + } + .unwrap(); self.context.get_addressable_entity(entity_hash).unwrap() } - fn get_addressable_entity_from_entity_addr(&self, entity_addr: &EntityAddr) -> AddressableEntity { - let query_result = self.context.query(None, - Key::AddressableEntity(*entity_addr), - &[]).unwrap(); - let addressable_entity = if let StoredValue::AddressableEntity(entity) = query_result { + fn get_addressable_entity_from_entity_addr( + &self, + entity_addr: &EntityAddr + ) -> AddressableEntity { + let query_result = self + .context + .query(None, Key::AddressableEntity(*entity_addr), &[]) + .unwrap(); + if let StoredValue::AddressableEntity(entity) = query_result { entity } else { panic!( "Stored value is not an adressable entity: {:?}", query_result ); - }; - addressable_entity + } } fn get_addressable_entity_hash(&self, address: &Address) -> AddressableEntityHash { - let query_result = self.context.query(None, - Key::Package(address.value()), - &[]).unwrap(); - let entity_hash = if let StoredValue::Package(package) = query_result { + let query_result = self + .context + .query(None, Key::Package(address.value()), &[]) + .unwrap(); + if let StoredValue::Package(package) = query_result { package.current_entity_hash() } else { panic!( "Stored value is not an adressable entity: {:?}", query_result ); - }.unwrap(); - - entity_hash + } + .unwrap() } fn get_messages(&self, address: &Address) { let entity = self.get_addressable_entity(address); let (topic_name, message_topic_hash) = entity - .message_topics() - .iter() - .next() - .expect("should have at least one topic"); + .message_topics() + .iter() + .next() + .expect("should have at least one topic"); let entity_hash = self.get_addressable_entity_hash(address).value(); - let q = self.context.query( - None, - Key::message_topic( - EntityAddr::SmartContract(entity_hash), - message_topic_hash.clone() - ), - &[] - ).unwrap(); + let q = self + .context + .query( + None, + Key::message_topic(EntityAddr::SmartContract(entity_hash), *message_topic_hash), + &[] + ) + .unwrap(); } /// Creates a new contract with the specified name, initialization arguments, and entry points caller. @@ -342,6 +360,7 @@ impl CasperVm { panic!("Revert: Contract deploy failed {:?}", odra_error); } else { let package_hash = self.package_hash_from_name(&package_hash_key_name); + self.collect_messages(); package_hash.into() } } @@ -502,7 +521,6 @@ impl CasperVm { } } - fn genesis_accounts( key_pairs: &BTreeMap ) -> Vec { @@ -565,7 +583,7 @@ impl CasperVm { gas_used: BTreeMap::new(), gas_report: GasReport::default(), key_pairs, - messages: Default::default(), + messages: Default::default() } } diff --git a/odra-casper/wasm-env/src/host_functions.rs b/odra-casper/wasm-env/src/host_functions.rs index c4cf09ee..4ea476ed 100644 --- a/odra-casper/wasm-env/src/host_functions.rs +++ b/odra-casper/wasm-env/src/host_functions.rs @@ -7,7 +7,11 @@ //! //! Build on top of the [casper_contract] crate. +use crate::consts; +use crate::consts::NATIVE_EVENT_TOPIC; +use casper_contract::contract_api::runtime::emit_message; use casper_contract::contract_api::storage::new_uref; +use casper_contract::ext_ffi::casper_emit_message; use casper_contract::{ contract_api::{ self, runtime, storage, @@ -20,10 +24,9 @@ use casper_contract::{ unwrap_or_revert::UnwrapOrRevert }; use core::mem::MaybeUninit; -use casper_contract::contract_api::runtime::emit_message; -use casper_contract::ext_ffi::casper_emit_message; use odra_core::casper_types::addressable_entity::NamedKeys; use odra_core::casper_types::bytesrepr::deserialize; +use odra_core::casper_types::contract_messages::{MessagePayload, MessageTopicOperation}; use odra_core::casper_types::contracts::ContractVersion; use odra_core::casper_types::system::Caller; use odra_core::casper_types::{ @@ -38,9 +41,6 @@ use odra_core::{ casper_event_standard::{self, Schema, Schemas} }; use odra_core::{prelude::*, Address, CallDef, ExecutionError}; -use odra_core::casper_types::contract_messages::{MessagePayload, MessageTopicOperation}; -use crate::consts; -use crate::consts::NATIVE_EVENT_TOPIC; lazy_static::lazy_static! { static ref STATE: URef = { @@ -88,7 +88,6 @@ pub fn install_contract( let mut mesage_topics = BTreeMap::new(); mesage_topics.insert(NATIVE_EVENT_TOPIC.to_string(), MessageTopicOperation::Add); - // Create new contract. let access_uref_key = format!("{}_access_token", package_hash_key); if is_upgradable { diff --git a/odra-vm/src/odra_vm_host.rs b/odra-vm/src/odra_vm_host.rs index f44e380b..2c2a4802 100644 --- a/odra-vm/src/odra_vm_host.rs +++ b/odra-vm/src/odra_vm_host.rs @@ -49,7 +49,11 @@ impl HostContext for OdraVmHost { self.vm.borrow().get_event(contract_address, index) } - fn get_native_event(&self, contract_address: &Address, index: u32) -> Result { + fn get_native_event( + &self, + contract_address: &Address, + index: u32 + ) -> Result { self.vm.borrow().get_native_event(contract_address, index) }