From 564415cf88d83f737f1968546cafec4206a9f6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 10 Jan 2024 15:59:40 +0100 Subject: [PATCH 01/24] Livenet skeleton --- Cargo.toml | 1 + core/src/host_context.rs | 1 + core/src/host_env.rs | 10 +++ examples/Cargo.toml | 5 ++ examples/bin/erc20_on_livenet.rs | 17 +++-- odra-casper/livenet-env/Cargo.toml | 9 +++ odra-casper/livenet-env/src/env.rs | 91 ++++++++++++++++++++++++++ odra-casper/livenet-env/src/lib.rs | 9 +++ odra-casper/test-vm/src/casper_host.rs | 4 ++ odra-vm/src/odra_vm_host.rs | 4 ++ 10 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 odra-casper/livenet-env/Cargo.toml create mode 100644 odra-casper/livenet-env/src/env.rs create mode 100644 odra-casper/livenet-env/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 7c4d9d5c..c271aa2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "odra-vm", "core", "odra-macros", + "odra-casper/livenet-env", "odra-casper/wasm-env", "odra-casper/test-vm", "odra-test", diff --git a/core/src/host_context.rs b/core/src/host_context.rs index 24f275db..e2c68e5f 100644 --- a/core/src/host_context.rs +++ b/core/src/host_context.rs @@ -8,6 +8,7 @@ use casper_types::PublicKey; pub trait HostContext { fn set_caller(&self, caller: Address); + fn set_gas(&self, gas: u64); fn caller(&self) -> Address; fn get_account(&self, index: usize) -> Address; fn balance_of(&self, address: &Address) -> U512; diff --git a/core/src/host_env.rs b/core/src/host_env.rs index 5c5e9b2e..616e1ada 100644 --- a/core/src/host_env.rs +++ b/core/src/host_env.rs @@ -235,4 +235,14 @@ impl HostEnv { let backend = self.backend.borrow(); backend.public_key(address) } + + pub fn caller(&self) -> Address { + let backend = self.backend.borrow(); + backend.caller() + } + + pub fn set_gas(&self, gas: u64) { + let backend = self.backend.borrow(); + backend.set_gas(gas) + } } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index db55965d..c18e172d 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] odra = { path = "../odra", default-features = false } odra-modules = { path = "../modules", default-features = false } +odra-casper-livenet-env = { path = "../odra-casper/livenet-env" } sha3 = { version = "0.10.6", default-features = false } [dev-dependencies] @@ -22,6 +23,10 @@ name = "odra_examples_build_schema" path = "bin/build_schema.rs" test = false +[[bin]] +name = "erc20_on_livenet" +path = "bin/erc20_on_livenet.rs" + [profile.release] codegen-units = 1 lto = true diff --git a/examples/bin/erc20_on_livenet.rs b/examples/bin/erc20_on_livenet.rs index af163739..c4c5b392 100644 --- a/examples/bin/erc20_on_livenet.rs +++ b/examples/bin/erc20_on_livenet.rs @@ -1,17 +1,20 @@ use std::str::FromStr; +use odra::{Address, U256}; +use odra_modules::erc20::Erc20Deployer; fn main() { + let env = odra_casper_livenet_env::livenet_env(); let name = String::from("Plascoin"); let symbol = String::from("PLS"); let decimals = 10u8; let initial_supply: U256 = U256::from(10_000); - let owner = client_env::caller(); + let owner = env.caller(); let recipient = "hash-2c4a6ce0da5d175e9638ec0830e01dd6cf5f4b1fbb0724f7d2d9de12b1e0f840"; let recipient = Address::from_str(recipient).unwrap(); - client_env::set_gas(110_000_000_000u64); - let mut token = Erc20Deployer::init(name, symbol, decimals, &Some(initial_supply)); + env.set_gas(110_000_000_000u64); + let mut token = Erc20Deployer::init(&env, name, symbol, decimals, Some(initial_supply)); // Uncomment to use already deployed contract. // let address = "hash-a12760e3ece51e0f31aa6d5af39660f5ec61185ad61c7551c796cca4592b9498"; @@ -20,9 +23,9 @@ fn main() { println!("Token name: {}", token.name()); - client_env::set_gas(3_000_000_000u64); - token.transfer(&recipient, &U256::from(1000)); + env.set_gas(3_000_000_000u64); + token.transfer(recipient, U256::from(1000)); - println!("Owner's balance: {:?}", token.balance_of(&owner)); - println!("Recipient's balance: {:?}", token.balance_of(&recipient)); + println!("Owner's balance: {:?}", token.balance_of(owner)); + println!("Recipient's balance: {:?}", token.balance_of(recipient)); } diff --git a/odra-casper/livenet-env/Cargo.toml b/odra-casper/livenet-env/Cargo.toml new file mode 100644 index 00000000..e554c08e --- /dev/null +++ b/odra-casper/livenet-env/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "odra-casper-livenet-env" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +odra-core = { path = "../../core" } \ No newline at end of file diff --git a/odra-casper/livenet-env/src/env.rs b/odra-casper/livenet-env/src/env.rs new file mode 100644 index 00000000..45277103 --- /dev/null +++ b/odra-casper/livenet-env/src/env.rs @@ -0,0 +1,91 @@ +use odra_core::event::EventError; +use odra_core::prelude::*; +use odra_core::{ + Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, + RuntimeArgs, U512 +}; + +pub struct LivenetEnv {} + +impl LivenetEnv { + pub fn new() -> Rc> { + Rc::new(RefCell::new(Self::new_instance())) + } + + pub fn new_instance() -> Self { + Self {} + } +} + +impl HostContext for LivenetEnv { + fn set_caller(&self, caller: Address) { + todo!() + } + + fn set_gas(&self, gas: u64) { + todo!() + } + + fn caller(&self) -> Address { + todo!() + } + + fn get_account(&self, index: usize) -> Address { + todo!() + } + + fn balance_of(&self, address: &Address) -> U512 { + todo!() + } + + fn advance_block_time(&self, time_diff: u64) { + panic!("Cannot advance block time in LivenetEnv") + } + + fn get_event(&self, contract_address: &Address, index: i32) -> Result { + todo!() + } + + fn get_events_count(&self, contract_address: &Address) -> u32 { + todo!() + } + + fn call_contract( + &self, + address: &Address, + call_def: CallDef, + _use_proxy: bool + ) -> Result { + todo!() + } + + fn new_contract( + &self, + name: &str, + init_args: Option, + entry_points_caller: Option + ) -> Address { + todo!() + } + + fn contract_env(&self) -> ContractEnv { + panic!("Cannot get contract env in LivenetEnv") + } + + fn print_gas_report(&self) { + todo!(); + println!("Gas report:"); + } + + fn last_call_gas_cost(&self) -> u64 { + todo!() + } + + fn sign_message(&self, message: &Bytes, address: &Address) -> Bytes { + todo!() + } + + fn public_key(&self, address: &Address) -> PublicKey { + todo!() + } +} diff --git a/odra-casper/livenet-env/src/lib.rs b/odra-casper/livenet-env/src/lib.rs new file mode 100644 index 00000000..3689138f --- /dev/null +++ b/odra-casper/livenet-env/src/lib.rs @@ -0,0 +1,9 @@ +pub mod env; + +use env::LivenetEnv; +use odra_core::HostEnv; + +pub fn livenet_env() -> HostEnv { + let env = LivenetEnv::new(); + HostEnv::new(env) +} diff --git a/odra-casper/test-vm/src/casper_host.rs b/odra-casper/test-vm/src/casper_host.rs index 0e7075de..e1255132 100644 --- a/odra-casper/test-vm/src/casper_host.rs +++ b/odra-casper/test-vm/src/casper_host.rs @@ -33,6 +33,10 @@ impl HostContext for CasperHost { self.vm.borrow_mut().set_caller(caller) } + fn set_gas(&self, gas: u64) { + // Set gas does nothing in this context + } + fn caller(&self) -> Address { self.vm.borrow().get_caller() } diff --git a/odra-vm/src/odra_vm_host.rs b/odra-vm/src/odra_vm_host.rs index 7c6d681f..f443ba71 100644 --- a/odra-vm/src/odra_vm_host.rs +++ b/odra-vm/src/odra_vm_host.rs @@ -16,6 +16,10 @@ impl HostContext for OdraVmHost { self.vm.borrow().set_caller(caller) } + fn set_gas(&self, gas: u64) { + // Set gas does nothing in this context + } + fn caller(&self) -> Address { *self.vm.borrow().callstack_tip().address() } From f7cdc6ae6b74930f82cc50f35b7b4ff80c9d7f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Mon, 15 Jan 2024 10:01:18 +0100 Subject: [PATCH 02/24] Livenet WIP --- .gitignore | 2 + Cargo.toml | 1 + .../src/vm => core/src}/contract_container.rs | 20 +- .../src/vm => core/src}/contract_register.rs | 7 +- core/src/lib.rs | 2 + examples/.env.sample | 1 + examples/bin/erc20_on_livenet.rs | 25 +- odra-casper/casper-client/Cargo.toml | 24 + .../casper-client/src/casper_client.rs | 495 ++++++++++++++++++ .../src/casper_node_port/account.rs | 32 ++ .../src/casper_node_port/approval.rs | 72 +++ .../src/casper_node_port/block_hash.rs | 85 +++ .../src/casper_node_port/contract_package.rs | 53 ++ .../src/casper_node_port/deploy.rs | 277 ++++++++++ .../src/casper_node_port/deploy_hash.rs | 97 ++++ .../src/casper_node_port/deploy_header.rs | 162 ++++++ .../src/casper_node_port/error.rs | 131 +++++ .../casper-client/src/casper_node_port/mod.rs | 15 + .../src/casper_node_port/rpcs.rs | 244 +++++++++ .../src/casper_node_port/utils.rs | 47 ++ odra-casper/casper-client/src/lib.rs | 4 + odra-casper/casper-client/src/log.rs | 14 + odra-casper/livenet-env/Cargo.toml | 3 +- .../livenet-env/src/client_env/callstack.rs | 16 + .../src/client_env/contract_container.rs | 54 ++ .../src/client_env/contract_register.rs | 36 ++ odra-casper/livenet-env/src/lib.rs | 6 +- .../src/{env.rs => livenet_host_env.rs} | 24 +- odra-casper/test-vm/src/vm/casper_vm.rs | 5 - odra-vm/src/vm/mod.rs | 2 - odra-vm/src/vm/odra_vm.rs | 4 +- 31 files changed, 1913 insertions(+), 47 deletions(-) rename {odra-vm/src/vm => core/src}/contract_container.rs (95%) rename {odra-vm/src/vm => core/src}/contract_register.rs (77%) create mode 100644 odra-casper/casper-client/Cargo.toml create mode 100644 odra-casper/casper-client/src/casper_client.rs create mode 100644 odra-casper/casper-client/src/casper_node_port/account.rs create mode 100644 odra-casper/casper-client/src/casper_node_port/approval.rs create mode 100644 odra-casper/casper-client/src/casper_node_port/block_hash.rs create mode 100644 odra-casper/casper-client/src/casper_node_port/contract_package.rs create mode 100644 odra-casper/casper-client/src/casper_node_port/deploy.rs create mode 100644 odra-casper/casper-client/src/casper_node_port/deploy_hash.rs create mode 100644 odra-casper/casper-client/src/casper_node_port/deploy_header.rs create mode 100644 odra-casper/casper-client/src/casper_node_port/error.rs create mode 100644 odra-casper/casper-client/src/casper_node_port/mod.rs create mode 100644 odra-casper/casper-client/src/casper_node_port/rpcs.rs create mode 100644 odra-casper/casper-client/src/casper_node_port/utils.rs create mode 100644 odra-casper/casper-client/src/lib.rs create mode 100644 odra-casper/casper-client/src/log.rs create mode 100644 odra-casper/livenet-env/src/client_env/callstack.rs create mode 100644 odra-casper/livenet-env/src/client_env/contract_container.rs create mode 100644 odra-casper/livenet-env/src/client_env/contract_register.rs rename odra-casper/livenet-env/src/{env.rs => livenet_host_env.rs} (67%) diff --git a/.gitignore b/.gitignore index 91d56f5f..0bdf4630 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Generated by Cargo # will have compiled files and executables +.* +!/.gitignore /target/ Cargo.lock .vscode/ diff --git a/Cargo.toml b/Cargo.toml index c271aa2d..802364d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "core", "odra-macros", "odra-casper/livenet-env", + "odra-casper/casper-client", "odra-casper/wasm-env", "odra-casper/test-vm", "odra-test", diff --git a/odra-vm/src/vm/contract_container.rs b/core/src/contract_container.rs similarity index 95% rename from odra-vm/src/vm/contract_container.rs rename to core/src/contract_container.rs index 6e64f89c..5bfa0463 100644 --- a/odra-vm/src/vm/contract_container.rs +++ b/core/src/contract_container.rs @@ -1,11 +1,7 @@ -use odra_core::call_def::CallDef; -use odra_core::entry_point_callback::EntryPointsCaller; -use odra_core::prelude::*; -use odra_core::HostEnv; -use odra_core::{ - casper_types::{NamedArg, RuntimeArgs}, - Bytes, OdraError, VmError -}; +use casper_types::bytesrepr::Bytes; +use casper_types::{NamedArg, RuntimeArgs}; +use crate::{CallDef, EntryPointsCaller, OdraError, VmError}; +use crate::prelude::*; #[doc(hidden)] pub type EntrypointCall = fn(String, &RuntimeArgs) -> Vec; @@ -60,14 +56,12 @@ impl ContractContainer { #[cfg(test)] mod tests { - use odra_core::prelude::{collections::*, *}; - use odra_core::{ + use crate::prelude::{collections::*, *}; + use crate::{ casper_types::{runtime_args, RuntimeArgs}, OdraError, VmError }; - use odra_core::{EntryPointsCaller, HostEnv}; - use url::Host; - + use crate::{EntryPointsCaller, HostEnv}; use super::{ContractContainer, EntrypointArgs, EntrypointCall}; #[test] diff --git a/odra-vm/src/vm/contract_register.rs b/core/src/contract_register.rs similarity index 77% rename from odra-vm/src/vm/contract_register.rs rename to core/src/contract_register.rs index 691b70e2..1a066c90 100644 --- a/odra-vm/src/vm/contract_register.rs +++ b/core/src/contract_register.rs @@ -1,7 +1,6 @@ -use odra_core::call_def::CallDef; -use odra_core::prelude::*; -use odra_core::HostEnv; -use odra_core::{casper_types::RuntimeArgs, Address, Bytes, OdraError, VmError}; +use crate::call_def::CallDef; +use crate::prelude::*; +use crate::{Address, Bytes, OdraError, VmError}; use super::contract_container::ContractContainer; diff --git a/core/src/lib.rs b/core/src/lib.rs index 34c91190..f9101818 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -29,6 +29,8 @@ mod unchecked_getter; mod unwrap_or_revert; pub mod utils; pub mod variable; +pub mod contract_container; +pub mod contract_register; pub use address::{Address, OdraAddress}; pub use call_def::CallDef; diff --git a/examples/.env.sample b/examples/.env.sample index 3bd5bf5e..98677fe1 100644 --- a/examples/.env.sample +++ b/examples/.env.sample @@ -6,4 +6,5 @@ # Chain name of the network. Known values: # - integration-test +# - casper-test # ODRA_CASPER_LIVENET_CHAIN_NAME= diff --git a/examples/bin/erc20_on_livenet.rs b/examples/bin/erc20_on_livenet.rs index c4c5b392..33ea37d1 100644 --- a/examples/bin/erc20_on_livenet.rs +++ b/examples/bin/erc20_on_livenet.rs @@ -1,6 +1,6 @@ use std::str::FromStr; use odra::{Address, U256}; -use odra_modules::erc20::Erc20Deployer; +use odra_modules::erc20::{Erc20Deployer, Erc20HostRef}; fn main() { let env = odra_casper_livenet_env::livenet_env(); @@ -10,22 +10,23 @@ fn main() { let initial_supply: U256 = U256::from(10_000); let owner = env.caller(); + dbg!(owner); let recipient = "hash-2c4a6ce0da5d175e9638ec0830e01dd6cf5f4b1fbb0724f7d2d9de12b1e0f840"; let recipient = Address::from_str(recipient).unwrap(); - env.set_gas(110_000_000_000u64); - let mut token = Erc20Deployer::init(&env, name, symbol, decimals, Some(initial_supply)); - + // env.set_gas(100_000_000_000u64); + // let mut token = Erc20Deployer::init(&env, name, symbol, decimals, Some(initial_supply)); + // // Uncomment to use already deployed contract. - // let address = "hash-a12760e3ece51e0f31aa6d5af39660f5ec61185ad61c7551c796cca4592b9498"; - // let address = Address::from_str(address).unwrap(); - // let mut token = Erc20Deployer::register(address); + let address = "hash-c0eb363adc62699b2e2bbf5d2c2df4c3216b36df2e65b66ae394e824076de2de"; + let address = Address::from_str(address).unwrap(); + let mut token = Erc20HostRef::new(address, env); println!("Token name: {}", token.name()); - env.set_gas(3_000_000_000u64); - token.transfer(recipient, U256::from(1000)); - - println!("Owner's balance: {:?}", token.balance_of(owner)); - println!("Recipient's balance: {:?}", token.balance_of(recipient)); + // env.set_gas(3_000_000_000u64); + // token.transfer(recipient, U256::from(1000)); + // + // println!("Owner's balance: {:?}", token.balance_of(owner)); + // println!("Recipient's balance: {:?}", token.balance_of(recipient)); } diff --git a/odra-casper/casper-client/Cargo.toml b/odra-casper/casper-client/Cargo.toml new file mode 100644 index 00000000..9b63fd9f --- /dev/null +++ b/odra-casper/casper-client/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "odra-casper-client" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +odra-core = { path = "../../core" } +casper-execution-engine = { workspace = true } +casper-hashing = "2.0.0" +jsonrpc-lite = "0.6.0" +serde_json = { version = "1.0", features = ["raw_value"] } +serde = "1.0" +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.0", default-features = false } +dotenv = "0.15.0" +prettycli = "0.1.1" +thiserror = "1.0.40" diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs new file mode 100644 index 00000000..881bf7ac --- /dev/null +++ b/odra-casper/casper-client/src/casper_client.rs @@ -0,0 +1,495 @@ +use std::{fs, path::PathBuf, str::from_utf8_unchecked, time::Duration}; + +use blake2::{ + digest::{Update, VariableOutput}, + VarBlake2b +}; +use casper_execution_engine::core::engine_state::ExecutableDeployItem; +use casper_hashing::Digest; +use jsonrpc_lite::JsonRpc; +use odra_core::{casper_types::{ + bytesrepr::{Bytes, FromBytes, ToBytes}, + runtime_args, ContractHash, ContractPackageHash, ExecutionResult, Key as CasperKey, + PublicKey, RuntimeArgs, SecretKey, TimeDiff, Timestamp, U512 +}, Address, CallDef}; +use serde::de::DeserializeOwned; +use serde_json::{json, Value}; + +use crate::{ + casper_node_port::{ + rpcs::{ + DictionaryIdentifier, GetDeployParams, GetDeployResult, GetDictionaryItemParams, + GetDictionaryItemResult, GetStateRootHashResult, GlobalStateIdentifier, + PutDeployResult, QueryGlobalStateParams, QueryGlobalStateResult + }, + Deploy, DeployHash + }, +}; + +use crate::log; + +pub const ENV_SECRET_KEY: &str = "ODRA_CASPER_LIVENET_SECRET_KEY_PATH"; +pub const ENV_NODE_ADDRESS: &str = "ODRA_CASPER_LIVENET_NODE_ADDRESS"; +pub const ENV_CHAIN_NAME: &str = "ODRA_CASPER_LIVENET_CHAIN_NAME"; + +fn get_env_variable(name: &str) -> String { + std::env::var(name).unwrap_or_else(|err| { + log::error(format!( + "{} must be set. Have you setup your .env file?", + name + )); + panic!("{}", err) + }) +} + +/// Client for interacting with Casper node. +pub struct CasperClient { + node_address: String, + chain_name: String, + secret_key: SecretKey, + gas: U512, +} + +impl CasperClient { + /// Creates new CasperClient. + pub fn new() -> Self { + dotenv::dotenv().ok(); + CasperClient { + node_address: get_env_variable(ENV_NODE_ADDRESS), + chain_name: get_env_variable(ENV_CHAIN_NAME), + secret_key: SecretKey::from_file(get_env_variable(ENV_SECRET_KEY)).unwrap(), + gas: U512::zero(), + } + } + + pub fn set_gas(&mut self, gas: u64) { + self.gas = gas.into(); + } + + /// Node address. + pub fn node_address_rpc(&self) -> String { + format!("{}/rpc", self.node_address) + } + + /// Chain name. + pub fn chain_name(&self) -> &str { + &self.chain_name + } + + /// Public key of the client account. + pub fn public_key(&self) -> PublicKey { + PublicKey::from(&self.secret_key) + } + + /// Address of the client account. + pub fn caller(&self) -> Address { + Address::from(self.public_key()) + } + + /// Query the node for the current state root hash. + pub 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).unwrap(); + result.state_root_hash.unwrap() + } + + /// Query the node for the deploy state. + pub fn get_deploy(&self, deploy_hash: DeployHash) -> GetDeployResult { + let params = GetDeployParams { + deploy_hash, + finalized_approvals: false + }; + + let request = json!( + { + "jsonrpc": "2.0", + "method": "info_get_deploy", + "params": params, + "id": 1, + } + ); + self.post_request(request).unwrap() + } + + /// Query the contract for the variable. + pub fn get_variable_value(&self, address: Address, key: &[u8]) -> Option { + // let key = LivenetKeyMaker::to_variable_key(key); + // SAFETY: we know the key maker creates a string of valid UTF-8 characters. + let key = unsafe { from_utf8_unchecked(key) }; + self.query_dictionary(address, key) + } + + /// Query the contract for the dictionary value. + pub fn get_dict_value( + &self, + address: Address, + seed: &[u8], + key: &[u8] + ) -> Option { + // let key = LivenetKeyMaker::to_dictionary_key(seed, key).unwrap(); + // SAFETY: we know the key maker creates a string of valid UTF-8 characters. + let key = unsafe { from_utf8_unchecked(key) }; + self.query_dictionary(address, key) + } + + /// Discover the contract address by name. + pub 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(); + let result = self.query_global_state(&CasperKey::Account(account_hash)); + let result_as_json = serde_json::to_value(result.stored_value).unwrap(); + + let named_keys = result_as_json["Account"]["named_keys"].as_array().unwrap(); + for named_key in named_keys { + if named_key["name"].as_str().unwrap() == key_name { + let key = named_key["key"] + .as_str() + .unwrap() + .replace("hash-", "contract-package-wasm"); + let contract_hash = ContractPackageHash::from_formatted_str(&key).unwrap(); + return Address::try_from(contract_hash).unwrap(); + } + } + log::error(format!( + "Contract {:?} not found in {:#?}", + key_name, result_as_json + )); + panic!("get_contract_address failed"); + } + + /// Find the contract hash by the contract package hash. + pub fn query_global_state_for_contract_hash(&self, address: Address) -> ContractHash { + let key = CasperKey::Hash(address.as_contract_package_hash().unwrap().value()); + let result = self.query_global_state(&key); + let result_as_json = serde_json::to_value(result).unwrap(); + let contract_hash: &str = result_as_json["stored_value"]["ContractPackage"]["versions"][0] + ["contract_hash"] + .as_str() + .unwrap(); + ContractHash::from_formatted_str(contract_hash).unwrap() + } + + /// Deploy the contract. + pub fn deploy_wasm(&self, wasm_file_name: &str, args: RuntimeArgs) -> Address { + log::info(format!("Deploying \"{}\".", wasm_file_name)); + let wasm_path = find_wasm_file_path(wasm_file_name); + let wasm_bytes = fs::read(wasm_path).unwrap(); + let session = ExecutableDeployItem::ModuleBytes { + module_bytes: Bytes::from(wasm_bytes), + args + }; + let deploy = self.new_deploy(session, self.gas); + let request = json!( + { + "jsonrpc": "2.0", + "method": "account_put_deploy", + "params": { + "deploy": deploy + }, + "id": 1, + } + ); + + let response: PutDeployResult = self.post_request(request).unwrap(); + let deploy_hash = response.deploy_hash; + self.wait_for_deploy_hash(deploy_hash); + + let address = self.get_contract_address(wasm_file_name.strip_suffix(".wasm").unwrap()); + log::info(format!("Contract {:?} deployed.", &address.to_string())); + address + } + + /// Deploy the entrypoint call. + pub fn deploy_entrypoint_call( + &self, + addr: Address, + call_def: CallDef + ) { + log::info(format!( + "Calling {:?} with entrypoint \"{}\".", + addr.to_string(), + call_def.entry_point + )); + let session = ExecutableDeployItem::StoredVersionedContractByHash { + hash: *addr.as_contract_package_hash().unwrap(), + version: None, + entry_point: call_def.entry_point, + args: call_def.args + }; + let deploy = self.new_deploy(session, self.gas); + let request = json!( + { + "jsonrpc": "2.0", + "method": "account_put_deploy", + "params": { + "deploy": deploy + }, + "id": 1, + } + ); + let response: PutDeployResult = self.post_request(request).unwrap(); + let deploy_hash = response.deploy_hash; + self.wait_for_deploy_hash(deploy_hash); + } + + fn query_global_state(&self, key: &CasperKey) -> QueryGlobalStateResult { + let state_root_hash = self.get_state_root_hash(); + 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).unwrap() + } + + fn query_dictionary(&self, address: Address, key: &str) -> Option { + let state_root_hash = self.get_state_root_hash(); + let contract_hash = self.query_global_state_for_contract_hash(address); + 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 request = json!( + { + "jsonrpc": "2.0", + "method": "state_get_dictionary_item", + "params": params, + "id": 1, + } + ); + + let result: Option = self.post_request(request); + result.map(|result| { + let result_as_json = serde_json::to_value(result).unwrap(); + let result = result_as_json["stored_value"]["CLValue"]["bytes"] + .as_str() + .unwrap(); + let bytes = hex::decode(result).unwrap(); + let (value, _) = FromBytes::from_bytes(&bytes).unwrap(); + value + }) + } + + fn wait_for_deploy_hash(&self, deploy_hash: DeployHash) { + 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 + )); + std::thread::sleep(time_diff); + let result: GetDeployResult = self.get_deploy(deploy_hash); + if !result.execution_results.is_empty() { + final_result = result; + break; + } + } + + match &final_result.execution_results[0].result { + ExecutionResult::Failure { + effect: _, + transfers: _, + cost: _, + error_message + } => { + log::error(format!( + "Deploy {:?} failed with error: {:?}.", + deploy_hash_str, error_message + )); + panic!("Deploy failed"); + } + ExecutionResult::Success { + effect: _, + transfers: _, + cost: _ + } => { + log::info(format!( + "Deploy {:?} successfully executed.", + deploy_hash_str + )); + } + } + } + + fn new_deploy(&self, session: ExecutableDeployItem, gas: U512) -> Deploy { + let timestamp = Timestamp::now(); + let ttl = TimeDiff::from_seconds(1000); + let gas_price = 1; + let dependencies = vec![]; + let chain_name = String::from(self.chain_name()); + let payment = ExecutableDeployItem::ModuleBytes { + module_bytes: Default::default(), + args: runtime_args! { + "amount" => gas + } + }; + + Deploy::new( + timestamp, + ttl, + gas_price, + dependencies, + chain_name, + payment, + session, + &self.secret_key, + Some(self.public_key()) + ) + } + + fn post_request(&self, request: Value) -> Option { + let client = reqwest::blocking::Client::new(); + let response = client + .post(self.node_address_rpc()) + .json(&request) + .send() + .unwrap(); + let response: JsonRpc = response.json().unwrap(); + response + .get_result() + .map(|result| serde_json::from_value(result.clone()).unwrap()) + } +} + +impl Default for CasperClient { + fn default() -> Self { + Self::new() + } +} + +/// 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"); +} + +pub struct LivenetKeyMaker; + +impl LivenetKeyMaker { + fn blake2b(preimage: &[u8]) -> [u8; 32] { + let mut result = [0; 32]; + let mut hasher = VarBlake2b::new(32).expect("should create hasher"); + + hasher.update(preimage); + hasher.finalize_variable(|slice| { + result.copy_from_slice(slice); + }); + result + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use casper_hashing::Digest; + use odra_core::{ + casper_types::bytesrepr::{FromBytes, ToBytes}, + Address, U256 + }; + + use crate::casper_node_port::DeployHash; + + use super::CasperClient; + + const CONTRACT_PACKAGE_HASH: &str = + "hash-40dd2fef4e994d2b0d3d415ce515446d7a1e389d2e6fc7c51319a70acf6f42d0"; + const ACCOUNT_HASH: &str = + "hash-2c4a6ce0da5d175e9638ec0830e01dd6cf5f4b1fbb0724f7d2d9de12b1e0f840"; + + #[test] + #[ignore] + pub fn client_works() { + let contract_hash = Address::from_str(CONTRACT_PACKAGE_HASH).unwrap(); + let result: Option = + CasperClient::new().get_variable_value(contract_hash, b"name_contract"); + assert_eq!(result.unwrap().as_str(), "Plascoin"); + + let account = Address::from_str(ACCOUNT_HASH).unwrap(); + let balance: Option = + CasperClient::new().get_dict_value(contract_hash, b"balances_contract", &account); + assert!(balance.is_some()); + } + + #[test] + #[ignore] + pub fn state_root_hash() { + CasperClient::new().get_state_root_hash(); + } + + #[test] + #[ignore] + pub fn get_deploy() { + let hash = DeployHash::new( + Digest::from_hex("98de69b3515fbefcd416e09b57642f721db354509c6d298f5f7cfa8b42714dba") + .unwrap() + ); + let result = CasperClient::new().get_deploy(hash); + assert_eq!(result.deploy.hash(), &hash); + CasperClient::new().wait_for_deploy_hash(hash); + } + + #[test] + #[ignore] + pub fn query_global_state_for_contract() { + let addr = Address::from_str(CONTRACT_PACKAGE_HASH).unwrap(); + let _result: Option = CasperClient::new().query_dictionary(addr, "name_contract"); + } + + #[test] + #[ignore] + pub fn discover_contract_address() { + let address = CasperClient::new().get_contract_address("erc20"); + let contract_hash = Address::from_str(CONTRACT_PACKAGE_HASH).unwrap(); + assert_eq!(address, contract_hash); + } + + #[test] + #[ignore] + pub fn parsing() { + let name = String::from("DragonsNFT_2"); + let bytes = ToBytes::to_bytes(&name).unwrap(); + assert_eq!( + hex::encode(bytes.clone()), + "0c000000447261676f6e734e46545f32" + ); + let (name2, _): (String, _) = FromBytes::from_bytes(&bytes).unwrap(); + assert_eq!(name, name2); + } +} diff --git a/odra-casper/casper-client/src/casper_node_port/account.rs b/odra-casper/casper-client/src/casper_node_port/account.rs new file mode 100644 index 00000000..56c438ae --- /dev/null +++ b/odra-casper/casper-client/src/casper_node_port/account.rs @@ -0,0 +1,32 @@ +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 +} + +#[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/casper-client/src/casper_node_port/approval.rs b/odra-casper/casper-client/src/casper_node_port/approval.rs new file mode 100644 index 00000000..0a915cbe --- /dev/null +++ b/odra-casper/casper-client/src/casper_node_port/approval.rs @@ -0,0 +1,72 @@ +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/casper-client/src/casper_node_port/block_hash.rs b/odra-casper/casper-client/src/casper_node_port/block_hash.rs new file mode 100644 index 00000000..1870a93d --- /dev/null +++ b/odra-casper/casper-client/src/casper_node_port/block_hash.rs @@ -0,0 +1,85 @@ +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/casper-client/src/casper_node_port/contract_package.rs b/odra-casper/casper-client/src/casper_node_port/contract_package.rs new file mode 100644 index 00000000..d317cd17 --- /dev/null +++ b/odra-casper/casper-client/src/casper_node_port/contract_package.rs @@ -0,0 +1,53 @@ +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/casper-client/src/casper_node_port/deploy.rs b/odra-casper/casper-client/src/casper_node_port/deploy.rs new file mode 100644 index 00000000..3d531b1f --- /dev/null +++ b/odra-casper/casper-client/src/casper_node_port/deploy.rs @@ -0,0 +1,277 @@ +#![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/casper-client/src/casper_node_port/deploy_hash.rs b/odra-casper/casper-client/src/casper_node_port/deploy_hash.rs new file mode 100644 index 00000000..63275627 --- /dev/null +++ b/odra-casper/casper-client/src/casper_node_port/deploy_hash.rs @@ -0,0 +1,97 @@ +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/casper-client/src/casper_node_port/deploy_header.rs b/odra-casper/casper-client/src/casper_node_port/deploy_header.rs new file mode 100644 index 00000000..f4f91b71 --- /dev/null +++ b/odra-casper/casper-client/src/casper_node_port/deploy_header.rs @@ -0,0 +1,162 @@ +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`]. +#[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/casper-client/src/casper_node_port/error.rs b/odra-casper/casper-client/src/casper_node_port/error.rs new file mode 100644 index 00000000..c8435721 --- /dev/null +++ b/odra-casper/casper-client/src/casper_node_port/error.rs @@ -0,0 +1,131 @@ +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/casper-client/src/casper_node_port/mod.rs b/odra-casper/casper-client/src/casper_node_port/mod.rs new file mode 100644 index 00000000..7ae905ef --- /dev/null +++ b/odra-casper/casper-client/src/casper_node_port/mod.rs @@ -0,0 +1,15 @@ +//! 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_package; +pub mod deploy; +pub mod deploy_hash; +pub mod deploy_header; +pub mod error; +pub mod rpcs; +pub mod utils; + +pub use deploy::Deploy; +pub use deploy_hash::DeployHash; diff --git a/odra-casper/casper-client/src/casper_node_port/rpcs.rs b/odra-casper/casper-client/src/casper_node_port/rpcs.rs new file mode 100644 index 00000000..5ef0f883 --- /dev/null +++ b/odra-casper/casper-client/src/casper_node_port/rpcs.rs @@ -0,0 +1,244 @@ +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) +} diff --git a/odra-casper/casper-client/src/casper_node_port/utils.rs b/odra-casper/casper-client/src/casper_node_port/utils.rs new file mode 100644 index 00000000..35a9e2b0 --- /dev/null +++ b/odra-casper/casper-client/src/casper_node_port/utils.rs @@ -0,0 +1,47 @@ +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/casper-client/src/lib.rs b/odra-casper/casper-client/src/lib.rs new file mode 100644 index 00000000..ff523b58 --- /dev/null +++ b/odra-casper/casper-client/src/lib.rs @@ -0,0 +1,4 @@ +#![feature(once_cell)] +mod casper_node_port; +pub mod casper_client; +pub mod log; \ No newline at end of file diff --git a/odra-casper/casper-client/src/log.rs b/odra-casper/casper-client/src/log.rs new file mode 100644 index 00000000..249cbd19 --- /dev/null +++ b/odra-casper/casper-client/src/log.rs @@ -0,0 +1,14 @@ + /// Info message. + pub fn info>(message: T) { + prettycli::info(message.as_ref()); + } + + /// Error message. + pub fn error>(message: T) { + prettycli::error(message.as_ref()); + } + + /// Wait message. + pub fn wait>(message: T) { + prettycli::wait(message.as_ref()); + } diff --git a/odra-casper/livenet-env/Cargo.toml b/odra-casper/livenet-env/Cargo.toml index e554c08e..eaf501f8 100644 --- a/odra-casper/livenet-env/Cargo.toml +++ b/odra-casper/livenet-env/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -odra-core = { path = "../../core" } \ No newline at end of file +odra-core = { path = "../../core" } +odra-casper-client = { path = "../casper-client" } diff --git a/odra-casper/livenet-env/src/client_env/callstack.rs b/odra-casper/livenet-env/src/client_env/callstack.rs new file mode 100644 index 00000000..5be655a3 --- /dev/null +++ b/odra-casper/livenet-env/src/client_env/callstack.rs @@ -0,0 +1,16 @@ +#[derive(Clone, Default)] +pub struct Callstack(Vec
); + +impl Callstack { + pub fn pop(&mut self) -> Option
{ + self.0.pop() + } + + pub fn push(&mut self, element: Address) { + self.0.push(element); + } + + pub fn current(&self) -> Address { + *self.0.last().unwrap() + } +} diff --git a/odra-casper/livenet-env/src/client_env/contract_container.rs b/odra-casper/livenet-env/src/client_env/contract_container.rs new file mode 100644 index 00000000..c1490c1a --- /dev/null +++ b/odra-casper/livenet-env/src/client_env/contract_container.rs @@ -0,0 +1,54 @@ +use std::collections::BTreeMap; + +use crate::{EntrypointArgs, EntrypointCall}; + +#[derive(Clone)] +pub struct ContractContainer { + address: Address, + entrypoints: BTreeMap +} + +impl ContractContainer { + pub fn new( + address: Address, + entrypoints: BTreeMap + ) -> Self { + Self { + address, + entrypoints + } + } + + pub fn call(&self, entrypoint: String, args: RuntimeArgs) -> Result, OdraError> { + match self.entrypoints.get(&entrypoint) { + Some((ep_args, call)) => { + self.validate_args(ep_args, &args)?; + Ok(call(String::new(), &args)) + } + None => Err(OdraError::VmError(VmError::NoSuchMethod(entrypoint))) + } + } + + pub fn address(&self) -> Address { + self.address + } + + fn validate_args(&self, args: &[String], input_args: &RuntimeArgs) -> Result<(), OdraError> { + let named_args = input_args + .named_args() + .map(|arg| arg.name().to_owned()) + .collect::>(); + + if args + .iter() + .filter(|arg| !named_args.contains(arg)) + .map(|arg| arg.to_owned()) + .next() + .is_none() + { + Ok(()) + } else { + Err(OdraError::VmError(VmError::MissingArg)) + } + } +} diff --git a/odra-casper/livenet-env/src/client_env/contract_register.rs b/odra-casper/livenet-env/src/client_env/contract_register.rs new file mode 100644 index 00000000..ccbae510 --- /dev/null +++ b/odra-casper/livenet-env/src/client_env/contract_register.rs @@ -0,0 +1,36 @@ + +use std::collections::BTreeMap; + +use super::contract_container::ContractContainer; + +#[derive(Default)] +pub struct ContractRegister { + contracts: BTreeMap +} + +impl ContractRegister { + pub fn add(&mut self, container: ContractContainer) { + self.contracts.insert(container.address(), container); + } + + pub fn call( + &self, + addr: &Address, + entrypoint: String, + args: &RuntimeArgs + ) -> Result, OdraError> { + self.internal_call(addr, |container| container.call(entrypoint, args.clone())) + } + + fn internal_call Result, OdraError>>( + &self, + addr: &Address, + call_fn: F + ) -> Result, OdraError> { + let contract = self.contracts.get(addr); + match contract { + Some(container) => call_fn(container), + None => Err(OdraError::VmError(VmError::InvalidContractAddress)) + } + } +} diff --git a/odra-casper/livenet-env/src/lib.rs b/odra-casper/livenet-env/src/lib.rs index 3689138f..e23fd388 100644 --- a/odra-casper/livenet-env/src/lib.rs +++ b/odra-casper/livenet-env/src/lib.rs @@ -1,6 +1,6 @@ -pub mod env; - -use env::LivenetEnv; +#![feature(once_cell)] +pub mod livenet_host_env; +use livenet_host_env::LivenetEnv; use odra_core::HostEnv; pub fn livenet_env() -> HostEnv { diff --git a/odra-casper/livenet-env/src/env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs similarity index 67% rename from odra-casper/livenet-env/src/env.rs rename to odra-casper/livenet-env/src/livenet_host_env.rs index 45277103..44bad701 100644 --- a/odra-casper/livenet-env/src/env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -1,11 +1,18 @@ +use std::fmt::format; +use std::sync::{Arc, RwLock}; +use odra_casper_client::casper_client::CasperClient; use odra_core::event::EventError; use odra_core::prelude::*; use odra_core::{ Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, RuntimeArgs, U512 }; +use odra_core::contract_register::ContractRegister; -pub struct LivenetEnv {} +pub struct LivenetEnv { + casper_client: Rc>, + contract_register: Arc> +} impl LivenetEnv { pub fn new() -> Rc> { @@ -13,7 +20,7 @@ impl LivenetEnv { } pub fn new_instance() -> Self { - Self {} + Self { casper_client: Default::default(), contract_register: Default::default() } } } @@ -23,11 +30,11 @@ impl HostContext for LivenetEnv { } fn set_gas(&self, gas: u64) { - todo!() + self.casper_client.borrow_mut().set_gas(gas); } fn caller(&self) -> Address { - todo!() + self.casper_client.borrow().caller() } fn get_account(&self, index: usize) -> Address { @@ -65,7 +72,14 @@ impl HostContext for LivenetEnv { init_args: Option, entry_points_caller: Option ) -> Address { - todo!() + let mut args = match init_args { + None => RuntimeArgs::new(), + Some(args) => args, + }; + args.insert("odra_cfg_is_upgradable", false).unwrap(); + args.insert("odra_cfg_allow_key_override", false).unwrap(); + args.insert("odra_cfg_package_hash_key_name", format!("{}_package_hash", name)).unwrap(); + self.casper_client.borrow_mut().deploy_wasm(name, args) } fn contract_env(&self) -> ContractEnv { diff --git a/odra-casper/test-vm/src/vm/casper_vm.rs b/odra-casper/test-vm/src/vm/casper_vm.rs index 5c510ec1..38f7d11a 100644 --- a/odra-casper/test-vm/src/vm/casper_vm.rs +++ b/odra-casper/test-vm/src/vm/casper_vm.rs @@ -275,11 +275,6 @@ impl CasperVm { args.insert(ALLOW_KEY_OVERRIDE_ARG, true).unwrap(); args.insert(IS_UPGRADABLE_ARG, false).unwrap(); - if init_args.is_some() { - args.insert(CONSTRUCTOR_NAME_ARG, CONSTRUCTOR_NAME.to_string()) - .unwrap(); - }; - self.deploy_contract(&wasm_path, &args); let contract_package_hash = self.contract_package_hash_from_name(&package_hash_key_name); contract_package_hash.try_into().unwrap() diff --git a/odra-vm/src/vm/mod.rs b/odra-vm/src/vm/mod.rs index dc2fc7ad..ba05d7ea 100644 --- a/odra-vm/src/vm/mod.rs +++ b/odra-vm/src/vm/mod.rs @@ -3,8 +3,6 @@ #![allow(unused_variables)] mod balance; mod callstack; -mod contract_container; -mod contract_register; mod odra_vm; mod odra_vm_state; mod storage; diff --git a/odra-vm/src/vm/odra_vm.rs b/odra-vm/src/vm/odra_vm.rs index aec14b84..6faca2f9 100644 --- a/odra-vm/src/vm/odra_vm.rs +++ b/odra-vm/src/vm/odra_vm.rs @@ -15,10 +15,10 @@ use odra_core::{ Address, Bytes, ExecutionError, PublicKey, SecretKey }; use odra_core::{OdraError, VmError}; +use odra_core::contract_container::ContractContainer; +use odra_core::contract_register::ContractRegister; use super::callstack::{CallstackElement, Entrypoint}; -use super::contract_container::ContractContainer; -use super::contract_register::ContractRegister; use super::odra_vm_state::OdraVmState; #[derive(Default)] From 7ab52d9a1c671644ea08744c3373e694309218e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Tue, 16 Jan 2024 14:16:51 +0100 Subject: [PATCH 03/24] Wip --- core/Cargo.toml | 4 +- examples/Cargo.toml | 4 - examples/bin/erc20_on_livenet.rs | 12 ++- odra-casper/casper-client/Cargo.toml | 1 + .../casper-client/src/casper_client.rs | 96 +++++++++++++++++-- .../livenet-env/src/livenet_host_env.rs | 18 +++- 6 files changed, 109 insertions(+), 26 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index ef9079b4..1000fd5e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -4,5 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -casper-types = "3.0.0" -casper-event-standard = "0.4.0" \ No newline at end of file +casper-types = { workspace = true } +casper-event-standard = { workspace = true } \ No newline at end of file diff --git a/examples/Cargo.toml b/examples/Cargo.toml index c18e172d..627122ae 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -33,7 +33,3 @@ lto = true [profile.dev.package."*"] opt-level = 3 -#[[bin]] -#name = "erc20-on-livenet" -#path = "bin/erc20_on_livenet.rs" -#required-features = ["casper-livenet"] diff --git a/examples/bin/erc20_on_livenet.rs b/examples/bin/erc20_on_livenet.rs index 33ea37d1..f309c817 100644 --- a/examples/bin/erc20_on_livenet.rs +++ b/examples/bin/erc20_on_livenet.rs @@ -10,19 +10,21 @@ fn main() { let initial_supply: U256 = U256::from(10_000); let owner = env.caller(); - dbg!(owner); + // dbg!(owner); let recipient = "hash-2c4a6ce0da5d175e9638ec0830e01dd6cf5f4b1fbb0724f7d2d9de12b1e0f840"; let recipient = Address::from_str(recipient).unwrap(); // env.set_gas(100_000_000_000u64); // let mut token = Erc20Deployer::init(&env, name, symbol, decimals, Some(initial_supply)); - // + // Uncomment to use already deployed contract. - let address = "hash-c0eb363adc62699b2e2bbf5d2c2df4c3216b36df2e65b66ae394e824076de2de"; + let address = "hash-d26fcbd2106e37be975d2045c580334a6d7b9d0a241c2358a4db970dfd516945"; let address = Address::from_str(address).unwrap(); - let mut token = Erc20HostRef::new(address, env); + let mut token = Erc20HostRef::new(address, env.clone()); + env.set_gas(1_000_000_000u64); + // token.approve(owner, U256::from(1000)); - println!("Token name: {}", token.name()); + println!("Token name: {}", token.symbol()); // env.set_gas(3_000_000_000u64); // token.transfer(recipient, U256::from(1000)); diff --git a/odra-casper/casper-client/Cargo.toml b/odra-casper/casper-client/Cargo.toml index 9b63fd9f..92481c20 100644 --- a/odra-casper/casper-client/Cargo.toml +++ b/odra-casper/casper-client/Cargo.toml @@ -22,3 +22,4 @@ blake2 = { version = "0.9.0", default-features = false } dotenv = "0.15.0" prettycli = "0.1.1" thiserror = "1.0.40" +bytes = "1.5.0" diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index 881bf7ac..0ca9348b 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -7,13 +7,14 @@ use blake2::{ use casper_execution_engine::core::engine_state::ExecutableDeployItem; use casper_hashing::Digest; use jsonrpc_lite::JsonRpc; -use odra_core::{casper_types::{ +use odra_core::{consts::*, casper_types::{ bytesrepr::{Bytes, FromBytes, ToBytes}, runtime_args, ContractHash, ContractPackageHash, ExecutionResult, Key as CasperKey, - PublicKey, RuntimeArgs, SecretKey, TimeDiff, Timestamp, U512 -}, Address, CallDef}; + PublicKey, RuntimeArgs, SecretKey, TimeDiff, Timestamp, U512, +}, Address, CallDef, OdraError, CLType}; use serde::de::DeserializeOwned; use serde_json::{json, Value}; +use odra_core::CLType::List; use crate::{ casper_node_port::{ @@ -25,6 +26,8 @@ use crate::{ Deploy, DeployHash }, }; +use crate::casper_node_port::rpcs::DictionaryIdentifier::Dictionary; +use crate::casper_node_port::rpcs::StoredValue::CLValue; use crate::log; @@ -118,20 +121,20 @@ impl CasperClient { } /// Query the contract for the variable. - pub fn get_variable_value(&self, address: Address, key: &[u8]) -> Option { + pub fn get_variable_value(&self, address: Address, key: &str) -> Option { // let key = LivenetKeyMaker::to_variable_key(key); // SAFETY: we know the key maker creates a string of valid UTF-8 characters. - let key = unsafe { from_utf8_unchecked(key) }; + // let key = unsafe { from_utf8_unchecked(key) }; self.query_dictionary(address, key) } /// Query the contract for the dictionary value. - pub fn get_dict_value( + pub fn get_dict_value( &self, address: Address, seed: &[u8], key: &[u8] - ) -> Option { + ) -> Option { // let key = LivenetKeyMaker::to_dictionary_key(seed, key).unwrap(); // SAFETY: we know the key maker creates a string of valid UTF-8 characters. let key = unsafe { from_utf8_unchecked(key) }; @@ -205,6 +208,61 @@ impl CasperClient { address } + pub fn deploy_entrypoint_call_with_proxy( + &self, + addr: Address, + call_def: CallDef + ) -> Bytes { + log::info(format!( + "Calling {:?} with entrypoint \"{}\".", + addr.to_string(), + call_def.entry_point + )); + // let session = ExecutableDeployItem::StoredVersionedContractByHash { + // hash: *addr.as_contract_package_hash().unwrap(), + // version: None, + // entry_point: call_def.entry_point, + // args: call_def.args + // }; + + 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()), + ATTACHED_VALUE_ARG => call_def.amount, + AMOUNT_ARG => call_def.amount, + }; + + let session = ExecutableDeployItem::ModuleBytes { + module_bytes: include_bytes!("../../test-vm/resources/proxy_caller_with_return.wasm").to_vec().into(), + args, + }; + + let deploy = self.new_deploy(session, self.gas); + let request = json!( + { + "jsonrpc": "2.0", + "method": "account_put_deploy", + "params": { + "deploy": deploy + }, + "id": 1, + } + ); + let response: PutDeployResult = self.post_request(request).unwrap(); + let deploy_hash = response.deploy_hash; + let result = self.wait_for_deploy_hash(deploy_hash); + + let r = self.query_result(&CasperKey::Account(self.public_key().to_account_hash())); + let result_as_json = serde_json::to_value(r).unwrap(); + let result = result_as_json["stored_value"]["CLValue"]["bytes"] + .as_str() + .unwrap(); + let bytes = hex::decode(result).unwrap(); + let (value, _) = FromBytes::from_bytes(&bytes).unwrap(); + value + } + /// Deploy the entrypoint call. pub fn deploy_entrypoint_call( &self, @@ -256,7 +314,25 @@ impl CasperClient { self.post_request(request).unwrap() } - fn query_dictionary(&self, address: Address, key: &str) -> Option { + fn query_result(&self, key: &CasperKey) -> Option { + let state_root_hash = self.get_state_root_hash(); + let params = QueryGlobalStateParams { + state_identifier: GlobalStateIdentifier::StateRootHash(state_root_hash), + key: key.to_formatted_string(), + path: vec![RESULT_KEY.to_string()] + }; + let request = json!( + { + "jsonrpc": "2.0", + "method": "query_global_state", + "params": params, + "id": 1, + } + ); + self.post_request(request).unwrap() + } + + fn query_dictionary(&self, address: Address, key: &str) -> Option { let state_root_hash = self.get_state_root_hash(); let contract_hash = self.query_global_state_for_contract_hash(address); let contract_hash = contract_hash @@ -279,7 +355,6 @@ impl CasperClient { "id": 1, } ); - let result: Option = self.post_request(request); result.map(|result| { let result_as_json = serde_json::to_value(result).unwrap(); @@ -292,7 +367,7 @@ impl CasperClient { }) } - fn wait_for_deploy_hash(&self, deploy_hash: DeployHash) { + fn wait_for_deploy_hash(&self, deploy_hash: DeployHash) -> ExecutionResult { let deploy_hash_str = format!("{:?}", deploy_hash.inner()); let time_diff = Duration::from_secs(15); let final_result; @@ -332,6 +407,7 @@ impl CasperClient { "Deploy {:?} successfully executed.", deploy_hash_str )); + final_result.execution_results[0].result.clone() } } } diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index 44bad701..bfa48fd9 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -61,9 +61,15 @@ impl HostContext for LivenetEnv { &self, address: &Address, call_def: CallDef, - _use_proxy: bool + use_proxy: bool ) -> Result { - todo!() + match use_proxy { + true => Ok(self.casper_client.borrow_mut().deploy_entrypoint_call_with_proxy(*address, call_def)), + false => { + self.casper_client.borrow_mut().deploy_entrypoint_call(*address, call_def); + return Ok(Default::default()); + }, + } } fn new_contract( @@ -76,6 +82,7 @@ impl HostContext for LivenetEnv { None => RuntimeArgs::new(), Some(args) => args, }; + // todo: move this up the stack args.insert("odra_cfg_is_upgradable", false).unwrap(); args.insert("odra_cfg_allow_key_override", false).unwrap(); args.insert("odra_cfg_package_hash_key_name", format!("{}_package_hash", name)).unwrap(); @@ -87,12 +94,13 @@ impl HostContext for LivenetEnv { } fn print_gas_report(&self) { - todo!(); - println!("Gas report:"); + // Todo: implement + println!("Gas report is unavailable for livenet"); } fn last_call_gas_cost(&self) -> u64 { - todo!() + // Todo: implement + 0 } fn sign_message(&self, message: &Bytes, address: &Address) -> Bytes { From ccaa3b5f9011bebe77519852ab10b52537af6fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Tue, 16 Jan 2024 15:41:49 +0100 Subject: [PATCH 04/24] add is_mut property to CallDef --- core/src/call_def.rs | 12 +++++++++--- odra-macros/src/ast/external_contract_item.rs | 2 ++ odra-macros/src/ast/host_ref_item.rs | 11 +++++++++++ odra-macros/src/ast/ref_item.rs | 12 ++++++++++++ odra-macros/src/ast/ref_utils.rs | 6 ++++-- odra-macros/src/ast/test_parts.rs | 6 ++++++ 6 files changed, 44 insertions(+), 5 deletions(-) diff --git a/core/src/call_def.rs b/core/src/call_def.rs index 017547ef..f691a811 100644 --- a/core/src/call_def.rs +++ b/core/src/call_def.rs @@ -6,15 +6,17 @@ use casper_types::{CLTyped, RuntimeArgs, U512}; pub struct CallDef { pub entry_point: String, pub args: RuntimeArgs, - pub amount: U512 + pub amount: U512, + is_mut: bool, } impl CallDef { - pub fn new(method: String, args: RuntimeArgs) -> Self { + pub fn new(method: String, is_mut: bool, args: RuntimeArgs) -> Self { CallDef { entry_point: method, args, - amount: U512::zero() + amount: U512::zero(), + is_mut, } } @@ -38,4 +40,8 @@ impl CallDef { pub fn attached_value(&self) -> U512 { self.amount } + + pub fn is_mut(&self) -> bool { + self.is_mut + } } diff --git a/odra-macros/src/ast/external_contract_item.rs b/odra-macros/src/ast/external_contract_item.rs index fe7312a5..3d5dcb54 100644 --- a/odra-macros/src/ast/external_contract_item.rs +++ b/odra-macros/src/ast/external_contract_item.rs @@ -66,6 +66,7 @@ mod test { self.address, odra::CallDef::new( String::from("balance_of"), + false, { let mut named_args = odra::RuntimeArgs::new(); let _ = named_args.insert("owner", owner); @@ -129,6 +130,7 @@ mod test { self.address, odra::CallDef::new( String::from("balance_of"), + false, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { diff --git a/odra-macros/src/ast/host_ref_item.rs b/odra-macros/src/ast/host_ref_item.rs index 0b768edb..dd31d6ea 100644 --- a/odra-macros/src/ast/host_ref_item.rs +++ b/odra-macros/src/ast/host_ref_item.rs @@ -277,6 +277,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("total_supply"), + false, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -298,6 +299,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("pay_to_mint"), + true, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -324,6 +326,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("approve"), + true, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -347,6 +350,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("airdrop"), + false, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -420,6 +424,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("total_supply"), + false, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -441,6 +446,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("pay_to_mint"), + true, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -513,6 +519,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("total_supply"), + false, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -534,6 +541,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("get_owner"), + false, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -556,6 +564,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("set_owner"), + true, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -579,6 +588,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("name"), + false, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -601,6 +611,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("symbol"), + false, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { diff --git a/odra-macros/src/ast/ref_item.rs b/odra-macros/src/ast/ref_item.rs index eb581cb2..58088700 100644 --- a/odra-macros/src/ast/ref_item.rs +++ b/odra-macros/src/ast/ref_item.rs @@ -151,6 +151,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("init"), + true, { let mut named_args = odra::RuntimeArgs::new(); let _ = named_args.insert("total_supply", total_supply); @@ -165,6 +166,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("total_supply"), + false, { let mut named_args = odra::RuntimeArgs::new(); named_args @@ -179,6 +181,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("pay_to_mint"), + true, { let mut named_args = odra::RuntimeArgs::new(); named_args @@ -193,6 +196,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("approve"), + true, { let mut named_args = odra::RuntimeArgs::new(); let _ = named_args.insert("to", to); @@ -209,6 +213,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("airdrop"), + false, { let mut named_args = odra::RuntimeArgs::new(); let _ = named_args.insert("to", to); @@ -247,6 +252,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("total_supply"), + false, { let mut named_args = odra::RuntimeArgs::new(); named_args @@ -261,6 +267,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("pay_to_mint"), + true, { let mut named_args = odra::RuntimeArgs::new(); named_args @@ -297,6 +304,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("total_supply"), + false, { let mut named_args = odra::RuntimeArgs::new(); named_args @@ -311,6 +319,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("get_owner"), + false, { let mut named_args = odra::RuntimeArgs::new(); named_args @@ -325,6 +334,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("set_owner"), + true, { let mut named_args = odra::RuntimeArgs::new(); let _ = named_args.insert("new_owner", new_owner); @@ -340,6 +350,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("name"), + false, { let mut named_args = odra::RuntimeArgs::new(); named_args @@ -354,6 +365,7 @@ mod ref_item_tests { self.address, odra::CallDef::new( String::from("symbol"), + false, { let mut named_args = odra::RuntimeArgs::new(); named_args diff --git a/odra-macros/src/ast/ref_utils.rs b/odra-macros/src/ast/ref_utils.rs index b245a983..de3a9c69 100644 --- a/odra-macros/src/ast/ref_utils.rs +++ b/odra-macros/src/ast/ref_utils.rs @@ -49,16 +49,18 @@ fn call_def(fun: &FnIR) -> syn::Expr { let ty_call_def = utils::ty::call_def(); let fun_name_str = fun.name_str(); let args_block = fn_utils::runtime_args_block(fun, insert_arg_stmt); - syn::parse_quote!(#ty_call_def::new(String::from(#fun_name_str), #args_block)) + let is_mut = fun.is_mut(); + syn::parse_quote!(#ty_call_def::new(String::from(#fun_name_str), #is_mut, #args_block)) } fn call_def_with_amount(fun: &FnIR) -> syn::Expr { let ty_call_def = utils::ty::call_def(); let fun_name_str = fun.name_str(); let args_block = runtime_args_with_amount_block(fun, insert_arg_stmt); + let is_mut = fun.is_mut(); let attached_value = utils::member::attached_value(); - syn::parse_quote!(#ty_call_def::new(String::from(#fun_name_str), #args_block).with_amount(#attached_value)) + syn::parse_quote!(#ty_call_def::new(String::from(#fun_name_str), #is_mut, #args_block).with_amount(#attached_value)) } fn function_signature(fun: &FnIR) -> syn::Signature { diff --git a/odra-macros/src/ast/test_parts.rs b/odra-macros/src/ast/test_parts.rs index 07974ab8..00514d88 100644 --- a/odra-macros/src/ast/test_parts.rs +++ b/odra-macros/src/ast/test_parts.rs @@ -128,6 +128,7 @@ mod test { self.address, odra::CallDef::new( String::from("total_supply"), + false, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -148,6 +149,7 @@ mod test { self.address, odra::CallDef::new( String::from("pay_to_mint"), + true, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -168,6 +170,7 @@ mod test { self.address, odra::CallDef::new( String::from("approve"), + true, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -190,6 +193,7 @@ mod test { self.address, odra::CallDef::new( String::from("airdrop"), + false, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -320,6 +324,7 @@ mod test { self.address, odra::CallDef::new( String::from("total_supply"), + false, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { @@ -340,6 +345,7 @@ mod test { self.address, odra::CallDef::new( String::from("pay_to_mint"), + true, { let mut named_args = odra::RuntimeArgs::new(); if self.attached_value > odra::U512::zero() { From 1dd45bff41aae39ab69700bfc31f2d18328145bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 17 Jan 2024 12:27:59 +0100 Subject: [PATCH 05/24] Wip --- core/src/host_env.rs | 5 ++ examples/bin/erc20_on_livenet.rs | 5 +- .../casper-client/src/casper_client.rs | 4 + .../livenet-env/src/livenet_host_env.rs | 3 + odra-macros/src/ast/deployer_item.rs | 32 ++++++-- odra-macros/src/ast/deployer_utils.rs | 74 +++++++++++++++++-- odra-macros/src/ir/mod.rs | 1 + odra-macros/src/utils/ident.rs | 4 + odra-vm/src/odra_vm_host.rs | 2 +- 9 files changed, 112 insertions(+), 18 deletions(-) diff --git a/core/src/host_env.rs b/core/src/host_env.rs index 616e1ada..177c8b8f 100644 --- a/core/src/host_env.rs +++ b/core/src/host_env.rs @@ -57,6 +57,11 @@ impl HostEnv { deployed_contract } + pub fn register_contract(&self, address: Address) { + self.deployed_contracts.borrow_mut().push(address); + self.events_count.borrow_mut().insert(address, 0); + } + pub fn call_contract( &self, address: Address, diff --git a/examples/bin/erc20_on_livenet.rs b/examples/bin/erc20_on_livenet.rs index f309c817..0fbed442 100644 --- a/examples/bin/erc20_on_livenet.rs +++ b/examples/bin/erc20_on_livenet.rs @@ -22,9 +22,10 @@ fn main() { let address = Address::from_str(address).unwrap(); let mut token = Erc20HostRef::new(address, env.clone()); env.set_gas(1_000_000_000u64); - // token.approve(owner, U256::from(1000)); + token.approve(owner, U256::from(1000)); + let name = token.name(); - println!("Token name: {}", token.symbol()); + // println!("Token name: {}", token.symbol()); // env.set_gas(3_000_000_000u64); // token.transfer(recipient, U256::from(1000)); diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index 0ca9348b..c811f46d 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -263,6 +263,10 @@ impl CasperClient { value } + pub fn call_without_deploy(&self, address: Address, call_def: CallDef) -> Result { + todo!("call_without_deploy"); + } + /// Deploy the entrypoint call. pub fn deploy_entrypoint_call( &self, diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index bfa48fd9..fc224880 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -63,6 +63,9 @@ impl HostContext for LivenetEnv { call_def: CallDef, use_proxy: bool ) -> Result { + if !call_def.is_mut() { + return self.casper_client.borrow_mut().call_without_deploy(*address, call_def); + } match use_proxy { true => Ok(self.casper_client.borrow_mut().deploy_entrypoint_call_with_proxy(*address, call_def)), false => { diff --git a/odra-macros/src/ast/deployer_item.rs b/odra-macros/src/ast/deployer_item.rs index 20f3108f..dfbafd7a 100644 --- a/odra-macros/src/ast/deployer_item.rs +++ b/odra-macros/src/ast/deployer_item.rs @@ -2,9 +2,7 @@ use derive_try_from::TryFromRef; use crate::{ir::ModuleImplIR, utils}; -use super::deployer_utils::{ - DeployerInitSignature, EntrypointCallerExpr, HostRefInstanceExpr, NewContractExpr -}; +use super::deployer_utils::{CallEpcExpr, EpcSignature, DeployerInitSignature, EntrypointCallerExpr, HostRefInstanceExpr, NewContractExpr}; #[derive(syn_derive::ToTokens)] struct DeployStructItem { @@ -34,6 +32,8 @@ struct DeployImplItem { #[syn(braced)] brace_token: syn::token::Brace, #[syn(in = brace_token)] + epc_fn: ContractEpcFn, + #[syn(in = brace_token)] init_fn: ContractInitFn } @@ -45,11 +45,25 @@ impl TryFrom<&'_ ModuleImplIR> for DeployImplItem { impl_token: Default::default(), ident: module.deployer_ident()?, brace_token: Default::default(), + epc_fn: module.try_into()?, init_fn: module.try_into()? }) } } +#[derive(syn_derive::ToTokens, TryFromRef)] +#[source(ModuleImplIR)] +pub struct ContractEpcFn { + #[expr(utils::syn::visibility_pub())] + vis: syn::Visibility, + sig: EpcSignature, + #[syn(braced)] + #[default] + braces: syn::token::Brace, + #[syn(in = braces)] + caller: EntrypointCallerExpr, +} + #[derive(syn_derive::ToTokens, TryFromRef)] #[source(ModuleImplIR)] struct ContractInitFn { @@ -60,7 +74,7 @@ struct ContractInitFn { #[default] braces: syn::token::Brace, #[syn(in = braces)] - caller: EntrypointCallerExpr, + caller: CallEpcExpr, #[syn(in = braces)] new_contract: NewContractExpr, #[syn(in = braces)] @@ -87,8 +101,8 @@ mod deployer_impl { pub struct Erc20Deployer; impl Erc20Deployer { - pub fn init(env: &odra::HostEnv, total_supply: Option) -> Erc20HostRef { - let caller = odra::EntryPointsCaller::new(env.clone(), |contract_env, call_def| { + pub fn epc(env: &odra::HostEnv) -> odra::EntryPointsCaller { + odra::EntryPointsCaller::new(env.clone(), |contract_env, call_def| { match call_def.method() { "init" => { let result = __erc20_exec_parts::execute_init(contract_env); @@ -114,7 +128,11 @@ mod deployer_impl { odra::VmError::NoSuchMethod(odra::prelude::String::from(name)), )) } - }); + }) + } + + pub fn init(env: &odra::HostEnv, total_supply: Option) -> Erc20HostRef { + let caller = Self::epc(env); let address = env.new_contract( "Erc20", diff --git a/odra-macros/src/ast/deployer_utils.rs b/odra-macros/src/ast/deployer_utils.rs index 2d732a13..cff40d09 100644 --- a/odra-macros/src/ast/deployer_utils.rs +++ b/odra-macros/src/ast/deployer_utils.rs @@ -40,13 +40,40 @@ impl TryFrom<&'_ ModuleImplIR> for DeployerInitSignature { } } +#[derive(syn_derive::ToTokens)] +pub struct EpcSignature { + fn_token: syn::token::Fn, + epc_token: syn::Ident, + #[syn(parenthesized)] + paren_token: syn::token::Paren, + #[syn(in = paren_token)] + input: syn::FnArg, + output: syn::ReturnType +} + +impl TryFrom<&'_ ModuleImplIR> for EpcSignature { + type Error = syn::Error; + + fn try_from(module: &'_ ModuleImplIR) -> Result { + let epc_ident = utils::ty::entry_points_caller(); + let ty_host_env = utils::ty::host_env(); + let env = utils::ident::env(); + + let input = parse_quote!(#env: &#ty_host_env); + + Ok(Self { + fn_token: Default::default(), + epc_token: utils::ident::epc(), + paren_token: Default::default(), + input, + output: utils::misc::ret_ty(&epc_ident) + }) + } +} + #[derive(syn_derive::ToTokens)] pub struct EntrypointCallerExpr { - let_token: syn::token::Let, - ident: syn::Ident, - assign_token: syn::token::Eq, caller_expr: syn::Expr, - semi_token: syn::token::Semi } impl TryFrom<&'_ ModuleImplIR> for EntrypointCallerExpr { @@ -54,11 +81,7 @@ impl TryFrom<&'_ ModuleImplIR> for EntrypointCallerExpr { fn try_from(module: &'_ ModuleImplIR) -> Result { Ok(Self { - let_token: Default::default(), - ident: utils::ident::caller(), - assign_token: Default::default(), caller_expr: Self::entrypoint_caller(module)?, - semi_token: Default::default() }) } } @@ -88,6 +111,41 @@ impl EntrypointCallerExpr { } } +#[derive(syn_derive::ToTokens)] +pub struct CallEpcExpr { + let_token: syn::token::Let, + ident: syn::Ident, + assign_token: syn::token::Eq, + epc_expression: syn::Expr, + semi_token: syn::token::Semi +} + +impl TryFrom<&'_ ModuleImplIR> for CallEpcExpr { + type Error = syn::Error; + + fn try_from(module: &'_ ModuleImplIR) -> Result { + let epc_ident = utils::ident::epc(); + let env_ident = utils::ident::env(); + let args = module + .constructor() + .map(|f| fn_utils::runtime_args_block(&f, ref_utils::insert_arg_stmt)) + .map(utils::expr::some) + .unwrap_or_else(utils::expr::none); + + + Ok(Self { + let_token: Default::default(), + ident: utils::ident::caller(), + assign_token: Default::default(), + epc_expression: parse_quote!( + Self::epc(env) + ), + semi_token: Default::default() + }) + } +} + + #[derive(syn_derive::ToTokens)] pub struct NewContractExpr { let_token: syn::token::Let, diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index f7d674d9..f7e42735 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -193,6 +193,7 @@ impl ModuleImplIR { module_ident.span() )) } + pub fn contract_ref_ident(&self) -> Result { let module_ident = self.module_ident()?; Ok(Ident::new( diff --git a/odra-macros/src/utils/ident.rs b/odra-macros/src/utils/ident.rs index d8ffca8b..f6ea9fb5 100644 --- a/odra-macros/src/utils/ident.rs +++ b/odra-macros/src/utils/ident.rs @@ -35,6 +35,10 @@ pub fn init() -> syn::Ident { format_ident!("init") } +pub fn epc() -> syn::Ident { + format_ident!("epc") +} + pub fn address() -> syn::Ident { format_ident!("address") } diff --git a/odra-vm/src/odra_vm_host.rs b/odra-vm/src/odra_vm_host.rs index f443ba71..83a0d8ad 100644 --- a/odra-vm/src/odra_vm_host.rs +++ b/odra-vm/src/odra_vm_host.rs @@ -79,7 +79,7 @@ impl HostContext for OdraVmHost { if let Some(init_args) = init_args { let _ = self.call_contract( &address, - CallDef::new(String::from("init"), init_args), + CallDef::new(String::from("init"), true, init_args), false ); } From 4b12c7c92638ddc27c9556ca6b97c00b63cb7904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 17 Jan 2024 13:48:47 +0100 Subject: [PATCH 06/24] Wip --- Cargo.toml | 3 +- core/src/host_context.rs | 5 ++ core/src/host_env.rs | 4 +- examples/bin/erc20_on_livenet.rs | 10 +-- .../casper-client/src/casper_client.rs | 4 + odra-casper/livenet-env/Cargo.toml | 1 + odra-casper/livenet-env/src/lib.rs | 1 + .../livenet-env/src/livenet_contract_env.rs | 86 +++++++++++++++++++ .../livenet-env/src/livenet_host_env.rs | 23 +++-- odra-casper/test-vm/src/casper_host.rs | 4 + odra-macros/src/ast/deployer_item.rs | 36 +++++++- odra-macros/src/ast/deployer_utils.rs | 61 +++++++++++++ odra-macros/src/utils/ident.rs | 4 + odra-vm/Cargo.toml | 2 +- odra-vm/src/odra_vm_host.rs | 4 + 15 files changed, 231 insertions(+), 17 deletions(-) create mode 100644 odra-casper/livenet-env/src/livenet_contract_env.rs diff --git a/Cargo.toml b/Cargo.toml index 802364d3..599f10f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,5 @@ repository = "https://github.com/odradev/odra" casper-contract = { version = "3.0.0", default-features = false } casper-types = { version = "3.0.0", default-features = false } casper-execution-engine = "5.0.0" -casper-event-standard = "0.4.1" \ No newline at end of file +casper-event-standard = "0.4.1" +blake2 = "0.10.6" \ No newline at end of file diff --git a/core/src/host_context.rs b/core/src/host_context.rs index e2c68e5f..f0420719 100644 --- a/core/src/host_context.rs +++ b/core/src/host_context.rs @@ -28,6 +28,11 @@ pub trait HostContext { init_args: Option, entry_points_caller: Option ) -> Address; + fn register_contract( + &self, + address: Address, + entry_points_caller: EntryPointsCaller + ); fn contract_env(&self) -> ContractEnv; fn print_gas_report(&self); fn last_call_gas_cost(&self) -> u64; diff --git a/core/src/host_env.rs b/core/src/host_env.rs index 177c8b8f..314602de 100644 --- a/core/src/host_env.rs +++ b/core/src/host_env.rs @@ -57,7 +57,9 @@ impl HostEnv { deployed_contract } - pub fn register_contract(&self, address: Address) { + 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, 0); } diff --git a/examples/bin/erc20_on_livenet.rs b/examples/bin/erc20_on_livenet.rs index 0fbed442..c678c203 100644 --- a/examples/bin/erc20_on_livenet.rs +++ b/examples/bin/erc20_on_livenet.rs @@ -20,12 +20,12 @@ fn main() { // Uncomment to use already deployed contract. let address = "hash-d26fcbd2106e37be975d2045c580334a6d7b9d0a241c2358a4db970dfd516945"; let address = Address::from_str(address).unwrap(); - let mut token = Erc20HostRef::new(address, env.clone()); - env.set_gas(1_000_000_000u64); - token.approve(owner, U256::from(1000)); - let name = token.name(); + let mut token = Erc20Deployer::load(&env, address); + // env.set_gas(1_000_000_000u64); + // token.approve(owner, U256::from(1000)); + // let name = token.name(); - // println!("Token name: {}", token.symbol()); + println!("Token name: {}", token.symbol()); // env.set_gas(3_000_000_000u64); // token.transfer(recipient, U256::from(1000)); diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index c811f46d..bec1aaaf 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -64,6 +64,10 @@ impl CasperClient { gas: U512::zero(), } } + + pub fn get_value(&self, key: &[u8]) -> Option { + None + } pub fn set_gas(&mut self, gas: u64) { self.gas = gas.into(); diff --git a/odra-casper/livenet-env/Cargo.toml b/odra-casper/livenet-env/Cargo.toml index eaf501f8..5879d834 100644 --- a/odra-casper/livenet-env/Cargo.toml +++ b/odra-casper/livenet-env/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] odra-core = { path = "../../core" } odra-casper-client = { path = "../casper-client" } +blake2 = { workspace = true } \ No newline at end of file diff --git a/odra-casper/livenet-env/src/lib.rs b/odra-casper/livenet-env/src/lib.rs index e23fd388..91a4e805 100644 --- a/odra-casper/livenet-env/src/lib.rs +++ b/odra-casper/livenet-env/src/lib.rs @@ -1,5 +1,6 @@ #![feature(once_cell)] pub mod livenet_host_env; +pub mod livenet_contract_env; use livenet_host_env::LivenetEnv; use odra_core::HostEnv; diff --git a/odra-casper/livenet-env/src/livenet_contract_env.rs b/odra-casper/livenet-env/src/livenet_contract_env.rs new file mode 100644 index 00000000..a51a118d --- /dev/null +++ b/odra-casper/livenet-env/src/livenet_contract_env.rs @@ -0,0 +1,86 @@ +use odra_casper_client::casper_client::CasperClient; +use blake2::digest::VariableOutput; +use blake2::{Blake2b, Blake2b512, Blake2bVar, Blake2s256, Digest}; +use odra_core::casper_types::BlockTime; +use odra_core::prelude::*; +use odra_core::{casper_types, Address, Bytes, OdraError, ToBytes, U512}; +use odra_core::{CallDef, ContractContext}; +use std::collections::hash_map::DefaultHasher; +use std::hash::Hasher; +use std::io::Write; + + +pub struct LivenetContractEnv { + casper_client: Rc>, +} + +impl ContractContext for LivenetContractEnv { + fn get_value(&self, key: &[u8]) -> Option { + self.casper_client.borrow().get_value(key) + } + + fn set_value(&self, key: &[u8], value: Bytes) { + panic!("Cannot set value in LivenetEnv") + } + + fn caller(&self) -> Address { + todo!() + } + + fn self_address(&self) -> Address { + todo!() + } + + fn call_contract(&self, address: Address, call_def: CallDef) -> Bytes { + todo!() + } + + fn get_block_time(&self) -> u64 { + todo!() + } + + fn attached_value(&self) -> U512 { + todo!() + } + + fn emit_event(&self, event: &Bytes) { + panic!("Cannot emit event in LivenetEnv") + } + + fn transfer_tokens(&self, to: &Address, amount: &U512) { + panic!("Cannot transfer tokens in LivenetEnv") + } + + fn revert(&self, error: OdraError) -> ! { + dbg!(error); + todo!() + } + + fn get_named_arg_bytes(&self, name: &str) -> Bytes { + todo!() + } + + fn handle_attached_value(&self) { + // no-op + } + + fn clear_attached_value(&self) { + // no-op + } + + fn hash(&self, bytes: &[u8]) -> [u8; 32] { + let mut result = [0u8; 32]; + let mut hasher = ::new(32).expect("should create hasher"); + let _ = hasher.write(bytes); + hasher + .finalize_variable(&mut result) + .expect("should copy hash to the result array"); + result + } +} + +impl LivenetContractEnv { + pub fn new(casper_client: Rc>) -> Rc> { + Rc::new(RefCell::new(Self { casper_client })) + } +} \ No newline at end of file diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index fc224880..930a8431 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -7,11 +7,14 @@ use odra_core::{ Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, RuntimeArgs, U512 }; +use odra_core::contract_container::ContractContainer; use odra_core::contract_register::ContractRegister; +use crate::livenet_contract_env::LivenetContractEnv; pub struct LivenetEnv { casper_client: Rc>, - contract_register: Arc> + contract_register: Arc>, + contract_env: Rc } impl LivenetEnv { @@ -20,7 +23,10 @@ impl LivenetEnv { } pub fn new_instance() -> Self { - Self { casper_client: Default::default(), contract_register: Default::default() } + let casper_client: Rc> = Default::default(); + let livenet_contract_env = LivenetContractEnv::new(casper_client.clone()); + let contract_env = Rc::new(ContractEnv::new(0, livenet_contract_env)); + Self { casper_client, contract_register: Default::default(), contract_env } } } @@ -54,7 +60,8 @@ impl HostContext for LivenetEnv { } fn get_events_count(&self, contract_address: &Address) -> u32 { - todo!() + // TODO: implement + 0 } fn call_contract( @@ -64,17 +71,21 @@ impl HostContext for LivenetEnv { use_proxy: bool ) -> Result { if !call_def.is_mut() { - return self.casper_client.borrow_mut().call_without_deploy(*address, call_def); + return self.contract_register.read().unwrap().call(address, call_def) } match use_proxy { true => Ok(self.casper_client.borrow_mut().deploy_entrypoint_call_with_proxy(*address, call_def)), false => { self.casper_client.borrow_mut().deploy_entrypoint_call(*address, call_def); - return Ok(Default::default()); + Ok(Default::default()) }, } } + fn register_contract(&self, address: Address, entry_points_caller: EntryPointsCaller) { + self.contract_register.write().unwrap().add(address, ContractContainer::new(&address.to_string(), entry_points_caller)); + } + fn new_contract( &self, name: &str, @@ -93,7 +104,7 @@ impl HostContext for LivenetEnv { } fn contract_env(&self) -> ContractEnv { - panic!("Cannot get contract env in LivenetEnv") + (*self.contract_env).clone() } fn print_gas_report(&self) { diff --git a/odra-casper/test-vm/src/casper_host.rs b/odra-casper/test-vm/src/casper_host.rs index e1255132..3a9a544f 100644 --- a/odra-casper/test-vm/src/casper_host.rs +++ b/odra-casper/test-vm/src/casper_host.rs @@ -97,6 +97,10 @@ impl HostContext for CasperHost { .new_contract(name, init_args, entry_points_caller) } + fn register_contract(&self, address: Address, entry_points_caller: EntryPointsCaller) { + panic!("register_contract is not supported in CasperHost"); + } + fn contract_env(&self) -> ContractEnv { unreachable!() } diff --git a/odra-macros/src/ast/deployer_item.rs b/odra-macros/src/ast/deployer_item.rs index dfbafd7a..0a71f0f9 100644 --- a/odra-macros/src/ast/deployer_item.rs +++ b/odra-macros/src/ast/deployer_item.rs @@ -2,7 +2,7 @@ use derive_try_from::TryFromRef; use crate::{ir::ModuleImplIR, utils}; -use super::deployer_utils::{CallEpcExpr, EpcSignature, DeployerInitSignature, EntrypointCallerExpr, HostRefInstanceExpr, NewContractExpr}; +use super::deployer_utils::{CallEpcExpr, EpcSignature, DeployerInitSignature, EntrypointCallerExpr, HostRefInstanceExpr, NewContractExpr, DeployerLoadSignature, LoadContractExpr}; #[derive(syn_derive::ToTokens)] struct DeployStructItem { @@ -34,7 +34,9 @@ struct DeployImplItem { #[syn(in = brace_token)] epc_fn: ContractEpcFn, #[syn(in = brace_token)] - init_fn: ContractInitFn + init_fn: ContractInitFn, + #[syn(in = brace_token)] + load_fn: ContractLoadFn } impl TryFrom<&'_ ModuleImplIR> for DeployImplItem { @@ -46,7 +48,8 @@ impl TryFrom<&'_ ModuleImplIR> for DeployImplItem { ident: module.deployer_ident()?, brace_token: Default::default(), epc_fn: module.try_into()?, - init_fn: module.try_into()? + init_fn: module.try_into()?, + load_fn: module.try_into()? }) } } @@ -81,6 +84,23 @@ struct ContractInitFn { host_ref_instance: HostRefInstanceExpr } +#[derive(syn_derive::ToTokens, TryFromRef)] +#[source(ModuleImplIR)] +struct ContractLoadFn { + #[expr(utils::syn::visibility_pub())] + vis: syn::Visibility, + sig: DeployerLoadSignature, + #[syn(braced)] + #[default] + braces: syn::token::Brace, + #[syn(in = braces)] + caller: CallEpcExpr, + #[syn(in = braces)] + load_contract: LoadContractExpr, + #[syn(in = braces)] + host_ref_instance: HostRefInstanceExpr +} + #[derive(syn_derive::ToTokens, TryFromRef)] #[source(ModuleImplIR)] pub struct DeployerItem { @@ -149,6 +169,16 @@ mod deployer_impl { attached_value: odra::U512::zero() } } + + pub fn load(env: &odra:HostEnv, address: Address) -> Erc20HostRef { + let caller = Self::epc(env); + env.register_contract(address, caller); + Erc20HostRef { + address, + env: env.clone(), + attached_value: odra::U512::zero() + } + } } }; let deployer_item = DeployerItem::try_from(&module).unwrap(); diff --git a/odra-macros/src/ast/deployer_utils.rs b/odra-macros/src/ast/deployer_utils.rs index cff40d09..1e9a8767 100644 --- a/odra-macros/src/ast/deployer_utils.rs +++ b/odra-macros/src/ast/deployer_utils.rs @@ -7,6 +7,7 @@ use crate::{ use proc_macro2::TokenStream; use quote::ToTokens; use syn::parse_quote; +use syn::punctuated::Pair::Punctuated; #[derive(syn_derive::ToTokens)] pub struct DeployerInitSignature { @@ -40,6 +41,41 @@ impl TryFrom<&'_ ModuleImplIR> for DeployerInitSignature { } } +#[derive(syn_derive::ToTokens)] +pub struct DeployerLoadSignature { + fn_token: syn::token::Fn, + init_token: syn::Ident, + #[syn(parenthesized)] + paren_token: syn::token::Paren, + #[syn(in = paren_token)] + inputs: syn::punctuated::Punctuated, + output: syn::ReturnType +} + +impl TryFrom<&'_ ModuleImplIR> for DeployerLoadSignature { + type Error = syn::Error; + + fn try_from(module: &'_ ModuleImplIR) -> Result { + let host_ref_ident = module.host_ref_ident()?.as_type(); + let ty_host_env = utils::ty::host_env(); + let env = utils::ident::env(); + let ty_address = utils::ty::address(); + + let mut inputs = syn::punctuated::Punctuated::new(); + inputs.push(parse_quote!(#env: &#ty_host_env)); + inputs.push(parse_quote!(address: #ty_address)); + + Ok(Self { + fn_token: Default::default(), + init_token: utils::ident::load(), + paren_token: Default::default(), + inputs, + output: utils::misc::ret_ty(&host_ref_ident) + }) + } +} + + #[derive(syn_derive::ToTokens)] pub struct EpcSignature { fn_token: syn::token::Fn, @@ -186,6 +222,31 @@ impl TryFrom<&'_ ModuleImplIR> for NewContractExpr { } } +#[derive(syn_derive::ToTokens)] +pub struct LoadContractExpr { + load_contract_expr: syn::Expr, + semi_token: syn::token::Semi +} + +impl TryFrom<&'_ ModuleImplIR> for LoadContractExpr { + type Error = syn::Error; + + fn try_from(module: &'_ ModuleImplIR) -> Result { + let env_ident = utils::ident::env(); + let address_ident = utils::ident::address(); + let caller_ident = utils::ident::caller(); + + let load_contract_expr = parse_quote!( + #env_ident.register_contract(#address_ident, #caller_ident) + ); + + Ok(Self { + load_contract_expr, + semi_token: Default::default() + }) + } +} + #[derive(syn_derive::ToTokens)] pub struct HostRefInstanceExpr { ident: syn::Ident, diff --git a/odra-macros/src/utils/ident.rs b/odra-macros/src/utils/ident.rs index f6ea9fb5..9f5f5916 100644 --- a/odra-macros/src/utils/ident.rs +++ b/odra-macros/src/utils/ident.rs @@ -39,6 +39,10 @@ pub fn epc() -> syn::Ident { format_ident!("epc") } +pub fn load() -> syn::Ident { + format_ident!("load") +} + pub fn address() -> syn::Ident { format_ident!("address") } diff --git a/odra-vm/Cargo.toml b/odra-vm/Cargo.toml index dc296cdf..26eece48 100644 --- a/odra-vm/Cargo.toml +++ b/odra-vm/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" odra-core = { path = "../core" } anyhow = "1.0.75" url = "2.4.1" -blake2 = "0.10.6" +blake2 = { workspace = true } [dev-dependencies] odra = { path = "../odra" } \ No newline at end of file diff --git a/odra-vm/src/odra_vm_host.rs b/odra-vm/src/odra_vm_host.rs index 83a0d8ad..40ec09e4 100644 --- a/odra-vm/src/odra_vm_host.rs +++ b/odra-vm/src/odra_vm_host.rs @@ -87,6 +87,10 @@ impl HostContext for OdraVmHost { address } + fn register_contract(&self, address: Address, entry_points_caller: EntryPointsCaller) { + panic!("register_contract is not supported for OdraVM"); + } + fn contract_env(&self) -> ContractEnv { (*self.contract_env).clone() } From 70eb941dfcc75fa5f650034d1e19d942e23852df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 17 Jan 2024 15:16:10 +0100 Subject: [PATCH 07/24] Wip --- core/src/host_context.rs | 2 +- core/src/host_env.rs | 2 +- .../casper-client/src/casper_client.rs | 4 ++-- .../livenet-env/src/livenet_contract_env.rs | 5 +++-- .../livenet-env/src/livenet_host_env.rs | 19 +++++++++++++------ odra-casper/test-vm/src/casper_host.rs | 2 +- odra-macros/src/ast/deployer_item.rs | 6 +++--- odra-macros/src/ast/deployer_utils.rs | 2 +- odra-vm/src/odra_vm_host.rs | 2 +- 9 files changed, 26 insertions(+), 18 deletions(-) diff --git a/core/src/host_context.rs b/core/src/host_context.rs index f0420719..7b9080ec 100644 --- a/core/src/host_context.rs +++ b/core/src/host_context.rs @@ -26,7 +26,7 @@ pub trait HostContext { &self, name: &str, init_args: Option, - entry_points_caller: Option + entry_points_caller: EntryPointsCaller ) -> Address; fn register_contract( &self, diff --git a/core/src/host_env.rs b/core/src/host_env.rs index 314602de..4c52cfde 100644 --- a/core/src/host_env.rs +++ b/core/src/host_env.rs @@ -48,7 +48,7 @@ impl HostEnv { &self, name: &str, init_args: Option, - entry_points_caller: Option + entry_points_caller: EntryPointsCaller ) -> Address { let backend = self.backend.borrow(); let deployed_contract = backend.new_contract(name, init_args, entry_points_caller); diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index bec1aaaf..51e9b5be 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -64,9 +64,9 @@ impl CasperClient { gas: U512::zero(), } } - + pub fn get_value(&self, key: &[u8]) -> Option { - None + Some(Bytes::from("todo".to_string().to_bytes().unwrap())) } pub fn set_gas(&mut self, gas: u64) { diff --git a/odra-casper/livenet-env/src/livenet_contract_env.rs b/odra-casper/livenet-env/src/livenet_contract_env.rs index a51a118d..e2891c2d 100644 --- a/odra-casper/livenet-env/src/livenet_contract_env.rs +++ b/odra-casper/livenet-env/src/livenet_contract_env.rs @@ -12,6 +12,7 @@ use std::io::Write; pub struct LivenetContractEnv { casper_client: Rc>, + callstack: Rc>>, } impl ContractContext for LivenetContractEnv { @@ -80,7 +81,7 @@ impl ContractContext for LivenetContractEnv { } impl LivenetContractEnv { - pub fn new(casper_client: Rc>) -> Rc> { - Rc::new(RefCell::new(Self { casper_client })) + pub fn new(casper_client: Rc>, callstack: Rc>>) -> Rc> { + Rc::new(RefCell::new(Self { casper_client, callstack })) } } \ No newline at end of file diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index 930a8431..ae6f4802 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -14,7 +14,8 @@ use crate::livenet_contract_env::LivenetContractEnv; pub struct LivenetEnv { casper_client: Rc>, contract_register: Arc>, - contract_env: Rc + contract_env: Rc, + callstack: Rc>>, } impl LivenetEnv { @@ -24,9 +25,10 @@ impl LivenetEnv { pub fn new_instance() -> Self { let casper_client: Rc> = Default::default(); - let livenet_contract_env = LivenetContractEnv::new(casper_client.clone()); + let callstack: Rc>> = Default::default(); + let livenet_contract_env = LivenetContractEnv::new(casper_client.clone(), callstack.clone()); let contract_env = Rc::new(ContractEnv::new(0, livenet_contract_env)); - Self { casper_client, contract_register: Default::default(), contract_env } + Self { casper_client, contract_register: Default::default(), contract_env, callstack } } } @@ -71,7 +73,10 @@ impl HostContext for LivenetEnv { use_proxy: bool ) -> Result { if !call_def.is_mut() { - return self.contract_register.read().unwrap().call(address, call_def) + self.callstack.borrow_mut().push(*address); + let result = self.contract_register.read().unwrap().call(address, call_def); + self.callstack.borrow_mut().pop(); + return result } match use_proxy { true => Ok(self.casper_client.borrow_mut().deploy_entrypoint_call_with_proxy(*address, call_def)), @@ -90,7 +95,7 @@ impl HostContext for LivenetEnv { &self, name: &str, init_args: Option, - entry_points_caller: Option + entry_points_caller: EntryPointsCaller ) -> Address { let mut args = match init_args { None => RuntimeArgs::new(), @@ -100,7 +105,9 @@ impl HostContext for LivenetEnv { args.insert("odra_cfg_is_upgradable", false).unwrap(); args.insert("odra_cfg_allow_key_override", false).unwrap(); args.insert("odra_cfg_package_hash_key_name", format!("{}_package_hash", name)).unwrap(); - self.casper_client.borrow_mut().deploy_wasm(name, args) + let address = self.casper_client.borrow_mut().deploy_wasm(name, args); + self.register_contract(address, entry_points_caller); + address } fn contract_env(&self) -> ContractEnv { diff --git a/odra-casper/test-vm/src/casper_host.rs b/odra-casper/test-vm/src/casper_host.rs index 3a9a544f..16c20fdb 100644 --- a/odra-casper/test-vm/src/casper_host.rs +++ b/odra-casper/test-vm/src/casper_host.rs @@ -90,7 +90,7 @@ impl HostContext for CasperHost { &self, name: &str, init_args: Option, - entry_points_caller: Option + entry_points_caller: EntryPointsCaller ) -> Address { self.vm .borrow_mut() diff --git a/odra-macros/src/ast/deployer_item.rs b/odra-macros/src/ast/deployer_item.rs index 0a71f0f9..2f92fad0 100644 --- a/odra-macros/src/ast/deployer_item.rs +++ b/odra-macros/src/ast/deployer_item.rs @@ -161,7 +161,7 @@ mod deployer_impl { let _ = named_args.insert("total_supply", total_supply); named_args }), - Some(caller) + caller ); Erc20HostRef { address, @@ -170,13 +170,13 @@ mod deployer_impl { } } - pub fn load(env: &odra:HostEnv, address: Address) -> Erc20HostRef { + pub fn load(env: &odra::HostEnv, address: odra::Address) -> Erc20HostRef { let caller = Self::epc(env); env.register_contract(address, caller); Erc20HostRef { address, env: env.clone(), - attached_value: odra::U512::zero() + attached_value: odra::U512::zero(), } } } diff --git a/odra-macros/src/ast/deployer_utils.rs b/odra-macros/src/ast/deployer_utils.rs index 1e9a8767..d0d8ae22 100644 --- a/odra-macros/src/ast/deployer_utils.rs +++ b/odra-macros/src/ast/deployer_utils.rs @@ -196,7 +196,7 @@ impl TryFrom<&'_ ModuleImplIR> for NewContractExpr { fn try_from(module: &'_ ModuleImplIR) -> Result { let module_str = module.module_str()?; - let caller_expr = utils::expr::some(utils::ident::caller()); + let caller_expr = utils::ident::caller(); let env_ident = utils::ident::env(); let args = module .constructor() diff --git a/odra-vm/src/odra_vm_host.rs b/odra-vm/src/odra_vm_host.rs index 40ec09e4..88b9bfef 100644 --- a/odra-vm/src/odra_vm_host.rs +++ b/odra-vm/src/odra_vm_host.rs @@ -68,7 +68,7 @@ impl HostContext for OdraVmHost { &self, name: &str, init_args: Option, - entry_points_caller: Option + entry_points_caller: EntryPointsCaller ) -> Address { // TODO: panic in nice way let address = self From 23b80d4d13ec033e5598885eb53e42036eda8c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 17 Jan 2024 15:44:21 +0100 Subject: [PATCH 08/24] Livenet basic --- {odra-vm/src/vm => core/src}/callstack.rs | 3 +- core/src/lib.rs | 1 + examples/bin/erc20_on_livenet.rs | 52 +++++++++++-------- .../casper-client/src/casper_client.rs | 4 +- .../livenet-env/src/livenet_contract_env.rs | 29 ++++++----- .../livenet-env/src/livenet_host_env.rs | 7 +-- odra-vm/src/vm/mod.rs | 1 - odra-vm/src/vm/odra_vm_state.rs | 2 +- 8 files changed, 56 insertions(+), 43 deletions(-) rename {odra-vm/src/vm => core/src}/callstack.rs (95%) diff --git a/odra-vm/src/vm/callstack.rs b/core/src/callstack.rs similarity index 95% rename from odra-vm/src/vm/callstack.rs rename to core/src/callstack.rs index 25cc1bc2..8e20462a 100644 --- a/odra-vm/src/vm/callstack.rs +++ b/core/src/callstack.rs @@ -1,4 +1,5 @@ -use odra_core::{casper_types::U512, Address, CallDef}; +use super::{casper_types::U512, Address, CallDef}; +use crate::prelude::*; #[derive(Clone)] pub enum CallstackElement { diff --git a/core/src/lib.rs b/core/src/lib.rs index f9101818..1aa5912b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -31,6 +31,7 @@ pub mod utils; pub mod variable; pub mod contract_container; pub mod contract_register; +pub mod callstack; pub use address::{Address, OdraAddress}; pub use call_def::CallDef; diff --git a/examples/bin/erc20_on_livenet.rs b/examples/bin/erc20_on_livenet.rs index c678c203..f0f242fb 100644 --- a/examples/bin/erc20_on_livenet.rs +++ b/examples/bin/erc20_on_livenet.rs @@ -1,35 +1,43 @@ use std::str::FromStr; -use odra::{Address, U256}; +use odra::{Address, HostEnv, U256}; use odra_modules::erc20::{Erc20Deployer, Erc20HostRef}; fn main() { let env = odra_casper_livenet_env::livenet_env(); - let name = String::from("Plascoin"); - let symbol = String::from("PLS"); - let decimals = 10u8; - let initial_supply: U256 = U256::from(10_000); let owner = env.caller(); - // dbg!(owner); let recipient = "hash-2c4a6ce0da5d175e9638ec0830e01dd6cf5f4b1fbb0724f7d2d9de12b1e0f840"; let recipient = Address::from_str(recipient).unwrap(); - // env.set_gas(100_000_000_000u64); - // let mut token = Erc20Deployer::init(&env, name, symbol, decimals, Some(initial_supply)); - // Uncomment to use already deployed contract. + // Uncomment to deploy new contract. + // let token = deploy_new(&env); + // println!("Token address: {}", token.address()); + + // Load existing contract. + let mut token = load(&env); + + println!("Token name: {}", token.name()); + + env.set_gas(3_000_000_000u64); + token.transfer(recipient, U256::from(1000)); + + println!("Owner's balance: {:?}", token.balance_of(owner)); + println!("Recipient's balance: {:?}", token.balance_of(recipient)); +} + +fn deploy_new(env: &HostEnv) -> Erc20HostRef { + let name = String::from("Plascoin"); + let symbol = String::from("PLS"); + let decimals = 10u8; + let initial_supply: U256 = U256::from(10_000); + + env.set_gas(100_000_000_000u64); + Erc20Deployer::init(env, name, symbol, decimals, Some(initial_supply)) +} + +fn load(env: &HostEnv) -> Erc20HostRef { let address = "hash-d26fcbd2106e37be975d2045c580334a6d7b9d0a241c2358a4db970dfd516945"; let address = Address::from_str(address).unwrap(); - let mut token = Erc20Deployer::load(&env, address); - // env.set_gas(1_000_000_000u64); - // token.approve(owner, U256::from(1000)); - // let name = token.name(); - - println!("Token name: {}", token.symbol()); - - // env.set_gas(3_000_000_000u64); - // token.transfer(recipient, U256::from(1000)); - // - // println!("Owner's balance: {:?}", token.balance_of(owner)); - // println!("Recipient's balance: {:?}", token.balance_of(recipient)); -} + Erc20Deployer::load(env, address) +} \ No newline at end of file diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index 51e9b5be..3d694e4c 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -65,8 +65,8 @@ impl CasperClient { } } - pub fn get_value(&self, key: &[u8]) -> Option { - Some(Bytes::from("todo".to_string().to_bytes().unwrap())) + pub fn get_value(&self, address: &Address, key: &[u8]) -> Option { + self.query_dictionary(*address, unsafe { from_utf8_unchecked(key) }) } pub fn set_gas(&mut self, gas: u64) { diff --git a/odra-casper/livenet-env/src/livenet_contract_env.rs b/odra-casper/livenet-env/src/livenet_contract_env.rs index e2891c2d..553e04f9 100644 --- a/odra-casper/livenet-env/src/livenet_contract_env.rs +++ b/odra-casper/livenet-env/src/livenet_contract_env.rs @@ -1,26 +1,24 @@ use odra_casper_client::casper_client::CasperClient; use blake2::digest::VariableOutput; -use blake2::{Blake2b, Blake2b512, Blake2bVar, Blake2s256, Digest}; -use odra_core::casper_types::BlockTime; +use blake2::Blake2bVar; use odra_core::prelude::*; -use odra_core::{casper_types, Address, Bytes, OdraError, ToBytes, U512}; +use odra_core::{Address, Bytes, OdraError, U512}; use odra_core::{CallDef, ContractContext}; -use std::collections::hash_map::DefaultHasher; -use std::hash::Hasher; use std::io::Write; +use odra_core::callstack::{Callstack, CallstackElement}; pub struct LivenetContractEnv { casper_client: Rc>, - callstack: Rc>>, + callstack: Rc>, } impl ContractContext for LivenetContractEnv { fn get_value(&self, key: &[u8]) -> Option { - self.casper_client.borrow().get_value(key) + self.casper_client.borrow().get_value(self.callstack.borrow().current().address(), key) } - fn set_value(&self, key: &[u8], value: Bytes) { + fn set_value(&self, _key: &[u8], _value: Bytes) { panic!("Cannot set value in LivenetEnv") } @@ -32,7 +30,7 @@ impl ContractContext for LivenetContractEnv { todo!() } - fn call_contract(&self, address: Address, call_def: CallDef) -> Bytes { + fn call_contract(&self, _address: Address, _call_def: CallDef) -> Bytes { todo!() } @@ -44,11 +42,11 @@ impl ContractContext for LivenetContractEnv { todo!() } - fn emit_event(&self, event: &Bytes) { + fn emit_event(&self, _event: &Bytes) { panic!("Cannot emit event in LivenetEnv") } - fn transfer_tokens(&self, to: &Address, amount: &U512) { + fn transfer_tokens(&self, _to: &Address, _amount: &U512) { panic!("Cannot transfer tokens in LivenetEnv") } @@ -58,7 +56,12 @@ impl ContractContext for LivenetContractEnv { } fn get_named_arg_bytes(&self, name: &str) -> Bytes { - todo!() + match self.callstack.borrow().current() { + CallstackElement::Account(_) => todo!("get_named_arg_bytes"), + CallstackElement::Entrypoint(ep) => { + Bytes::from(ep.call_def.args.get(name).unwrap().inner_bytes().to_vec()) + } + } } fn handle_attached_value(&self) { @@ -81,7 +84,7 @@ impl ContractContext for LivenetContractEnv { } impl LivenetContractEnv { - pub fn new(casper_client: Rc>, callstack: Rc>>) -> Rc> { + pub fn new(casper_client: Rc>, callstack: Rc>) -> Rc> { Rc::new(RefCell::new(Self { casper_client, callstack })) } } \ No newline at end of file diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index ae6f4802..426d7563 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -7,6 +7,7 @@ use odra_core::{ Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, RuntimeArgs, U512 }; +use odra_core::callstack::{Callstack, CallstackElement, Entrypoint}; use odra_core::contract_container::ContractContainer; use odra_core::contract_register::ContractRegister; use crate::livenet_contract_env::LivenetContractEnv; @@ -15,7 +16,7 @@ pub struct LivenetEnv { casper_client: Rc>, contract_register: Arc>, contract_env: Rc, - callstack: Rc>>, + callstack: Rc>, } impl LivenetEnv { @@ -25,7 +26,7 @@ impl LivenetEnv { pub fn new_instance() -> Self { let casper_client: Rc> = Default::default(); - let callstack: Rc>> = Default::default(); + let callstack: Rc> = Default::default(); let livenet_contract_env = LivenetContractEnv::new(casper_client.clone(), callstack.clone()); let contract_env = Rc::new(ContractEnv::new(0, livenet_contract_env)); Self { casper_client, contract_register: Default::default(), contract_env, callstack } @@ -73,7 +74,7 @@ impl HostContext for LivenetEnv { use_proxy: bool ) -> Result { if !call_def.is_mut() { - self.callstack.borrow_mut().push(*address); + self.callstack.borrow_mut().push(CallstackElement::Entrypoint(Entrypoint::new(*address, call_def.clone()))); let result = self.contract_register.read().unwrap().call(address, call_def); self.callstack.borrow_mut().pop(); return result diff --git a/odra-vm/src/vm/mod.rs b/odra-vm/src/vm/mod.rs index ba05d7ea..3898878b 100644 --- a/odra-vm/src/vm/mod.rs +++ b/odra-vm/src/vm/mod.rs @@ -2,7 +2,6 @@ #![allow(unused_imports)] #![allow(unused_variables)] mod balance; -mod callstack; mod odra_vm; mod odra_vm_state; mod storage; diff --git a/odra-vm/src/vm/odra_vm_state.rs b/odra-vm/src/vm/odra_vm_state.rs index 46521592..4c62f4c7 100644 --- a/odra-vm/src/vm/odra_vm_state.rs +++ b/odra-vm/src/vm/odra_vm_state.rs @@ -1,5 +1,4 @@ use super::balance::AccountBalance; -use super::callstack::{Callstack, CallstackElement}; use super::storage::Storage; use anyhow::Result; use odra_core::casper_types::account::AccountHash; @@ -10,6 +9,7 @@ use odra_core::{ Address, Bytes, ExecutionError, FromBytes, OdraError, PublicKey, SecretKey, ToBytes, U512 }; use std::collections::BTreeMap; +use odra_core::callstack::{Callstack, CallstackElement}; pub struct OdraVmState { storage: Storage, From 1adf749307f81f332dee1732963b71ac17b1dce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Thu, 18 Jan 2024 17:20:25 +0100 Subject: [PATCH 09/24] Somewhat working, but still working on it --- Cargo.toml | 2 - core/src/call_def.rs | 4 +- core/src/callstack.rs | 11 ++ core/src/contract_container.rs | 13 +- core/src/host_context.rs | 6 +- core/src/lib.rs | 6 +- examples/Cargo.toml | 4 + examples/bin/erc20_on_livenet.rs | 5 +- examples/bin/nested_modules_on_livenet.rs | 48 +++++++ examples/src/features/module_nesting.rs | 6 +- .../casper-client/src/casper_client.rs | 133 +++++++----------- odra-casper/casper-client/src/lib.rs | 4 +- odra-casper/casper-client/src/log.rs | 24 ++-- odra-casper/livenet-env/src/lib.rs | 2 +- .../livenet-env/src/livenet_contract_env.rs | 41 ++++-- .../livenet-env/src/livenet_host_env.rs | 83 +++++++---- odra-casper/test-vm/src/vm/casper_vm.rs | 4 +- odra-macros/src/ast/deployer_item.rs | 7 +- odra-macros/src/ast/deployer_utils.rs | 28 +--- odra-macros/src/ir/mod.rs | 2 +- odra-test/Cargo.toml | 1 + odra-test/src/lib.rs | 1 + odra-vm/src/odra_vm_host.rs | 2 +- odra-vm/src/vm/odra_vm.rs | 11 +- odra-vm/src/vm/odra_vm_state.rs | 2 +- 25 files changed, 254 insertions(+), 196 deletions(-) create mode 100644 examples/bin/nested_modules_on_livenet.rs diff --git a/Cargo.toml b/Cargo.toml index 599f10f1..c47f6840 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,6 @@ members = [ "odra-casper/test-vm", "odra-test", "try-from-macro" -# needs a refactor -# "odra-casper/livenet" ] exclude = [ "examples", "examples2", "modules", "odra-casper/proxy-caller" ] resolver = "2" diff --git a/core/src/call_def.rs b/core/src/call_def.rs index f691a811..754a2e69 100644 --- a/core/src/call_def.rs +++ b/core/src/call_def.rs @@ -7,7 +7,7 @@ pub struct CallDef { pub entry_point: String, pub args: RuntimeArgs, pub amount: U512, - is_mut: bool, + is_mut: bool } impl CallDef { @@ -16,7 +16,7 @@ impl CallDef { entry_point: method, args, amount: U512::zero(), - is_mut, + is_mut } } diff --git a/core/src/callstack.rs b/core/src/callstack.rs index 8e20462a..ef64d5a3 100644 --- a/core/src/callstack.rs +++ b/core/src/callstack.rs @@ -32,6 +32,13 @@ impl Entrypoint { pub struct Callstack(Vec); impl Callstack { + pub fn first(&self) -> CallstackElement { + self.0 + .first() + .expect("Not enough elements on callstack") + .clone() + } + pub fn pop(&mut self) -> Option { self.0.pop() } @@ -67,4 +74,8 @@ impl Callstack { pub fn len(&self) -> usize { self.0.len() } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } } diff --git a/core/src/contract_container.rs b/core/src/contract_container.rs index 5bfa0463..13a0b07c 100644 --- a/core/src/contract_container.rs +++ b/core/src/contract_container.rs @@ -1,7 +1,7 @@ +use crate::prelude::*; +use crate::{CallDef, EntryPointsCaller, OdraError, VmError}; use casper_types::bytesrepr::Bytes; use casper_types::{NamedArg, RuntimeArgs}; -use crate::{CallDef, EntryPointsCaller, OdraError, VmError}; -use crate::prelude::*; #[doc(hidden)] pub type EntrypointCall = fn(String, &RuntimeArgs) -> Vec; @@ -10,15 +10,14 @@ pub type EntrypointArgs = Vec; #[derive(Clone)] pub struct ContractContainer { - name: String, - + _name: String, entry_points_caller: EntryPointsCaller } impl ContractContainer { pub fn new(name: &str, entry_points_caller: EntryPointsCaller) -> Self { Self { - name: String::from(name), + _name: String::from(name), entry_points_caller } } @@ -33,7 +32,7 @@ impl ContractContainer { self.entry_points_caller.call(call_def) } - fn validate_args(&self, args: &[String], input_args: &RuntimeArgs) -> Result<(), OdraError> { + fn _validate_args(&self, args: &[String], input_args: &RuntimeArgs) -> Result<(), OdraError> { // TODO: What's the purpose of this code? Is it needed? let named_args = input_args .named_args() @@ -56,13 +55,13 @@ impl ContractContainer { #[cfg(test)] mod tests { + use super::{ContractContainer, EntrypointArgs, EntrypointCall}; use crate::prelude::{collections::*, *}; use crate::{ casper_types::{runtime_args, RuntimeArgs}, OdraError, VmError }; use crate::{EntryPointsCaller, HostEnv}; - use super::{ContractContainer, EntrypointArgs, EntrypointCall}; #[test] fn test_call_wrong_entrypoint() { diff --git a/core/src/host_context.rs b/core/src/host_context.rs index 7b9080ec..df8920ed 100644 --- a/core/src/host_context.rs +++ b/core/src/host_context.rs @@ -28,11 +28,7 @@ pub trait HostContext { init_args: Option, entry_points_caller: EntryPointsCaller ) -> Address; - fn register_contract( - &self, - address: Address, - entry_points_caller: EntryPointsCaller - ); + fn register_contract(&self, address: Address, entry_points_caller: EntryPointsCaller); fn contract_env(&self) -> ContractEnv; fn print_gas_report(&self); fn last_call_gas_cost(&self) -> u64; diff --git a/core/src/lib.rs b/core/src/lib.rs index 1aa5912b..415ff10f 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -7,10 +7,13 @@ pub mod address; pub mod arithmetic; pub mod call_def; pub mod call_result; +pub mod callstack; pub mod consts; +pub mod contract_container; mod contract_context; pub mod contract_def; mod contract_env; +pub mod contract_register; pub mod crypto; pub mod entry_point_callback; pub mod error; @@ -29,9 +32,6 @@ mod unchecked_getter; mod unwrap_or_revert; pub mod utils; pub mod variable; -pub mod contract_container; -pub mod contract_register; -pub mod callstack; pub use address::{Address, OdraAddress}; pub use call_def::CallDef; diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 627122ae..7588cf0d 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -27,6 +27,10 @@ test = false name = "erc20_on_livenet" path = "bin/erc20_on_livenet.rs" +[[bin]] +name = "nested_modules_on_livenet" +path = "bin/nested_modules_on_livenet.rs" + [profile.release] codegen-units = 1 lto = true diff --git a/examples/bin/erc20_on_livenet.rs b/examples/bin/erc20_on_livenet.rs index f0f242fb..ca2e32e8 100644 --- a/examples/bin/erc20_on_livenet.rs +++ b/examples/bin/erc20_on_livenet.rs @@ -1,6 +1,6 @@ -use std::str::FromStr; use odra::{Address, HostEnv, U256}; use odra_modules::erc20::{Erc20Deployer, Erc20HostRef}; +use std::str::FromStr; fn main() { let env = odra_casper_livenet_env::livenet_env(); @@ -9,7 +9,6 @@ fn main() { let recipient = "hash-2c4a6ce0da5d175e9638ec0830e01dd6cf5f4b1fbb0724f7d2d9de12b1e0f840"; let recipient = Address::from_str(recipient).unwrap(); - // Uncomment to deploy new contract. // let token = deploy_new(&env); // println!("Token address: {}", token.address()); @@ -40,4 +39,4 @@ fn load(env: &HostEnv) -> Erc20HostRef { let address = "hash-d26fcbd2106e37be975d2045c580334a6d7b9d0a241c2358a4db970dfd516945"; let address = Address::from_str(address).unwrap(); Erc20Deployer::load(env, address) -} \ No newline at end of file +} diff --git a/examples/bin/nested_modules_on_livenet.rs b/examples/bin/nested_modules_on_livenet.rs new file mode 100644 index 00000000..cd1e1414 --- /dev/null +++ b/examples/bin/nested_modules_on_livenet.rs @@ -0,0 +1,48 @@ +use odra::{Address, HostEnv}; +use odra_examples::features::module_nesting::{ + NestedOdraTypesContractDeployer, NestedOdraTypesContractHostRef, OperationResult, Status +}; +use std::process::exit; +use std::str::FromStr; + +fn main() { + let env = odra_casper_livenet_env::livenet_env(); + + let owner = env.caller(); + let recipient = "hash-2c4a6ce0da5d175e9638ec0830e01dd6cf5f4b1fbb0724f7d2d9de12b1e0f840"; + let recipient = Address::from_str(recipient).unwrap(); + + // Uncomment to deploy new contract. + // let mut token = deploy_new(&env); + // println!("Token address: {}", token.address().to_string()); + + // Load existing contract. + let mut token = load(&env); + + println!("Current generation: {:?}", token.current_generation()); + println!("Latest result: {:?}", token.latest_result()); + + exit(0); + + println!("Saving operation result"); + env.set_gas(3_000_000_000u64); + token.save_operation_result(OperationResult { + id: 0, + status: Status::Success, + description: "Zero is a success".to_string() + }); + + println!("Current generation: {:?}", token.current_generation()); + println!("Latest result: {:?}", token.latest_result()); +} + +fn _deploy_new(env: &HostEnv) -> NestedOdraTypesContractHostRef { + env.set_gas(100_000_000_000u64); + NestedOdraTypesContractDeployer::init(env) +} + +fn load(env: &HostEnv) -> NestedOdraTypesContractHostRef { + let address = "hash-da858ca065eb039b10085467673ee896f853fdc5e629b38ae3f7746e3bab4dca"; + let address = Address::from_str(address).unwrap(); + NestedOdraTypesContractDeployer::load(env, address) +} diff --git a/examples/src/features/module_nesting.rs b/examples/src/features/module_nesting.rs index d4ee7eea..c977da34 100644 --- a/examples/src/features/module_nesting.rs +++ b/examples/src/features/module_nesting.rs @@ -70,9 +70,9 @@ pub enum Status { #[derive(OdraType, PartialEq, Debug)] pub struct OperationResult { - id: u32, - status: Status, - description: String + pub id: u32, + pub status: Status, + pub description: String } #[derive(Event, PartialEq, Debug)] diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index 3d694e4c..7a61a1d9 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -1,33 +1,28 @@ use std::{fs, path::PathBuf, str::from_utf8_unchecked, time::Duration}; -use blake2::{ - digest::{Update, VariableOutput}, - VarBlake2b -}; use casper_execution_engine::core::engine_state::ExecutableDeployItem; use casper_hashing::Digest; use jsonrpc_lite::JsonRpc; -use odra_core::{consts::*, casper_types::{ - bytesrepr::{Bytes, FromBytes, ToBytes}, - runtime_args, ContractHash, ContractPackageHash, ExecutionResult, Key as CasperKey, - PublicKey, RuntimeArgs, SecretKey, TimeDiff, Timestamp, U512, -}, Address, CallDef, OdraError, CLType}; +use odra_core::{ + casper_types::{ + bytesrepr::{Bytes, FromBytes, ToBytes}, + runtime_args, ContractHash, ContractPackageHash, ExecutionResult, Key as CasperKey, + PublicKey, RuntimeArgs, SecretKey, TimeDiff, Timestamp, U512 + }, + consts::*, + Address, CallDef +}; use serde::de::DeserializeOwned; use serde_json::{json, Value}; -use odra_core::CLType::List; - -use crate::{ - casper_node_port::{ - rpcs::{ - DictionaryIdentifier, GetDeployParams, GetDeployResult, GetDictionaryItemParams, - GetDictionaryItemResult, GetStateRootHashResult, GlobalStateIdentifier, - PutDeployResult, QueryGlobalStateParams, QueryGlobalStateResult - }, - Deploy, DeployHash + +use crate::casper_node_port::{ + rpcs::{ + DictionaryIdentifier, GetDeployParams, GetDeployResult, GetDictionaryItemParams, + GetDictionaryItemResult, GetStateRootHashResult, GlobalStateIdentifier, PutDeployResult, + QueryGlobalStateParams, QueryGlobalStateResult }, + Deploy, DeployHash }; -use crate::casper_node_port::rpcs::DictionaryIdentifier::Dictionary; -use crate::casper_node_port::rpcs::StoredValue::CLValue; use crate::log; @@ -50,7 +45,7 @@ pub struct CasperClient { node_address: String, chain_name: String, secret_key: SecretKey, - gas: U512, + gas: U512 } impl CasperClient { @@ -61,7 +56,7 @@ impl CasperClient { node_address: get_env_variable(ENV_NODE_ADDRESS), chain_name: get_env_variable(ENV_CHAIN_NAME), secret_key: SecretKey::from_file(get_env_variable(ENV_SECRET_KEY)).unwrap(), - gas: U512::zero(), + gas: U512::zero() } } @@ -93,6 +88,22 @@ impl CasperClient { Address::from(self.public_key()) } + pub fn get_block_time(&self) -> u64 { + let request = json!( + { + "jsonrpc": "2.0", + "method": "info_get_status", + "id": 1, + } + ); + let result: serde_json::Value = self.post_request(request).unwrap(); + let result = result["result"]["last_added_block_info"]["timestamp"] + .as_str() + .unwrap(); + let timestamp: u64 = result.parse().unwrap(); + timestamp + } + /// Query the node for the current state root hash. pub fn get_state_root_hash(&self) -> Digest { let request = json!( @@ -133,12 +144,7 @@ impl CasperClient { } /// Query the contract for the dictionary value. - pub fn get_dict_value( - &self, - address: Address, - seed: &[u8], - key: &[u8] - ) -> Option { + pub fn get_dict_value(&self, address: Address, key: &[u8]) -> Option { // let key = LivenetKeyMaker::to_dictionary_key(seed, key).unwrap(); // SAFETY: we know the key maker creates a string of valid UTF-8 characters. let key = unsafe { from_utf8_unchecked(key) }; @@ -212,11 +218,7 @@ impl CasperClient { address } - pub fn deploy_entrypoint_call_with_proxy( - &self, - addr: Address, - call_def: CallDef - ) -> Bytes { + pub fn deploy_entrypoint_call_with_proxy(&self, addr: Address, call_def: CallDef) -> Bytes { log::info(format!( "Calling {:?} with entrypoint \"{}\".", addr.to_string(), @@ -230,16 +232,18 @@ impl CasperClient { // }; 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()), - ATTACHED_VALUE_ARG => call_def.amount, - AMOUNT_ARG => call_def.amount, - }; + 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()), + ATTACHED_VALUE_ARG => call_def.amount, + AMOUNT_ARG => call_def.amount, + }; let session = ExecutableDeployItem::ModuleBytes { - module_bytes: include_bytes!("../../test-vm/resources/proxy_caller_with_return.wasm").to_vec().into(), - args, + module_bytes: include_bytes!("../../test-vm/resources/proxy_caller_with_return.wasm") + .to_vec() + .into(), + args }; let deploy = self.new_deploy(session, self.gas); @@ -255,7 +259,7 @@ impl CasperClient { ); let response: PutDeployResult = self.post_request(request).unwrap(); let deploy_hash = response.deploy_hash; - let result = self.wait_for_deploy_hash(deploy_hash); + self.wait_for_deploy_hash(deploy_hash); let r = self.query_result(&CasperKey::Account(self.public_key().to_account_hash())); let result_as_json = serde_json::to_value(r).unwrap(); @@ -267,16 +271,8 @@ impl CasperClient { value } - pub fn call_without_deploy(&self, address: Address, call_def: CallDef) -> Result { - todo!("call_without_deploy"); - } - /// Deploy the entrypoint call. - pub fn deploy_entrypoint_call( - &self, - addr: Address, - call_def: CallDef - ) { + pub fn deploy_entrypoint_call(&self, addr: Address, call_def: CallDef) { log::info(format!( "Calling {:?} with entrypoint \"{}\".", addr.to_string(), @@ -468,7 +464,9 @@ impl Default for CasperClient { /// 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 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() { @@ -483,21 +481,6 @@ fn find_wasm_file_path(wasm_file_name: &str) -> PathBuf { panic!("Wasm not found"); } -pub struct LivenetKeyMaker; - -impl LivenetKeyMaker { - fn blake2b(preimage: &[u8]) -> [u8; 32] { - let mut result = [0; 32]; - let mut hasher = VarBlake2b::new(32).expect("should create hasher"); - - hasher.update(preimage); - hasher.finalize_variable(|slice| { - result.copy_from_slice(slice); - }); - result - } -} - #[cfg(test)] mod tests { use std::str::FromStr; @@ -517,20 +500,6 @@ mod tests { const ACCOUNT_HASH: &str = "hash-2c4a6ce0da5d175e9638ec0830e01dd6cf5f4b1fbb0724f7d2d9de12b1e0f840"; - #[test] - #[ignore] - pub fn client_works() { - let contract_hash = Address::from_str(CONTRACT_PACKAGE_HASH).unwrap(); - let result: Option = - CasperClient::new().get_variable_value(contract_hash, b"name_contract"); - assert_eq!(result.unwrap().as_str(), "Plascoin"); - - let account = Address::from_str(ACCOUNT_HASH).unwrap(); - let balance: Option = - CasperClient::new().get_dict_value(contract_hash, b"balances_contract", &account); - assert!(balance.is_some()); - } - #[test] #[ignore] pub fn state_root_hash() { diff --git a/odra-casper/casper-client/src/lib.rs b/odra-casper/casper-client/src/lib.rs index ff523b58..7cd130f4 100644 --- a/odra-casper/casper-client/src/lib.rs +++ b/odra-casper/casper-client/src/lib.rs @@ -1,4 +1,4 @@ #![feature(once_cell)] -mod casper_node_port; pub mod casper_client; -pub mod log; \ No newline at end of file +mod casper_node_port; +pub mod log; diff --git a/odra-casper/casper-client/src/log.rs b/odra-casper/casper-client/src/log.rs index 249cbd19..0bc3bad7 100644 --- a/odra-casper/casper-client/src/log.rs +++ b/odra-casper/casper-client/src/log.rs @@ -1,14 +1,14 @@ - /// Info message. - pub fn info>(message: T) { - prettycli::info(message.as_ref()); - } +/// Info message. +pub fn info>(message: T) { + prettycli::info(message.as_ref()); +} - /// Error message. - pub fn error>(message: T) { - prettycli::error(message.as_ref()); - } +/// Error message. +pub fn error>(message: T) { + prettycli::error(message.as_ref()); +} - /// Wait message. - pub fn wait>(message: T) { - prettycli::wait(message.as_ref()); - } +/// Wait message. +pub fn wait>(message: T) { + prettycli::wait(message.as_ref()); +} diff --git a/odra-casper/livenet-env/src/lib.rs b/odra-casper/livenet-env/src/lib.rs index 91a4e805..ace86896 100644 --- a/odra-casper/livenet-env/src/lib.rs +++ b/odra-casper/livenet-env/src/lib.rs @@ -1,6 +1,6 @@ #![feature(once_cell)] -pub mod livenet_host_env; pub mod livenet_contract_env; +pub mod livenet_host_env; use livenet_host_env::LivenetEnv; use odra_core::HostEnv; diff --git a/odra-casper/livenet-env/src/livenet_contract_env.rs b/odra-casper/livenet-env/src/livenet_contract_env.rs index 553e04f9..81e62127 100644 --- a/odra-casper/livenet-env/src/livenet_contract_env.rs +++ b/odra-casper/livenet-env/src/livenet_contract_env.rs @@ -1,21 +1,22 @@ -use odra_casper_client::casper_client::CasperClient; use blake2::digest::VariableOutput; use blake2::Blake2bVar; +use odra_casper_client::casper_client::CasperClient; +use odra_core::callstack::{Callstack, CallstackElement}; use odra_core::prelude::*; use odra_core::{Address, Bytes, OdraError, U512}; use odra_core::{CallDef, ContractContext}; use std::io::Write; -use odra_core::callstack::{Callstack, CallstackElement}; - pub struct LivenetContractEnv { casper_client: Rc>, - callstack: Rc>, + callstack: Rc> } impl ContractContext for LivenetContractEnv { fn get_value(&self, key: &[u8]) -> Option { - self.casper_client.borrow().get_value(self.callstack.borrow().current().address(), key) + self.casper_client + .borrow() + .get_value(self.callstack.borrow().current().address(), key) } fn set_value(&self, _key: &[u8], _value: Bytes) { @@ -23,23 +24,23 @@ impl ContractContext for LivenetContractEnv { } fn caller(&self) -> Address { - todo!() + *self.callstack.borrow().first().address() } fn self_address(&self) -> Address { - todo!() + *self.callstack.borrow().current().address() } fn call_contract(&self, _address: Address, _call_def: CallDef) -> Bytes { - todo!() + todo!("call_contract") } fn get_block_time(&self) -> u64 { - todo!() + self.casper_client.borrow().get_block_time() } fn attached_value(&self) -> U512 { - todo!() + self.callstack.borrow().attached_value() } fn emit_event(&self, _event: &Bytes) { @@ -51,8 +52,12 @@ impl ContractContext for LivenetContractEnv { } fn revert(&self, error: OdraError) -> ! { - dbg!(error); - todo!() + let mut revert_msg = String::from(""); + if let CallstackElement::Entrypoint(ep) = self.callstack.borrow().current() { + revert_msg = format!("{:?}::{}", ep.address, ep.call_def.entry_point); + } + + panic!("Revert: {:?} - {}", error, revert_msg); } fn get_named_arg_bytes(&self, name: &str) -> Bytes { @@ -84,7 +89,13 @@ impl ContractContext for LivenetContractEnv { } impl LivenetContractEnv { - pub fn new(casper_client: Rc>, callstack: Rc>) -> Rc> { - Rc::new(RefCell::new(Self { casper_client, callstack })) + pub fn new( + casper_client: Rc>, + callstack: Rc> + ) -> Rc> { + Rc::new(RefCell::new(Self { + casper_client, + callstack + })) } -} \ No newline at end of file +} diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index 426d7563..00cac6a1 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -1,22 +1,22 @@ -use std::fmt::format; -use std::sync::{Arc, RwLock}; +use crate::livenet_contract_env::LivenetContractEnv; use odra_casper_client::casper_client::CasperClient; +use odra_core::callstack::{Callstack, CallstackElement, Entrypoint}; +use odra_core::contract_container::ContractContainer; +use odra_core::contract_register::ContractRegister; use odra_core::event::EventError; use odra_core::prelude::*; use odra_core::{ Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, RuntimeArgs, U512 }; -use odra_core::callstack::{Callstack, CallstackElement, Entrypoint}; -use odra_core::contract_container::ContractContainer; -use odra_core::contract_register::ContractRegister; -use crate::livenet_contract_env::LivenetContractEnv; +use std::sync::{Arc, RwLock}; +use std::thread::sleep; pub struct LivenetEnv { casper_client: Rc>, contract_register: Arc>, contract_env: Rc, - callstack: Rc>, + callstack: Rc> } impl LivenetEnv { @@ -27,14 +27,20 @@ impl LivenetEnv { pub fn new_instance() -> Self { let casper_client: Rc> = Default::default(); let callstack: Rc> = Default::default(); - let livenet_contract_env = LivenetContractEnv::new(casper_client.clone(), callstack.clone()); + let livenet_contract_env = + LivenetContractEnv::new(casper_client.clone(), callstack.clone()); let contract_env = Rc::new(ContractEnv::new(0, livenet_contract_env)); - Self { casper_client, contract_register: Default::default(), contract_env, callstack } + Self { + casper_client, + contract_register: Default::default(), + contract_env, + callstack + } } } impl HostContext for LivenetEnv { - fn set_caller(&self, caller: Address) { + fn set_caller(&self, _caller: Address) { todo!() } @@ -46,23 +52,25 @@ impl HostContext for LivenetEnv { self.casper_client.borrow().caller() } - fn get_account(&self, index: usize) -> Address { + fn get_account(&self, _index: usize) -> Address { todo!() } - fn balance_of(&self, address: &Address) -> U512 { + fn balance_of(&self, _address: &Address) -> U512 { todo!() } fn advance_block_time(&self, time_diff: u64) { - panic!("Cannot advance block time in LivenetEnv") + // Todo: implement logging, especially for such cases: + // info!("advance_block_time called - Waiting for {} ms", time_diff); + sleep(std::time::Duration::from_millis(time_diff)); } - fn get_event(&self, contract_address: &Address, index: i32) -> Result { + fn get_event(&self, _contract_address: &Address, _index: i32) -> Result { todo!() } - fn get_events_count(&self, contract_address: &Address) -> u32 { + fn get_events_count(&self, _contract_address: &Address) -> u32 { // TODO: implement 0 } @@ -74,22 +82,39 @@ impl HostContext for LivenetEnv { use_proxy: bool ) -> Result { if !call_def.is_mut() { - self.callstack.borrow_mut().push(CallstackElement::Entrypoint(Entrypoint::new(*address, call_def.clone()))); - let result = self.contract_register.read().unwrap().call(address, call_def); + self.callstack + .borrow_mut() + .push(CallstackElement::Entrypoint(Entrypoint::new( + *address, + call_def.clone() + ))); + let result = self + .contract_register + .read() + .unwrap() + .call(address, call_def); self.callstack.borrow_mut().pop(); - return result + return result; } match use_proxy { - true => Ok(self.casper_client.borrow_mut().deploy_entrypoint_call_with_proxy(*address, call_def)), + true => Ok(self + .casper_client + .borrow_mut() + .deploy_entrypoint_call_with_proxy(*address, call_def)), false => { - self.casper_client.borrow_mut().deploy_entrypoint_call(*address, call_def); + self.casper_client + .borrow_mut() + .deploy_entrypoint_call(*address, call_def); Ok(Default::default()) - }, + } } } fn register_contract(&self, address: Address, entry_points_caller: EntryPointsCaller) { - self.contract_register.write().unwrap().add(address, ContractContainer::new(&address.to_string(), entry_points_caller)); + self.contract_register.write().unwrap().add( + address, + ContractContainer::new(&address.to_string(), entry_points_caller) + ); } fn new_contract( @@ -100,12 +125,16 @@ impl HostContext for LivenetEnv { ) -> Address { let mut args = match init_args { None => RuntimeArgs::new(), - Some(args) => args, + Some(args) => args }; // todo: move this up the stack args.insert("odra_cfg_is_upgradable", false).unwrap(); - args.insert("odra_cfg_allow_key_override", false).unwrap(); - args.insert("odra_cfg_package_hash_key_name", format!("{}_package_hash", name)).unwrap(); + args.insert("odra_cfg_allow_key_override", true).unwrap(); + args.insert( + "odra_cfg_package_hash_key_name", + format!("{}_package_hash", name) + ) + .unwrap(); let address = self.casper_client.borrow_mut().deploy_wasm(name, args); self.register_contract(address, entry_points_caller); address @@ -125,11 +154,11 @@ impl HostContext for LivenetEnv { 0 } - fn sign_message(&self, message: &Bytes, address: &Address) -> Bytes { + fn sign_message(&self, _message: &Bytes, _address: &Address) -> Bytes { todo!() } - fn public_key(&self, address: &Address) -> PublicKey { + fn public_key(&self, _address: &Address) -> PublicKey { todo!() } } diff --git a/odra-casper/test-vm/src/vm/casper_vm.rs b/odra-casper/test-vm/src/vm/casper_vm.rs index 38f7d11a..75e981b5 100644 --- a/odra-casper/test-vm/src/vm/casper_vm.rs +++ b/odra-casper/test-vm/src/vm/casper_vm.rs @@ -265,11 +265,11 @@ impl CasperVm { &mut self, name: &str, init_args: Option, - entry_points_caller: Option + entry_points_caller: EntryPointsCaller ) -> Address { let wasm_path = format!("{}.wasm", name); let package_hash_key_name = format!("{}_package_hash", name); - let mut args = init_args.clone().unwrap_or(runtime_args! {}); + let mut args = init_args.unwrap_or(runtime_args! {}); args.insert(PACKAGE_HASH_KEY_NAME_ARG, package_hash_key_name.clone()) .unwrap(); args.insert(ALLOW_KEY_OVERRIDE_ARG, true).unwrap(); diff --git a/odra-macros/src/ast/deployer_item.rs b/odra-macros/src/ast/deployer_item.rs index 2f92fad0..172e5af9 100644 --- a/odra-macros/src/ast/deployer_item.rs +++ b/odra-macros/src/ast/deployer_item.rs @@ -2,7 +2,10 @@ use derive_try_from::TryFromRef; use crate::{ir::ModuleImplIR, utils}; -use super::deployer_utils::{CallEpcExpr, EpcSignature, DeployerInitSignature, EntrypointCallerExpr, HostRefInstanceExpr, NewContractExpr, DeployerLoadSignature, LoadContractExpr}; +use super::deployer_utils::{ + CallEpcExpr, DeployerInitSignature, DeployerLoadSignature, EntrypointCallerExpr, EpcSignature, + HostRefInstanceExpr, LoadContractExpr, NewContractExpr +}; #[derive(syn_derive::ToTokens)] struct DeployStructItem { @@ -64,7 +67,7 @@ pub struct ContractEpcFn { #[default] braces: syn::token::Brace, #[syn(in = braces)] - caller: EntrypointCallerExpr, + caller: EntrypointCallerExpr } #[derive(syn_derive::ToTokens, TryFromRef)] diff --git a/odra-macros/src/ast/deployer_utils.rs b/odra-macros/src/ast/deployer_utils.rs index d0d8ae22..8be21e54 100644 --- a/odra-macros/src/ast/deployer_utils.rs +++ b/odra-macros/src/ast/deployer_utils.rs @@ -7,7 +7,6 @@ use crate::{ use proc_macro2::TokenStream; use quote::ToTokens; use syn::parse_quote; -use syn::punctuated::Pair::Punctuated; #[derive(syn_derive::ToTokens)] pub struct DeployerInitSignature { @@ -61,7 +60,7 @@ impl TryFrom<&'_ ModuleImplIR> for DeployerLoadSignature { let env = utils::ident::env(); let ty_address = utils::ty::address(); - let mut inputs = syn::punctuated::Punctuated::new(); + let mut inputs = syn::punctuated::Punctuated::new(); inputs.push(parse_quote!(#env: &#ty_host_env)); inputs.push(parse_quote!(address: #ty_address)); @@ -75,7 +74,6 @@ impl TryFrom<&'_ ModuleImplIR> for DeployerLoadSignature { } } - #[derive(syn_derive::ToTokens)] pub struct EpcSignature { fn_token: syn::token::Fn, @@ -90,7 +88,7 @@ pub struct EpcSignature { impl TryFrom<&'_ ModuleImplIR> for EpcSignature { type Error = syn::Error; - fn try_from(module: &'_ ModuleImplIR) -> Result { + fn try_from(_: &'_ ModuleImplIR) -> Result { let epc_ident = utils::ty::entry_points_caller(); let ty_host_env = utils::ty::host_env(); let env = utils::ident::env(); @@ -109,7 +107,7 @@ impl TryFrom<&'_ ModuleImplIR> for EpcSignature { #[derive(syn_derive::ToTokens)] pub struct EntrypointCallerExpr { - caller_expr: syn::Expr, + caller_expr: syn::Expr } impl TryFrom<&'_ ModuleImplIR> for EntrypointCallerExpr { @@ -117,7 +115,7 @@ impl TryFrom<&'_ ModuleImplIR> for EntrypointCallerExpr { fn try_from(module: &'_ ModuleImplIR) -> Result { Ok(Self { - caller_expr: Self::entrypoint_caller(module)?, + caller_expr: Self::entrypoint_caller(module)? }) } } @@ -159,29 +157,17 @@ pub struct CallEpcExpr { impl TryFrom<&'_ ModuleImplIR> for CallEpcExpr { type Error = syn::Error; - fn try_from(module: &'_ ModuleImplIR) -> Result { - let epc_ident = utils::ident::epc(); - let env_ident = utils::ident::env(); - let args = module - .constructor() - .map(|f| fn_utils::runtime_args_block(&f, ref_utils::insert_arg_stmt)) - .map(utils::expr::some) - .unwrap_or_else(utils::expr::none); - - + fn try_from(_: &'_ ModuleImplIR) -> Result { Ok(Self { let_token: Default::default(), ident: utils::ident::caller(), assign_token: Default::default(), - epc_expression: parse_quote!( - Self::epc(env) - ), + epc_expression: parse_quote!(Self::epc(env)), semi_token: Default::default() }) } } - #[derive(syn_derive::ToTokens)] pub struct NewContractExpr { let_token: syn::token::Let, @@ -231,7 +217,7 @@ pub struct LoadContractExpr { impl TryFrom<&'_ ModuleImplIR> for LoadContractExpr { type Error = syn::Error; - fn try_from(module: &'_ ModuleImplIR) -> Result { + fn try_from(_: &'_ ModuleImplIR) -> Result { let env_ident = utils::ident::env(); let address_ident = utils::ident::address(); let caller_ident = utils::ident::caller(); diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index f7e42735..0b6acedd 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -193,7 +193,7 @@ impl ModuleImplIR { module_ident.span() )) } - + pub fn contract_ref_ident(&self) -> Result { let module_ident = self.module_ident()?; Ok(Ident::new( diff --git a/odra-test/Cargo.toml b/odra-test/Cargo.toml index 02d9fd5d..10683dd9 100644 --- a/odra-test/Cargo.toml +++ b/odra-test/Cargo.toml @@ -11,3 +11,4 @@ repository.workspace = true odra-core = { path = "../core" } odra-casper-test-vm = { path = "../odra-casper/test-vm" } odra-vm = { path = "../odra-vm" } +odra-casper-livenet-env = { path = "../odra-casper/livenet-env" } \ No newline at end of file diff --git a/odra-test/src/lib.rs b/odra-test/src/lib.rs index 3da8da15..7ab30a7b 100644 --- a/odra-test/src/lib.rs +++ b/odra-test/src/lib.rs @@ -13,6 +13,7 @@ pub fn env() -> odra_core::HostEnv { let backend: String = std::env::var("ODRA_BACKEND").unwrap_or(String::from("odra-vm")); match backend.as_str() { "casper" => casper_env(), + "livenet" => odra_casper_livenet_env::livenet_env(), _ => odra_env() } } diff --git a/odra-vm/src/odra_vm_host.rs b/odra-vm/src/odra_vm_host.rs index 88b9bfef..4b0b5b52 100644 --- a/odra-vm/src/odra_vm_host.rs +++ b/odra-vm/src/odra_vm_host.rs @@ -74,7 +74,7 @@ impl HostContext for OdraVmHost { let address = self .vm .borrow() - .register_contract(name, entry_points_caller.unwrap()); + .register_contract(name, entry_points_caller); if let Some(init_args) = init_args { let _ = self.call_contract( diff --git a/odra-vm/src/vm/odra_vm.rs b/odra-vm/src/vm/odra_vm.rs index 6faca2f9..bc94d367 100644 --- a/odra-vm/src/vm/odra_vm.rs +++ b/odra-vm/src/vm/odra_vm.rs @@ -5,9 +5,14 @@ use std::sync::{Arc, RwLock}; use anyhow::Result; use odra_core::call_def::CallDef; +use odra_core::callstack::CallstackElement; +use odra_core::callstack::CallstackElement::Entrypoint; +use odra_core::contract_container::ContractContainer; +use odra_core::contract_register::ContractRegister; use odra_core::entry_point_callback::EntryPointsCaller; use odra_core::event::EventError; use odra_core::{ + callstack, casper_types::{ bytesrepr::{FromBytes, ToBytes}, U512 @@ -15,10 +20,7 @@ use odra_core::{ Address, Bytes, ExecutionError, PublicKey, SecretKey }; use odra_core::{OdraError, VmError}; -use odra_core::contract_container::ContractContainer; -use odra_core::contract_register::ContractRegister; -use super::callstack::{CallstackElement, Entrypoint}; use super::odra_vm_state::OdraVmState; #[derive(Default)] @@ -92,7 +94,8 @@ impl OdraVm { } // Put the address on stack. - let element = CallstackElement::Entrypoint(Entrypoint::new(address, call_def.clone())); + let element = + CallstackElement::Entrypoint(callstack::Entrypoint::new(address, call_def.clone())); state.push_callstack_element(element); } diff --git a/odra-vm/src/vm/odra_vm_state.rs b/odra-vm/src/vm/odra_vm_state.rs index 4c62f4c7..dca2bebd 100644 --- a/odra-vm/src/vm/odra_vm_state.rs +++ b/odra-vm/src/vm/odra_vm_state.rs @@ -1,6 +1,7 @@ use super::balance::AccountBalance; use super::storage::Storage; use anyhow::Result; +use odra_core::callstack::{Callstack, CallstackElement}; use odra_core::casper_types::account::AccountHash; use odra_core::casper_types::bytesrepr::Error; use odra_core::crypto::generate_key_pairs; @@ -9,7 +10,6 @@ use odra_core::{ Address, Bytes, ExecutionError, FromBytes, OdraError, PublicKey, SecretKey, ToBytes, U512 }; use std::collections::BTreeMap; -use odra_core::callstack::{Callstack, CallstackElement}; pub struct OdraVmState { storage: Storage, From fef866a208e667a35ffd73408733fd565f3843fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Fri, 19 Jan 2024 16:52:17 +0100 Subject: [PATCH 10/24] Wip --- examples/bin/{nested_modules_on_livenet.rs => livenet_tests.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/bin/{nested_modules_on_livenet.rs => livenet_tests.rs} (100%) diff --git a/examples/bin/nested_modules_on_livenet.rs b/examples/bin/livenet_tests.rs similarity index 100% rename from examples/bin/nested_modules_on_livenet.rs rename to examples/bin/livenet_tests.rs From 881188dfc73893fc6fa349c8d01d761cf5edd051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Mon, 22 Jan 2024 14:45:09 +0100 Subject: [PATCH 11/24] Wip --- examples/Cargo.toml | 14 +++- examples/Odra.toml | 4 ++ examples/bin/erc20_on_livenet.rs | 6 +- examples/bin/livenet_tests.rs | 65 ++++++++++--------- examples/src/features/livenet.rs | 38 +++++++++++ examples/src/features/mod.rs | 1 + .../casper-client/src/casper_client.rs | 47 +++++++++++--- .../src/casper_node_port/contract.rs | 42 ++++++++++++ .../casper-client/src/casper_node_port/mod.rs | 1 + .../src/casper_node_port/rpcs.rs | 5 +- odra-casper/casper-client/src/lib.rs | 2 +- odra-casper/livenet-env/Cargo.toml | 2 +- .../livenet-env/src/livenet_contract_env.rs | 33 ++++++++-- .../livenet-env/src/livenet_host_env.rs | 47 +++++++++++--- 14 files changed, 245 insertions(+), 62 deletions(-) create mode 100644 examples/src/features/livenet.rs create mode 100644 odra-casper/casper-client/src/casper_node_port/contract.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 7588cf0d..13dacf1f 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -6,13 +6,17 @@ edition = "2021" [dependencies] odra = { path = "../odra", default-features = false } odra-modules = { path = "../modules", default-features = false } -odra-casper-livenet-env = { path = "../odra-casper/livenet-env" } sha3 = { version = "0.10.6", default-features = false } +odra-casper-livenet-env = { path = "../odra-casper/livenet-env", optional = true } [dev-dependencies] odra-test = { path = "../odra-test" } hex = "0.4.3" +[features] +default = [] +livenet = ["odra-casper-livenet-env"] + [[bin]] name = "odra_examples_build_contract" path = "bin/build_contract.rs" @@ -26,10 +30,14 @@ test = false [[bin]] name = "erc20_on_livenet" path = "bin/erc20_on_livenet.rs" +required-features = ["livenet"] +test = false [[bin]] -name = "nested_modules_on_livenet" -path = "bin/nested_modules_on_livenet.rs" +name = "livenet_tests" +path = "bin/livenet_tests.rs" +required-features = ["livenet"] +test = false [profile.release] codegen-units = 1 diff --git a/examples/Odra.toml b/examples/Odra.toml index 675356d7..1b04ea56 100644 --- a/examples/Odra.toml +++ b/examples/Odra.toml @@ -77,3 +77,7 @@ fqn = "odra_examples::features::access_control::MockModerated" [[contracts]] name = "PauseableCounter" fqn = "odra_examples::features::pauseable::PauseableCounter" + +[[contracts]] +name = "LivenetContract" +fqn = "odra_examples::features::livenet::LivenetContract" diff --git a/examples/bin/erc20_on_livenet.rs b/examples/bin/erc20_on_livenet.rs index ca2e32e8..94665650 100644 --- a/examples/bin/erc20_on_livenet.rs +++ b/examples/bin/erc20_on_livenet.rs @@ -17,9 +17,9 @@ fn main() { let mut token = load(&env); println!("Token name: {}", token.name()); - - env.set_gas(3_000_000_000u64); - token.transfer(recipient, U256::from(1000)); + // + // env.set_gas(3_000_000_000u64); + // token.transfer(recipient, U256::from(1000)); println!("Owner's balance: {:?}", token.balance_of(owner)); println!("Recipient's balance: {:?}", token.balance_of(recipient)); diff --git a/examples/bin/livenet_tests.rs b/examples/bin/livenet_tests.rs index cd1e1414..0b004afa 100644 --- a/examples/bin/livenet_tests.rs +++ b/examples/bin/livenet_tests.rs @@ -1,48 +1,53 @@ -use odra::{Address, HostEnv}; -use odra_examples::features::module_nesting::{ - NestedOdraTypesContractDeployer, NestedOdraTypesContractHostRef, OperationResult, Status -}; -use std::process::exit; use std::str::FromStr; +use odra::{Address, HostEnv}; +use odra_examples::features::livenet::{LivenetContractDeployer, LivenetContractHostRef}; fn main() { let env = odra_casper_livenet_env::livenet_env(); let owner = env.caller(); - let recipient = "hash-2c4a6ce0da5d175e9638ec0830e01dd6cf5f4b1fbb0724f7d2d9de12b1e0f840"; - let recipient = Address::from_str(recipient).unwrap(); - // Uncomment to deploy new contract. - // let mut token = deploy_new(&env); - // println!("Token address: {}", token.address().to_string()); + // Contract can be deployed + // env.set_gas(30_000_000_000u64); + // let contract = deploy_new(&env); + // println!("Contract address: {}", contract.address().to_string()); + + // Contract can be loaded + // let mut contract = load(&env, *contract.address()); + + let address = Address::from_str("hash-b2cf71fcbf69eea9e8bfc7b76fb388ccfdc7233fbe1358fbc0959ef14511d756").unwrap(); + let mut contract = load(&env, address); + + // 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); + + // 2. If the endpoint is mutable and returns something, it can be called through the proxy: + // let value = contract.pop_from_stack(); + // assert_eq!(value, 1); - // Load existing contract. - let mut token = load(&env); + // 3. If the endpoint is immutable, it can be called locally, querying only storage from livenet: + // assert_eq!(contract.owner(), owner); - println!("Current generation: {:?}", token.current_generation()); - println!("Latest result: {:?}", token.latest_result()); + // By querying livenet storage + // - we can also test the events + assert_eq!(env.events_count(contract.address()), 1); - exit(0); + // - we can test immutable crosscalls - println!("Saving operation result"); - env.set_gas(3_000_000_000u64); - token.save_operation_result(OperationResult { - id: 0, - status: Status::Success, - description: "Zero is a success".to_string() - }); + // - mutable crosscalls will require a deploy - println!("Current generation: {:?}", token.current_generation()); - println!("Latest result: {:?}", token.latest_result()); + // We can change the caller } -fn _deploy_new(env: &HostEnv) -> NestedOdraTypesContractHostRef { +fn deploy_new(env: &HostEnv) -> LivenetContractHostRef { env.set_gas(100_000_000_000u64); - NestedOdraTypesContractDeployer::init(env) + LivenetContractDeployer::init(env) } -fn load(env: &HostEnv) -> NestedOdraTypesContractHostRef { - let address = "hash-da858ca065eb039b10085467673ee896f853fdc5e629b38ae3f7746e3bab4dca"; - let address = Address::from_str(address).unwrap(); - NestedOdraTypesContractDeployer::load(env, address) +fn load(env: &HostEnv, address: Address) -> LivenetContractHostRef { + LivenetContractDeployer::load(env, address) } diff --git a/examples/src/features/livenet.rs b/examples/src/features/livenet.rs new file mode 100644 index 00000000..e1ae604f --- /dev/null +++ b/examples/src/features/livenet.rs @@ -0,0 +1,38 @@ +use odra::{Address, List, Module, ModuleWrapper, UnwrapOrRevert, Variable}; +use odra::prelude::*; +use odra_modules::access::Ownable; + +#[odra::module] +pub struct LivenetContract { + creator: Variable
, + ownable: ModuleWrapper, + stack: List, +} + +#[odra::module] +impl LivenetContract { + pub fn init(mut self) { + self.creator.set(self.env().caller()); + self.ownable.init(); + } + + pub fn transfer_ownership(&mut self, new_owner: Address) { + self.ownable.transfer_ownership(&new_owner); + } + + pub fn owner(&self) -> Address { + self.ownable.get_owner() + } + + pub fn push_on_stack(&mut self, value: u64) { + self.stack.push(value); + } + + pub fn pop_from_stack(&mut self) -> u64 { + self.stack.pop().unwrap_or_revert(&self.env()) + } + + pub fn get_stack_len(&self) -> u32 { + self.stack.len() + } +} \ No newline at end of file diff --git a/examples/src/features/mod.rs b/examples/src/features/mod.rs index 3bbeb726..8d6c8c18 100644 --- a/examples/src/features/mod.rs +++ b/examples/src/features/mod.rs @@ -12,3 +12,4 @@ pub mod reentrancy_guard; pub mod signature_verifier; pub mod storage; pub mod testing; +pub mod livenet; diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index 7a61a1d9..de95fcea 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -189,9 +189,9 @@ impl CasperClient { } /// Deploy the contract. - pub fn deploy_wasm(&self, wasm_file_name: &str, args: RuntimeArgs) -> Address { - log::info(format!("Deploying \"{}\".", wasm_file_name)); - let wasm_path = find_wasm_file_path(wasm_file_name); + pub fn deploy_wasm(&self, contract_name: &str, args: RuntimeArgs) -> Address { + 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), @@ -213,7 +213,7 @@ impl CasperClient { let deploy_hash = response.deploy_hash; self.wait_for_deploy_hash(deploy_hash); - let address = self.get_contract_address(wasm_file_name.strip_suffix(".wasm").unwrap()); + let address = self.get_contract_address(contract_name); log::info(format!("Contract {:?} deployed.", &address.to_string())); address } @@ -261,7 +261,7 @@ impl CasperClient { let deploy_hash = response.deploy_hash; self.wait_for_deploy_hash(deploy_hash); - let r = self.query_result(&CasperKey::Account(self.public_key().to_account_hash())); + let r = self.query_global_state(&CasperKey::Account(self.public_key().to_account_hash())); let result_as_json = serde_json::to_value(r).unwrap(); let result = result_as_json["stored_value"]["CLValue"]["bytes"] .as_str() @@ -300,12 +300,14 @@ impl CasperClient { self.wait_for_deploy_hash(deploy_hash); } - fn query_global_state(&self, key: &CasperKey) -> QueryGlobalStateResult { + pub fn query_global_state_path(&self, address: Address, path: String) -> Option { + let hash = self.query_global_state_for_contract_hash(address); + let key = CasperKey::Hash(hash.value()); let state_root_hash = self.get_state_root_hash(); let params = QueryGlobalStateParams { state_identifier: GlobalStateIdentifier::StateRootHash(state_root_hash), key: key.to_formatted_string(), - path: Vec::new() + path: vec![] }; let request = json!( { @@ -318,12 +320,12 @@ impl CasperClient { self.post_request(request).unwrap() } - fn query_result(&self, key: &CasperKey) -> Option { + pub fn query_global_state(&self, key: &CasperKey) -> QueryGlobalStateResult { let state_root_hash = self.get_state_root_hash(); let params = QueryGlobalStateParams { state_identifier: GlobalStateIdentifier::StateRootHash(state_root_hash), key: key.to_formatted_string(), - path: vec![RESULT_KEY.to_string()] + path: Vec::new() }; let request = json!( { @@ -371,6 +373,33 @@ impl CasperClient { }) } + pub fn query_dict_nk(&self, address: Address, key: &str) -> Option { + let state_root_hash = self.get_state_root_hash(); + let contract_hash = self.query_global_state_for_contract_hash(address); + 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("__events_length"), + dictionary_item_key: String::from(key) + } + }; + + let request = json!( + { + "jsonrpc": "2.0", + "method": "state_get_dictionary_item", + "params": params, + "id": 1, + } + ); + let result: Option = self.post_request(request); + None + } + fn wait_for_deploy_hash(&self, deploy_hash: DeployHash) -> ExecutionResult { let deploy_hash_str = format!("{:?}", deploy_hash.inner()); let time_diff = Duration::from_secs(15); diff --git a/odra-casper/casper-client/src/casper_node_port/contract.rs b/odra-casper/casper-client/src/casper_node_port/contract.rs new file mode 100644 index 00000000..c66b557b --- /dev/null +++ b/odra-casper/casper-client/src/casper_node_port/contract.rs @@ -0,0 +1,42 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use odra_core::casper_types::{ContractPackageHash, ContractWasmHash, EntryPoint, NamedKey, ProtocolVersion}; + + +/// 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 + } +} \ No newline at end of file diff --git a/odra-casper/casper-client/src/casper_node_port/mod.rs b/odra-casper/casper-client/src/casper_node_port/mod.rs index 7ae905ef..f6bf1a3b 100644 --- a/odra-casper/casper-client/src/casper_node_port/mod.rs +++ b/odra-casper/casper-client/src/casper_node_port/mod.rs @@ -10,6 +10,7 @@ pub mod deploy_header; pub mod error; pub mod rpcs; pub mod utils; +pub mod contract; pub use deploy::Deploy; pub use deploy_hash::DeployHash; diff --git a/odra-casper/casper-client/src/casper_node_port/rpcs.rs b/odra-casper/casper-client/src/casper_node_port/rpcs.rs index 5ef0f883..11bf29fd 100644 --- a/odra-casper/casper-client/src/casper_node_port/rpcs.rs +++ b/odra-casper/casper-client/src/casper_node_port/rpcs.rs @@ -4,6 +4,7 @@ use odra_core::casper_types::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::casper_node_port::contract::Contract; use super::{ account::Account, @@ -240,5 +241,7 @@ pub enum StoredValue { Account(Account), /// A contract definition, metadata, and security container. - ContractPackage(ContractPackage) + ContractPackage(ContractPackage), + + Contract(Contract) } diff --git a/odra-casper/casper-client/src/lib.rs b/odra-casper/casper-client/src/lib.rs index 7cd130f4..af2b96f7 100644 --- a/odra-casper/casper-client/src/lib.rs +++ b/odra-casper/casper-client/src/lib.rs @@ -1,4 +1,4 @@ #![feature(once_cell)] pub mod casper_client; -mod casper_node_port; +pub mod casper_node_port; pub mod log; diff --git a/odra-casper/livenet-env/Cargo.toml b/odra-casper/livenet-env/Cargo.toml index 5879d834..418edeeb 100644 --- a/odra-casper/livenet-env/Cargo.toml +++ b/odra-casper/livenet-env/Cargo.toml @@ -6,6 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -odra-core = { path = "../../core" } +odra-core = { path = "../../core", default-features = false } odra-casper-client = { path = "../casper-client" } blake2 = { workspace = true } \ No newline at end of file diff --git a/odra-casper/livenet-env/src/livenet_contract_env.rs b/odra-casper/livenet-env/src/livenet_contract_env.rs index 81e62127..fa851610 100644 --- a/odra-casper/livenet-env/src/livenet_contract_env.rs +++ b/odra-casper/livenet-env/src/livenet_contract_env.rs @@ -1,15 +1,18 @@ use blake2::digest::VariableOutput; use blake2::Blake2bVar; use odra_casper_client::casper_client::CasperClient; -use odra_core::callstack::{Callstack, CallstackElement}; +use odra_core::callstack::{Callstack, CallstackElement, Entrypoint}; +use odra_core::contract_register::ContractRegister; use odra_core::prelude::*; use odra_core::{Address, Bytes, OdraError, U512}; use odra_core::{CallDef, ContractContext}; use std::io::Write; +use std::sync::{Arc, RwLock}; pub struct LivenetContractEnv { casper_client: Rc>, - callstack: Rc> + callstack: Rc>, + contract_register: Arc> } impl ContractContext for LivenetContractEnv { @@ -31,8 +34,24 @@ impl ContractContext for LivenetContractEnv { *self.callstack.borrow().current().address() } - fn call_contract(&self, _address: Address, _call_def: CallDef) -> Bytes { - todo!("call_contract") + fn call_contract(&self, address: Address, call_def: CallDef) -> Bytes { + if call_def.is_mut() { + panic!("Cannot cross call mutable entrypoint from non-mutable entrypoint") + } + + self.callstack + .borrow_mut() + .push(CallstackElement::Entrypoint(Entrypoint { + address, + call_def: call_def.clone() + })); + let result = self + .contract_register + .read() + .unwrap() + .call(&address, call_def); + self.callstack.borrow_mut().pop(); + result.unwrap() } fn get_block_time(&self) -> u64 { @@ -91,11 +110,13 @@ impl ContractContext for LivenetContractEnv { impl LivenetContractEnv { pub fn new( casper_client: Rc>, - callstack: Rc> + callstack: Rc>, + contract_register: Arc> ) -> Rc> { Rc::new(RefCell::new(Self { casper_client, - callstack + callstack, + contract_register })) } } diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index 00cac6a1..3550de69 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -1,16 +1,19 @@ use crate::livenet_contract_env::LivenetContractEnv; use odra_casper_client::casper_client::CasperClient; use odra_core::callstack::{Callstack, CallstackElement, Entrypoint}; +use odra_core::casper_types::bytesrepr::deserialize; use odra_core::contract_container::ContractContainer; use odra_core::contract_register::ContractRegister; use odra_core::event::EventError; use odra_core::prelude::*; use odra_core::{ - Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, - RuntimeArgs, U512 + Address, Bytes, CLType, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, + PublicKey, RuntimeArgs, U512 }; +use casper_node_port::*; use std::sync::{Arc, RwLock}; use std::thread::sleep; +use odra_casper_client::casper_node_port; pub struct LivenetEnv { casper_client: Rc>, @@ -27,12 +30,16 @@ impl LivenetEnv { pub fn new_instance() -> Self { let casper_client: Rc> = Default::default(); let callstack: Rc> = Default::default(); - let livenet_contract_env = - LivenetContractEnv::new(casper_client.clone(), callstack.clone()); + let contract_register = Arc::new(RwLock::new(Default::default())); + let livenet_contract_env = LivenetContractEnv::new( + casper_client.clone(), + callstack.clone(), + contract_register.clone() + ); let contract_env = Rc::new(ContractEnv::new(0, livenet_contract_env)); Self { casper_client, - contract_register: Default::default(), + contract_register, contract_env, callstack } @@ -70,9 +77,33 @@ impl HostContext for LivenetEnv { todo!() } - fn get_events_count(&self, _contract_address: &Address) -> u32 { - // TODO: implement - 0 + fn get_events_count(&self, contract_address: &Address) -> u32 { + let d = self.casper_client.borrow().query_global_state_path(*contract_address, "__events_count".to_string()); + let c = d.unwrap().stored_value; + let ec_uref = match d.unwrap().stored_value { + casper_node_port::rpcs::StoredValue::Contract(contract) => { + contract.named_keys().find_map(|named_key |{ + if named_key.name == "__events_count" { + Some(named_key.key.clone()) + } else { + None + } + }) + }, + _ => panic!("Not a contract") + }.unwrap(); + + // next - extract value from uref + + // let bytes = self + // .casper_client + // .borrow() + // .query_global_state_for_contract_hash(contract_address); + // .unwrap() + // .to_vec(); + // // dbg!(bytes.clone()); + // deserialize(bytes).unwrap(); + 2 } fn call_contract( From 86577d0a2cd62f1994b954b77c48d9e8a8aad9ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Tue, 23 Jan 2024 11:59:48 +0100 Subject: [PATCH 12/24] Wip: events --- core/src/error.rs | 1 + examples/bin/livenet_tests.rs | 7 + .../casper-client/src/casper_client.rs | 137 +++++++++++------- .../src/casper_node_port/contract.rs | 9 +- .../casper-client/src/casper_node_port/mod.rs | 2 +- .../src/casper_node_port/rpcs.rs | 2 +- .../livenet-env/src/livenet_contract_env.rs | 2 +- .../livenet-env/src/livenet_host_env.rs | 49 +++---- 8 files changed, 119 insertions(+), 90 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index 6fd7f4bd..67fb04f9 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -95,6 +95,7 @@ pub enum ExecutionError { ExceededRecursionDepth = 116, KeyNotFound = 117, CouldNotDeserializeSignature = 118, + TypeMismatch = 119, MaxUserError = 32767, /// User error too high. The code should be in range 0..32767. UserErrorTooHigh = 32768, diff --git a/examples/bin/livenet_tests.rs b/examples/bin/livenet_tests.rs index 0b004afa..cbbd3957 100644 --- a/examples/bin/livenet_tests.rs +++ b/examples/bin/livenet_tests.rs @@ -1,6 +1,7 @@ use std::str::FromStr; use odra::{Address, HostEnv}; use odra_examples::features::livenet::{LivenetContractDeployer, LivenetContractHostRef}; +use odra_modules::access::events::OwnershipTransferred; fn main() { let env = odra_casper_livenet_env::livenet_env(); @@ -36,6 +37,12 @@ fn main() { // - we can also test the events assert_eq!(env.events_count(contract.address()), 1); + let event: OwnershipTransferred = env.get_event(contract.address(), 0).unwrap(); + assert_eq!(event.new_owner, Some(owner)); + + + + // - we can test immutable crosscalls // - mutable crosscalls will require a deploy diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index de95fcea..bd367e5f 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -3,6 +3,8 @@ use std::{fs, path::PathBuf, str::from_utf8_unchecked, time::Duration}; use casper_execution_engine::core::engine_state::ExecutableDeployItem; use casper_hashing::Digest; use jsonrpc_lite::JsonRpc; +use odra_core::casper_types::{URef, URefAddr}; +use odra_core::CLType::U32; use odra_core::{ casper_types::{ bytesrepr::{Bytes, FromBytes, ToBytes}, @@ -10,11 +12,12 @@ use odra_core::{ PublicKey, RuntimeArgs, SecretKey, TimeDiff, Timestamp, U512 }, consts::*, - Address, CallDef + Address, CLTyped, CallDef, ExecutionError, OdraError }; use serde::de::DeserializeOwned; use serde_json::{json, Value}; +use crate::casper_node_port::rpcs::StoredValue::CLValue; use crate::casper_node_port::{ rpcs::{ DictionaryIdentifier, GetDeployParams, GetDeployResult, GetDictionaryItemParams, @@ -24,7 +27,7 @@ use crate::casper_node_port::{ Deploy, DeployHash }; -use crate::log; +use crate::{casper_node_port, log}; pub const ENV_SECRET_KEY: &str = "ODRA_CASPER_LIVENET_SECRET_KEY_PATH"; pub const ENV_NODE_ADDRESS: &str = "ODRA_CASPER_LIVENET_NODE_ADDRESS"; @@ -61,7 +64,7 @@ impl CasperClient { } pub fn get_value(&self, address: &Address, key: &[u8]) -> Option { - self.query_dictionary(*address, unsafe { from_utf8_unchecked(key) }) + self.query_state_dictionary(address, unsafe { from_utf8_unchecked(key) }) } pub fn set_gas(&mut self, gas: u64) { @@ -117,6 +120,66 @@ impl CasperClient { result.state_root_hash.unwrap() } + pub fn query_dict( + &self, + contract_address: &Address, + dictionary_name: String, + dictionary_item_key: String + ) -> Result { + let state_root_hash = self.get_state_root_hash(); + let contract_hash = self.query_global_state_for_contract_hash(contract_address); + 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: GetDictionaryItemResult = self.post_request(request).unwrap(); + match result.stored_value { + CLValue(value) => { + let return_value: T = value.into_t().unwrap(); + Ok(return_value) + } + _ => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) + } + } + + pub fn query_contract_named_key>( + &self, + contract_address: &Address, + key: T + ) -> Result { + let d = self.query_global_state_path(contract_address, key.as_ref().to_string()); + let c = d.as_ref().unwrap().stored_value.clone(); + Ok(match d.as_ref().unwrap().stored_value.clone() { + casper_node_port::rpcs::StoredValue::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") + } + .unwrap()) + } + /// Query the node for the deploy state. pub fn get_deploy(&self, deploy_hash: DeployHash) -> GetDeployResult { let params = GetDeployParams { @@ -135,22 +198,6 @@ impl CasperClient { self.post_request(request).unwrap() } - /// Query the contract for the variable. - pub fn get_variable_value(&self, address: Address, key: &str) -> Option { - // let key = LivenetKeyMaker::to_variable_key(key); - // SAFETY: we know the key maker creates a string of valid UTF-8 characters. - // let key = unsafe { from_utf8_unchecked(key) }; - self.query_dictionary(address, key) - } - - /// Query the contract for the dictionary value. - pub fn get_dict_value(&self, address: Address, key: &[u8]) -> Option { - // let key = LivenetKeyMaker::to_dictionary_key(seed, key).unwrap(); - // SAFETY: we know the key maker creates a string of valid UTF-8 characters. - let key = unsafe { from_utf8_unchecked(key) }; - self.query_dictionary(address, key) - } - /// Discover the contract address by name. pub fn get_contract_address(&self, key_name: &str) -> Address { let key_name = format!("{}_package_hash", key_name); @@ -177,7 +224,7 @@ impl CasperClient { } /// Find the contract hash by the contract package hash. - pub fn query_global_state_for_contract_hash(&self, address: Address) -> ContractHash { + pub fn query_global_state_for_contract_hash(&self, address: &Address) -> ContractHash { let key = CasperKey::Hash(address.as_contract_package_hash().unwrap().value()); let result = self.query_global_state(&key); let result_as_json = serde_json::to_value(result).unwrap(); @@ -224,12 +271,6 @@ impl CasperClient { addr.to_string(), call_def.entry_point )); - // let session = ExecutableDeployItem::StoredVersionedContractByHash { - // hash: *addr.as_contract_package_hash().unwrap(), - // version: None, - // entry_point: call_def.entry_point, - // args: call_def.args - // }; let args = runtime_args! { CONTRACT_PACKAGE_HASH_ARG => *addr.as_contract_package_hash().unwrap(), @@ -300,7 +341,11 @@ impl CasperClient { self.wait_for_deploy_hash(deploy_hash); } - pub fn query_global_state_path(&self, address: Address, path: String) -> Option { + pub fn query_global_state_path( + &self, + address: &Address, + path: String + ) -> Option { let hash = self.query_global_state_for_contract_hash(address); let key = CasperKey::Hash(hash.value()); let state_root_hash = self.get_state_root_hash(); @@ -338,7 +383,7 @@ impl CasperClient { self.post_request(request).unwrap() } - fn query_dictionary(&self, address: Address, key: &str) -> Option { + fn query_state_dictionary(&self, address: &Address, key: &str) -> Option { let state_root_hash = self.get_state_root_hash(); let contract_hash = self.query_global_state_for_contract_hash(address); let contract_hash = contract_hash @@ -373,31 +418,15 @@ impl CasperClient { }) } - pub fn query_dict_nk(&self, address: Address, key: &str) -> Option { - let state_root_hash = self.get_state_root_hash(); - let contract_hash = self.query_global_state_for_contract_hash(address); - 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("__events_length"), - dictionary_item_key: String::from(key) - } - }; - - let request = json!( - { - "jsonrpc": "2.0", - "method": "state_get_dictionary_item", - "params": params, - "id": 1, + pub fn query_uref(&self, uref: URef) -> Result { + let result = self.query_global_state(&CasperKey::URef(uref)); + match result.stored_value { + CLValue(value) => { + let return_value: T = value.into_t().unwrap(); + Ok(return_value) } - ); - let result: Option = self.post_request(request); - None + _ => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) + } } fn wait_for_deploy_hash(&self, deploy_hash: DeployHash) -> ExecutionResult { @@ -472,6 +501,7 @@ impl CasperClient { } fn post_request(&self, request: Value) -> Option { + // TODO: change result to Result let client = reqwest::blocking::Client::new(); let response = client .post(self.node_address_rpc()) @@ -551,7 +581,8 @@ mod tests { #[ignore] pub fn query_global_state_for_contract() { let addr = Address::from_str(CONTRACT_PACKAGE_HASH).unwrap(); - let _result: Option = CasperClient::new().query_dictionary(addr, "name_contract"); + let _result: Option = + CasperClient::new().query_state_dictionary(addr, "name_contract"); } #[test] diff --git a/odra-casper/casper-client/src/casper_node_port/contract.rs b/odra-casper/casper-client/src/casper_node_port/contract.rs index c66b557b..e6034f2f 100644 --- a/odra-casper/casper-client/src/casper_node_port/contract.rs +++ b/odra-casper/casper-client/src/casper_node_port/contract.rs @@ -1,7 +1,8 @@ +use odra_core::casper_types::{ + ContractPackageHash, ContractWasmHash, EntryPoint, NamedKey, ProtocolVersion +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use odra_core::casper_types::{ContractPackageHash, ContractWasmHash, EntryPoint, NamedKey, ProtocolVersion}; - /// Details of a smart contract. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema)] @@ -11,7 +12,7 @@ pub struct Contract { contract_wasm_hash: ContractWasmHash, named_keys: Vec, entry_points: Vec, - protocol_version: ProtocolVersion, + protocol_version: ProtocolVersion } impl Contract { @@ -39,4 +40,4 @@ impl Contract { pub fn protocol_version(&self) -> &ProtocolVersion { &self.protocol_version } -} \ No newline at end of file +} diff --git a/odra-casper/casper-client/src/casper_node_port/mod.rs b/odra-casper/casper-client/src/casper_node_port/mod.rs index f6bf1a3b..175b5b9a 100644 --- a/odra-casper/casper-client/src/casper_node_port/mod.rs +++ b/odra-casper/casper-client/src/casper_node_port/mod.rs @@ -3,6 +3,7 @@ pub mod account; pub mod approval; pub mod block_hash; +pub mod contract; pub mod contract_package; pub mod deploy; pub mod deploy_hash; @@ -10,7 +11,6 @@ pub mod deploy_header; pub mod error; pub mod rpcs; pub mod utils; -pub mod contract; pub use deploy::Deploy; pub use deploy_hash::DeployHash; diff --git a/odra-casper/casper-client/src/casper_node_port/rpcs.rs b/odra-casper/casper-client/src/casper_node_port/rpcs.rs index 11bf29fd..749bd50e 100644 --- a/odra-casper/casper-client/src/casper_node_port/rpcs.rs +++ b/odra-casper/casper-client/src/casper_node_port/rpcs.rs @@ -1,10 +1,10 @@ +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 crate::casper_node_port::contract::Contract; use super::{ account::Account, diff --git a/odra-casper/livenet-env/src/livenet_contract_env.rs b/odra-casper/livenet-env/src/livenet_contract_env.rs index fa851610..3be95cba 100644 --- a/odra-casper/livenet-env/src/livenet_contract_env.rs +++ b/odra-casper/livenet-env/src/livenet_contract_env.rs @@ -23,7 +23,7 @@ impl ContractContext for LivenetContractEnv { } fn set_value(&self, _key: &[u8], _value: Bytes) { - panic!("Cannot set value in LivenetEnv") + panic!("Cannot set value in LivenetEnv without a deploy") } fn caller(&self) -> Address { diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index 3550de69..a1361fde 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -1,7 +1,10 @@ use crate::livenet_contract_env::LivenetContractEnv; +use casper_node_port::*; use odra_casper_client::casper_client::CasperClient; +use odra_casper_client::casper_node_port; use odra_core::callstack::{Callstack, CallstackElement, Entrypoint}; use odra_core::casper_types::bytesrepr::deserialize; +use odra_core::casper_types::URef; use odra_core::contract_container::ContractContainer; use odra_core::contract_register::ContractRegister; use odra_core::event::EventError; @@ -10,10 +13,8 @@ use odra_core::{ Address, Bytes, CLType, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, RuntimeArgs, U512 }; -use casper_node_port::*; use std::sync::{Arc, RwLock}; use std::thread::sleep; -use odra_casper_client::casper_node_port; pub struct LivenetEnv { casper_client: Rc>, @@ -73,37 +74,25 @@ impl HostContext for LivenetEnv { sleep(std::time::Duration::from_millis(time_diff)); } - fn get_event(&self, _contract_address: &Address, _index: i32) -> Result { - todo!() + fn get_event(&self, contract_address: &Address, index: i32) -> Result { + let event_bytes: Bytes = self + .casper_client + .borrow() + .query_dict(contract_address, "__events".to_string(), index.to_string()) + .unwrap(); + Ok(event_bytes) } fn get_events_count(&self, contract_address: &Address) -> u32 { - let d = self.casper_client.borrow().query_global_state_path(*contract_address, "__events_count".to_string()); - let c = d.unwrap().stored_value; - let ec_uref = match d.unwrap().stored_value { - casper_node_port::rpcs::StoredValue::Contract(contract) => { - contract.named_keys().find_map(|named_key |{ - if named_key.name == "__events_count" { - Some(named_key.key.clone()) - } else { - None - } - }) - }, - _ => panic!("Not a contract") - }.unwrap(); - - // next - extract value from uref - - // let bytes = self - // .casper_client - // .borrow() - // .query_global_state_for_contract_hash(contract_address); - // .unwrap() - // .to_vec(); - // // dbg!(bytes.clone()); - // deserialize(bytes).unwrap(); - 2 + let uref_str = self + .casper_client + .borrow() + .query_contract_named_key(contract_address, "__events_length") + .unwrap(); + self.casper_client + .borrow() + .query_uref(URef::from_formatted_str(uref_str.as_str()).unwrap()) + .unwrap() } fn call_contract( From d0f08b02f1c94be1cfd697a47530addadb43d6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Tue, 23 Jan 2024 13:22:54 +0100 Subject: [PATCH 13/24] Crosscalls --- examples/bin/erc20_on_livenet.rs | 18 ++--- examples/bin/livenet_tests.rs | 46 +++++++----- examples/src/features/livenet.rs | 19 ++++- examples/src/features/mod.rs | 2 +- .../casper-client/src/casper_client.rs | 70 ++++++++++--------- .../src/casper_node_port/account.rs | 6 ++ .../livenet-env/src/livenet_host_env.rs | 4 +- 7 files changed, 99 insertions(+), 66 deletions(-) diff --git a/examples/bin/erc20_on_livenet.rs b/examples/bin/erc20_on_livenet.rs index 94665650..52becb5d 100644 --- a/examples/bin/erc20_on_livenet.rs +++ b/examples/bin/erc20_on_livenet.rs @@ -9,17 +9,17 @@ fn main() { let recipient = "hash-2c4a6ce0da5d175e9638ec0830e01dd6cf5f4b1fbb0724f7d2d9de12b1e0f840"; let recipient = Address::from_str(recipient).unwrap(); - // Uncomment to deploy new contract. - // let token = deploy_new(&env); - // println!("Token address: {}", token.address()); + // Deploy new contract. + let mut token = deploy_new(&env); + println!("Token address: {}", token.address().to_string()); - // Load existing contract. - let mut token = load(&env); + // Uncomment to load existing contract. + // let mut token = load(&env); println!("Token name: {}", token.name()); - // - // env.set_gas(3_000_000_000u64); - // token.transfer(recipient, U256::from(1000)); + + env.set_gas(3_000_000_000u64); + token.transfer(recipient, U256::from(1000)); println!("Owner's balance: {:?}", token.balance_of(owner)); println!("Recipient's balance: {:?}", token.balance_of(recipient)); @@ -35,7 +35,7 @@ fn deploy_new(env: &HostEnv) -> Erc20HostRef { Erc20Deployer::init(env, name, symbol, decimals, Some(initial_supply)) } -fn load(env: &HostEnv) -> Erc20HostRef { +fn _load(env: &HostEnv) -> Erc20HostRef { let address = "hash-d26fcbd2106e37be975d2045c580334a6d7b9d0a241c2358a4db970dfd516945"; let address = Address::from_str(address).unwrap(); Erc20Deployer::load(env, address) diff --git a/examples/bin/livenet_tests.rs b/examples/bin/livenet_tests.rs index cbbd3957..36884a1d 100644 --- a/examples/bin/livenet_tests.rs +++ b/examples/bin/livenet_tests.rs @@ -1,7 +1,8 @@ -use std::str::FromStr; use odra::{Address, HostEnv}; use odra_examples::features::livenet::{LivenetContractDeployer, LivenetContractHostRef}; use odra_modules::access::events::OwnershipTransferred; +use odra_modules::erc20::Erc20Deployer; +use std::str::FromStr; fn main() { let env = odra_casper_livenet_env::livenet_env(); @@ -9,50 +10,61 @@ fn main() { let owner = env.caller(); // Contract can be deployed - // env.set_gas(30_000_000_000u64); - // let contract = deploy_new(&env); - // println!("Contract address: {}", contract.address().to_string()); + env.set_gas(30_000_000_000u64); + let contract = deploy_new(&env); + println!("Contract address: {}", contract.address().to_string()); // Contract can be loaded - // let mut contract = load(&env, *contract.address()); + let mut contract = load(&env, *contract.address()); - let address = Address::from_str("hash-b2cf71fcbf69eea9e8bfc7b76fb388ccfdc7233fbe1358fbc0959ef14511d756").unwrap(); - let mut contract = load(&env, address); + // Uncomment to load existing contract + // let address = Address::from_str("hash-bd80e12b6d189e3b492932fc08e1342b540555c60e299ea3563d81ad700997e0").unwrap(); + // let mut contract = load(&env, address); // 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); + contract.push_on_stack(1); // 2. If the endpoint is mutable and returns something, it can be called through the proxy: - // let value = contract.pop_from_stack(); - // assert_eq!(value, 1); + let value = contract.pop_from_stack(); + assert_eq!(value, 1); // 3. If the endpoint is immutable, it can be called locally, querying only storage from livenet: - // assert_eq!(contract.owner(), owner); + assert_eq!(contract.owner(), owner); // By querying livenet storage // - we can also test the events assert_eq!(env.events_count(contract.address()), 1); - let event: OwnershipTransferred = env.get_event(contract.address(), 0).unwrap(); + let event: OwnershipTransferred = env.get_event(contract.address(), 0).unwrap(); assert_eq!(event.new_owner, Some(owner)); - - - - // - we can test immutable crosscalls + // - we can test immutable crosscalls without deploying + assert_eq!(contract.immutable_cross_call(), 10_000.into()); // - mutable crosscalls will require a deploy + let pre_call_balance = Erc20Deployer::load(&env, erc20_address()).balance_of(env.caller()); + contract.mutable_cross_call(); + let post_call_balance = Erc20Deployer::load(&env, erc20_address()).balance_of(env.caller()); + assert_eq!(post_call_balance, pre_call_balance + 1); // We can change the caller } fn deploy_new(env: &HostEnv) -> LivenetContractHostRef { env.set_gas(100_000_000_000u64); - LivenetContractDeployer::init(env) + let livenet_contract = LivenetContractDeployer::init(env, erc20_address()); + Erc20Deployer::load(env, erc20_address()).transfer(*livenet_contract.address(), 1000.into()); + livenet_contract +} + +fn erc20_address() -> Address { + // Following contract is deployed on integration livenet, change it to address from erc20_on_livenet example + Address::from_str("hash-d26fcbd2106e37be975d2045c580334a6d7b9d0a241c2358a4db970dfd516945") + .unwrap() } fn load(env: &HostEnv, address: Address) -> LivenetContractHostRef { diff --git a/examples/src/features/livenet.rs b/examples/src/features/livenet.rs index e1ae604f..45c2e340 100644 --- a/examples/src/features/livenet.rs +++ b/examples/src/features/livenet.rs @@ -1,19 +1,23 @@ -use odra::{Address, List, Module, ModuleWrapper, UnwrapOrRevert, Variable}; +use odra::casper_types::U256; use odra::prelude::*; +use odra::{Address, List, Module, ModuleWrapper, UnwrapOrRevert, Variable}; use odra_modules::access::Ownable; +use odra_modules::erc20::Erc20ContractRef; #[odra::module] pub struct LivenetContract { creator: Variable
, ownable: ModuleWrapper, stack: List, + erc20_address: Variable
} #[odra::module] impl LivenetContract { - pub fn init(mut self) { + pub fn init(mut self, erc20_address: Address) { self.creator.set(self.env().caller()); self.ownable.init(); + self.erc20_address.set(erc20_address); } pub fn transfer_ownership(&mut self, new_owner: Address) { @@ -35,4 +39,13 @@ impl LivenetContract { pub fn get_stack_len(&self) -> u32 { self.stack.len() } -} \ No newline at end of file + + pub fn immutable_cross_call(&self) -> U256 { + Erc20ContractRef::new(self.env(), self.erc20_address.get().unwrap()).total_supply() + } + + pub fn mutable_cross_call(&mut self) { + Erc20ContractRef::new(self.env(), self.erc20_address.get().unwrap()) + .transfer(self.env().caller(), 1.into()); + } +} diff --git a/examples/src/features/mod.rs b/examples/src/features/mod.rs index 8d6c8c18..f52fe1ae 100644 --- a/examples/src/features/mod.rs +++ b/examples/src/features/mod.rs @@ -4,6 +4,7 @@ pub mod cross_calls; pub mod events; pub mod handling_errors; pub mod host_functions; +pub mod livenet; pub mod module_nesting; pub mod modules; pub mod native_token; @@ -12,4 +13,3 @@ pub mod reentrancy_guard; pub mod signature_verifier; pub mod storage; pub mod testing; -pub mod livenet; diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index bd367e5f..ee628276 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -3,8 +3,7 @@ use std::{fs, path::PathBuf, str::from_utf8_unchecked, time::Duration}; use casper_execution_engine::core::engine_state::ExecutableDeployItem; use casper_hashing::Digest; use jsonrpc_lite::JsonRpc; -use odra_core::casper_types::{URef, URefAddr}; -use odra_core::CLType::U32; +use odra_core::casper_types::URef; use odra_core::{ casper_types::{ bytesrepr::{Bytes, FromBytes, ToBytes}, @@ -17,7 +16,7 @@ use odra_core::{ use serde::de::DeserializeOwned; use serde_json::{json, Value}; -use crate::casper_node_port::rpcs::StoredValue::CLValue; +use crate::casper_node_port::rpcs::StoredValue::*; use crate::casper_node_port::{ rpcs::{ DictionaryIdentifier, GetDeployParams, GetDeployResult, GetDictionaryItemParams, @@ -163,21 +162,23 @@ impl CasperClient { contract_address: &Address, key: T ) -> Result { - let d = self.query_global_state_path(contract_address, key.as_ref().to_string()); - let c = d.as_ref().unwrap().stored_value.clone(); - Ok(match d.as_ref().unwrap().stored_value.clone() { - casper_node_port::rpcs::StoredValue::Contract(contract) => { - contract.named_keys().find_map(|named_key| { - if named_key.name == key.as_ref() { - Some(named_key.key.clone()) - } else { - None - } - }) + let contract_state = + self.query_global_state_path(contract_address, key.as_ref().to_string()); + Ok( + match contract_state.as_ref().unwrap().stored_value.clone() { + casper_node_port::rpcs::StoredValue::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") } - _ => panic!("Not a contract") - } - .unwrap()) + .unwrap() + ) } /// Query the node for the deploy state. @@ -265,9 +266,13 @@ impl CasperClient { address } - pub fn deploy_entrypoint_call_with_proxy(&self, addr: Address, call_def: CallDef) -> Bytes { + pub fn deploy_entrypoint_call_with_proxy( + &self, + addr: Address, + call_def: CallDef + ) -> Result { log::info(format!( - "Calling {:?} with entrypoint \"{}\".", + "Calling {:?} with entrypoint \"{}\" through proxy.", addr.to_string(), call_def.entry_point )); @@ -303,13 +308,18 @@ impl CasperClient { self.wait_for_deploy_hash(deploy_hash); let r = self.query_global_state(&CasperKey::Account(self.public_key().to_account_hash())); - let result_as_json = serde_json::to_value(r).unwrap(); - let result = result_as_json["stored_value"]["CLValue"]["bytes"] - .as_str() - .unwrap(); - let bytes = hex::decode(result).unwrap(); - let (value, _) = FromBytes::from_bytes(&bytes).unwrap(); - value + 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()), + None => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) + } + } + _ => Err(OdraError::ExecutionError(ExecutionError::TypeMismatch)) + } } /// Deploy the entrypoint call. @@ -577,14 +587,6 @@ mod tests { CasperClient::new().wait_for_deploy_hash(hash); } - #[test] - #[ignore] - pub fn query_global_state_for_contract() { - let addr = Address::from_str(CONTRACT_PACKAGE_HASH).unwrap(); - let _result: Option = - CasperClient::new().query_state_dictionary(addr, "name_contract"); - } - #[test] #[ignore] pub fn discover_contract_address() { diff --git a/odra-casper/casper-client/src/casper_node_port/account.rs b/odra-casper/casper-client/src/casper_node_port/account.rs index 56c438ae..4c8f0419 100644 --- a/odra-casper/casper-client/src/casper_node_port/account.rs +++ b/odra-casper/casper-client/src/casper_node_port/account.rs @@ -16,6 +16,12 @@ pub struct Account { 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 { diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index a1361fde..d048b1c2 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -117,10 +117,10 @@ impl HostContext for LivenetEnv { return result; } match use_proxy { - true => Ok(self + true => self .casper_client .borrow_mut() - .deploy_entrypoint_call_with_proxy(*address, call_def)), + .deploy_entrypoint_call_with_proxy(*address, call_def), false => { self.casper_client .borrow_mut() From 87f8e051a0a65140c766be2c94dbad96bf7091e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Tue, 23 Jan 2024 15:03:48 +0100 Subject: [PATCH 14/24] Multiple keys management Switching accounts Multiple enviroments --- examples/bin/livenet_tests.rs | 35 +++++------ .../casper-client/src/casper_client.rs | 59 +++++++++++++++++-- .../livenet-env/src/livenet_host_env.rs | 8 +-- 3 files changed, 75 insertions(+), 27 deletions(-) diff --git a/examples/bin/livenet_tests.rs b/examples/bin/livenet_tests.rs index 36884a1d..9e738786 100644 --- a/examples/bin/livenet_tests.rs +++ b/examples/bin/livenet_tests.rs @@ -10,48 +10,49 @@ fn main() { let owner = env.caller(); // Contract can be deployed - env.set_gas(30_000_000_000u64); - let contract = deploy_new(&env); - println!("Contract address: {}", contract.address().to_string()); + // env.set_gas(30_000_000_000u64); + // let contract = deploy_new(&env); + // println!("Contract address: {}", contract.address().to_string()); // Contract can be loaded - let mut contract = load(&env, *contract.address()); + // let mut contract = load(&env, *contract.address()); // Uncomment to load existing contract - // let address = Address::from_str("hash-bd80e12b6d189e3b492932fc08e1342b540555c60e299ea3563d81ad700997e0").unwrap(); - // let mut contract = load(&env, address); + let address = Address::from_str("hash-bd80e12b6d189e3b492932fc08e1342b540555c60e299ea3563d81ad700997e0").unwrap(); + let mut contract = load(&env, address); // 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); + // contract.push_on_stack(1); // 2. If the endpoint is mutable and returns something, it can be called through the proxy: - let value = contract.pop_from_stack(); - assert_eq!(value, 1); + // let value = contract.pop_from_stack(); + // assert_eq!(value, 1); // 3. If the endpoint is immutable, it can be called locally, querying only storage from livenet: - assert_eq!(contract.owner(), owner); + // assert_eq!(contract.owner(), owner); // By querying livenet storage // - we can also test the events assert_eq!(env.events_count(contract.address()), 1); - let event: OwnershipTransferred = env.get_event(contract.address(), 0).unwrap(); - assert_eq!(event.new_owner, Some(owner)); + // let event: OwnershipTransferred = env.get_event(contract.address(), 0).unwrap(); + // assert_eq!(event.new_owner, Some(owner)); // - we can test immutable crosscalls without deploying - assert_eq!(contract.immutable_cross_call(), 10_000.into()); + // assert_eq!(contract.immutable_cross_call(), 10_000.into()); // - mutable crosscalls will require a deploy - let pre_call_balance = Erc20Deployer::load(&env, erc20_address()).balance_of(env.caller()); - contract.mutable_cross_call(); - let post_call_balance = Erc20Deployer::load(&env, erc20_address()).balance_of(env.caller()); - assert_eq!(post_call_balance, pre_call_balance + 1); + // let pre_call_balance = Erc20Deployer::load(&env, erc20_address()).balance_of(env.caller()); + // contract.mutable_cross_call(); + // let post_call_balance = Erc20Deployer::load(&env, erc20_address()).balance_of(env.caller()); + // assert_eq!(post_call_balance, pre_call_balance + 1); // We can change the caller + env.set_caller(env.get_account(2)); } fn deploy_new(env: &HostEnv) -> LivenetContractHostRef { diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index ee628276..3862096f 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -1,7 +1,9 @@ +use std::path::Path; use std::{fs, path::PathBuf, str::from_utf8_unchecked, time::Duration}; use casper_execution_engine::core::engine_state::ExecutableDeployItem; use casper_hashing::Digest; +use itertools::Itertools; use jsonrpc_lite::JsonRpc; use odra_core::casper_types::URef; use odra_core::{ @@ -31,6 +33,7 @@ use crate::{casper_node_port, log}; pub const ENV_SECRET_KEY: &str = "ODRA_CASPER_LIVENET_SECRET_KEY_PATH"; pub const ENV_NODE_ADDRESS: &str = "ODRA_CASPER_LIVENET_NODE_ADDRESS"; pub const ENV_CHAIN_NAME: &str = "ODRA_CASPER_LIVENET_CHAIN_NAME"; +pub const ENV_ACCOUNT_PREFIX: &str = "ODRA_CASPER_LIVENET_KEY_"; fn get_env_variable(name: &str) -> String { std::env::var(name).unwrap_or_else(|err| { @@ -46,18 +49,38 @@ fn get_env_variable(name: &str) -> String { pub struct CasperClient { node_address: String, chain_name: String, - secret_key: SecretKey, + active_account: usize, + secret_keys: Vec, gas: U512 } impl CasperClient { /// Creates new CasperClient. pub fn new() -> Self { + // Check for additional .env file + let additional_env_file = std::env::var("ODRA_CASPER_LIVENET_ENV"); + + if let Ok(additional_env_file) = additional_env_file { + let filename = PathBuf::from(additional_env_file).with_extension("env"); + dotenv::from_filename(filename).ok(); + } + + // Load .env dotenv::dotenv().ok(); + + let mut secret_keys = vec![]; + secret_keys.push(SecretKey::from_file(get_env_variable(ENV_SECRET_KEY)).unwrap()); + let mut i = 0; + while let Ok(key_filename) = std::env::var(format!("{}{}", ENV_ACCOUNT_PREFIX, i + 1)) { + secret_keys.push(SecretKey::from_file(key_filename).unwrap()); + i += 1; + } + CasperClient { node_address: get_env_variable(ENV_NODE_ADDRESS), chain_name: get_env_variable(ENV_CHAIN_NAME), - secret_key: SecretKey::from_file(get_env_variable(ENV_SECRET_KEY)).unwrap(), + active_account: 0, + secret_keys, gas: U512::zero() } } @@ -82,7 +105,11 @@ impl CasperClient { /// Public key of the client account. pub fn public_key(&self) -> PublicKey { - PublicKey::from(&self.secret_key) + PublicKey::from(&self.secret_keys[self.active_account]) + } + + pub fn secret_key(&self) -> &SecretKey { + &self.secret_keys[self.active_account] } /// Address of the client account. @@ -90,6 +117,26 @@ impl CasperClient { Address::from(self.public_key()) } + pub fn get_account(&self, index: usize) -> Address { + if index >= self.secret_keys.len() { + panic!("Key for account with index {} is not loaded", index); + } + Address::from(PublicKey::from(&self.secret_keys[index])) + } + + pub fn set_caller(&mut self, address: Address) { + match self + .secret_keys + .iter() + .find_position(|key| Address::from(PublicKey::from(*key)) == address) + { + Some((index, _)) => { + self.active_account = index; + } + None => panic!("Key for address {:?} is not loaded", address) + } + } + pub fn get_block_time(&self) -> u64 { let request = json!( { @@ -354,7 +401,7 @@ impl CasperClient { pub fn query_global_state_path( &self, address: &Address, - path: String + _path: String ) -> Option { let hash = self.query_global_state_for_contract_hash(address); let key = CasperKey::Hash(hash.value()); @@ -505,7 +552,7 @@ impl CasperClient { chain_name, payment, session, - &self.secret_key, + self.secret_key(), Some(self.public_key()) ) } @@ -557,7 +604,7 @@ mod tests { use casper_hashing::Digest; use odra_core::{ casper_types::bytesrepr::{FromBytes, ToBytes}, - Address, U256 + Address }; use crate::casper_node_port::DeployHash; diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index d048b1c2..fd246f09 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -48,8 +48,8 @@ impl LivenetEnv { } impl HostContext for LivenetEnv { - fn set_caller(&self, _caller: Address) { - todo!() + fn set_caller(&self, caller: Address) { + self.casper_client.borrow_mut().set_caller(caller); } fn set_gas(&self, gas: u64) { @@ -60,8 +60,8 @@ impl HostContext for LivenetEnv { self.casper_client.borrow().caller() } - fn get_account(&self, _index: usize) -> Address { - todo!() + fn get_account(&self, index: usize) -> Address { + self.casper_client.borrow().get_account(index) } fn balance_of(&self, _address: &Address) -> U512 { From 90a9f6ba44c4797bd09a42cb206a3b0a2ef48b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Tue, 23 Jan 2024 16:10:30 +0100 Subject: [PATCH 15/24] Query balance --- .gitignore | 1 + examples/.env.sample | 12 ++++- examples/bin/livenet_tests.rs | 27 +++++----- .../casper-client/src/casper_client.rs | 23 ++++++++- .../casper-client/src/casper_node_port/mod.rs | 1 + .../src/casper_node_port/query_balance.rs | 49 +++++++++++++++++++ .../livenet-env/src/livenet_host_env.rs | 11 ++--- odra-macros/src/ir/attr.rs | 2 +- 8 files changed, 104 insertions(+), 22 deletions(-) create mode 100644 odra-casper/casper-client/src/casper_node_port/query_balance.rs diff --git a/.gitignore b/.gitignore index 0bdf4630..f6e4da6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Generated by Cargo # will have compiled files and executables .* +*.env !/.gitignore /target/ Cargo.lock diff --git a/examples/.env.sample b/examples/.env.sample index 98677fe1..4962b763 100644 --- a/examples/.env.sample +++ b/examples/.env.sample @@ -1,10 +1,20 @@ +# .env file used by Livenet integration. You can use multiple .env files to manage deploys on multiple chains +# by naming them casper-test.env, casper-livenet.env, etc. and calling the deploy script with the name of the +# ennviroment provided in the "ODRA_CASPER_LIVENET_ENV" variable. For example: +# ODRA_CASPER_LIVENET_ENV=casper-test cargo run --bin livenet_tests --feature livenet +# This will load integration.env file first, and then fill the missing values with the values from casper-test.env. + # Path to the secret key of the account that will be used to deploy the contracts. # ODRA_CASPER_LIVENET_SECRET_KEY_PATH= # RPC address of the node that will be used to deploy the contracts. # ODRA_CASPER_LIVENET_NODE_ADDRESS=http://localhost:7777 -# Chain name of the network. Known values: +# Chain name of the network. Sample values: # - integration-test # - casper-test # ODRA_CASPER_LIVENET_CHAIN_NAME= + +# Paths to the secret keys of the additional acccounts. Main secret key will be 0th account. +# ODRA_CASPER_LIVENET_KEY_1=./keys/secret_key_1.pem +# ODRA_CASPER_LIVENET_KEY_2=./keys/secret_key_2.pem diff --git a/examples/bin/livenet_tests.rs b/examples/bin/livenet_tests.rs index 9e738786..08be691c 100644 --- a/examples/bin/livenet_tests.rs +++ b/examples/bin/livenet_tests.rs @@ -26,36 +26,39 @@ fn main() { // 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); + contract.push_on_stack(1); // 2. If the endpoint is mutable and returns something, it can be called through the proxy: - // let value = contract.pop_from_stack(); - // assert_eq!(value, 1); + let value = contract.pop_from_stack(); + assert_eq!(value, 1); // 3. If the endpoint is immutable, it can be called locally, querying only storage from livenet: - // assert_eq!(contract.owner(), owner); + assert_eq!(contract.owner(), owner); // By querying livenet storage // - we can also test the events assert_eq!(env.events_count(contract.address()), 1); - // let event: OwnershipTransferred = env.get_event(contract.address(), 0).unwrap(); - // assert_eq!(event.new_owner, Some(owner)); + let event: OwnershipTransferred = env.get_event(contract.address(), 0).unwrap(); + assert_eq!(event.new_owner, Some(owner)); // - we can test immutable crosscalls without deploying - // assert_eq!(contract.immutable_cross_call(), 10_000.into()); + assert_eq!(contract.immutable_cross_call(), 10_000.into()); // - mutable crosscalls will require a deploy - // let pre_call_balance = Erc20Deployer::load(&env, erc20_address()).balance_of(env.caller()); - // contract.mutable_cross_call(); - // let post_call_balance = Erc20Deployer::load(&env, erc20_address()).balance_of(env.caller()); - // assert_eq!(post_call_balance, pre_call_balance + 1); + let pre_call_balance = Erc20Deployer::load(&env, erc20_address()).balance_of(env.caller()); + contract.mutable_cross_call(); + let post_call_balance = Erc20Deployer::load(&env, erc20_address()).balance_of(env.caller()); + assert_eq!(post_call_balance, pre_call_balance + 1); // We can change the caller env.set_caller(env.get_account(2)); + + // And query the balance + println!("Balance of caller: {}", env.balance_of(&env.caller())); } -fn deploy_new(env: &HostEnv) -> LivenetContractHostRef { +fn _deploy_new(env: &HostEnv) -> LivenetContractHostRef { env.set_gas(100_000_000_000u64); let livenet_contract = LivenetContractDeployer::init(env, erc20_address()); Erc20Deployer::load(env, erc20_address()).transfer(*livenet_contract.address(), 1000.into()); diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index 3862096f..66fd0367 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -1,4 +1,3 @@ -use std::path::Path; use std::{fs, path::PathBuf, str::from_utf8_unchecked, time::Duration}; use casper_execution_engine::core::engine_state::ExecutableDeployItem; @@ -28,6 +27,9 @@ use crate::casper_node_port::{ Deploy, DeployHash }; +use crate::casper_node_port::query_balance::{ + PurseIdentifier, QueryBalanceParams, QueryBalanceResult, QUERY_BALANCE_METHOD +}; use crate::{casper_node_port, log}; pub const ENV_SECRET_KEY: &str = "ODRA_CASPER_LIVENET_SECRET_KEY_PATH"; @@ -137,6 +139,25 @@ impl CasperClient { } } + pub fn get_balance(&self, address: &Address) -> U512 { + let query_balance_params = QueryBalanceParams::new( + Some(GlobalStateIdentifier::StateRootHash( + self.get_state_root_hash() + )), + PurseIdentifier::MainPurseUnderAccountHash(*address.as_account_hash().unwrap()) + ); + let request = json!( + { + "jsonrpc": "2.0", + "method": QUERY_BALANCE_METHOD, + "params": query_balance_params, + "id": 1, + } + ); + let result: QueryBalanceResult = self.post_request(request).unwrap(); + result.balance + } + pub fn get_block_time(&self) -> u64 { let request = json!( { diff --git a/odra-casper/casper-client/src/casper_node_port/mod.rs b/odra-casper/casper-client/src/casper_node_port/mod.rs index 175b5b9a..f977fa46 100644 --- a/odra-casper/casper-client/src/casper_node_port/mod.rs +++ b/odra-casper/casper-client/src/casper_node_port/mod.rs @@ -9,6 +9,7 @@ pub mod deploy; pub mod deploy_hash; pub mod deploy_header; pub mod error; +pub mod query_balance; pub mod rpcs; pub mod utils; diff --git a/odra-casper/casper-client/src/casper_node_port/query_balance.rs b/odra-casper/casper-client/src/casper_node_port/query_balance.rs new file mode 100644 index 00000000..464a9018 --- /dev/null +++ b/odra-casper/casper-client/src/casper_node_port/query_balance.rs @@ -0,0 +1,49 @@ +use crate::casper_node_port::rpcs::GlobalStateIdentifier; +use odra_core::casper_types::account::AccountHash; +use odra_core::casper_types::{ProtocolVersion, URef}; +use odra_core::{PublicKey, 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/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index fd246f09..bb4dde97 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -1,17 +1,14 @@ use crate::livenet_contract_env::LivenetContractEnv; -use casper_node_port::*; use odra_casper_client::casper_client::CasperClient; -use odra_casper_client::casper_node_port; use odra_core::callstack::{Callstack, CallstackElement, Entrypoint}; -use odra_core::casper_types::bytesrepr::deserialize; use odra_core::casper_types::URef; use odra_core::contract_container::ContractContainer; use odra_core::contract_register::ContractRegister; use odra_core::event::EventError; use odra_core::prelude::*; use odra_core::{ - Address, Bytes, CLType, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, - PublicKey, RuntimeArgs, U512 + Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, + RuntimeArgs, U512 }; use std::sync::{Arc, RwLock}; use std::thread::sleep; @@ -64,8 +61,8 @@ impl HostContext for LivenetEnv { self.casper_client.borrow().get_account(index) } - fn balance_of(&self, _address: &Address) -> U512 { - todo!() + fn balance_of(&self, address: &Address) -> U512 { + self.casper_client.borrow().get_balance(address) } fn advance_block_time(&self, time_diff: u64) { diff --git a/odra-macros/src/ir/attr.rs b/odra-macros/src/ir/attr.rs index 3c6de0fa..94b87d86 100644 --- a/odra-macros/src/ir/attr.rs +++ b/odra-macros/src/ir/attr.rs @@ -276,7 +276,7 @@ mod tests { .collect::>(), other_attrs .into_iter() - .map(|a| Attribute::Other(a)) + .map(Attribute::Other) .collect::>() ) }) From 8efc87adac935e1d32fd858d101d63a4743520af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Tue, 23 Jan 2024 16:42:54 +0100 Subject: [PATCH 16/24] Removed old livenet implementation. --- core/src/event.rs | 4 +- .../casper-client/src/casper_client.rs | 148 +++--- .../livenet-env/src/livenet_host_env.rs | 37 +- odra-casper/livenet/Cargo.toml | 30 -- odra-casper/livenet/README.md | 3 - .../livenet/resources/proxy_caller.wasm | Bin 41349 -> 0 bytes odra-casper/livenet/src/casper_client.rs | 495 ------------------ .../livenet/src/casper_node_port/account.rs | 32 -- .../livenet/src/casper_node_port/approval.rs | 72 --- .../src/casper_node_port/block_hash.rs | 85 --- .../src/casper_node_port/contract_package.rs | 53 -- .../livenet/src/casper_node_port/deploy.rs | 277 ---------- .../src/casper_node_port/deploy_hash.rs | 97 ---- .../src/casper_node_port/deploy_header.rs | 162 ------ .../livenet/src/casper_node_port/error.rs | 131 ----- .../livenet/src/casper_node_port/mod.rs | 15 - .../livenet/src/casper_node_port/rpcs.rs | 244 --------- .../livenet/src/casper_node_port/utils.rs | 47 -- odra-casper/livenet/src/client_env.rs | 196 ------- .../livenet/src/client_env/callstack.rs | 18 - .../src/client_env/contract_container.rs | 56 -- .../src/client_env/contract_register.rs | 37 -- odra-casper/livenet/src/contract_env.rs | 88 ---- odra-casper/livenet/src/lib.rs | 30 -- 24 files changed, 77 insertions(+), 2280 deletions(-) delete mode 100644 odra-casper/livenet/Cargo.toml delete mode 100644 odra-casper/livenet/README.md delete mode 100755 odra-casper/livenet/resources/proxy_caller.wasm delete mode 100644 odra-casper/livenet/src/casper_client.rs delete mode 100644 odra-casper/livenet/src/casper_node_port/account.rs delete mode 100644 odra-casper/livenet/src/casper_node_port/approval.rs delete mode 100644 odra-casper/livenet/src/casper_node_port/block_hash.rs delete mode 100644 odra-casper/livenet/src/casper_node_port/contract_package.rs delete mode 100644 odra-casper/livenet/src/casper_node_port/deploy.rs delete mode 100644 odra-casper/livenet/src/casper_node_port/deploy_hash.rs delete mode 100644 odra-casper/livenet/src/casper_node_port/deploy_header.rs delete mode 100644 odra-casper/livenet/src/casper_node_port/error.rs delete mode 100644 odra-casper/livenet/src/casper_node_port/mod.rs delete mode 100644 odra-casper/livenet/src/casper_node_port/rpcs.rs delete mode 100644 odra-casper/livenet/src/casper_node_port/utils.rs delete mode 100644 odra-casper/livenet/src/client_env.rs delete mode 100644 odra-casper/livenet/src/client_env/callstack.rs delete mode 100644 odra-casper/livenet/src/client_env/contract_container.rs delete mode 100644 odra-casper/livenet/src/client_env/contract_register.rs delete mode 100644 odra-casper/livenet/src/contract_env.rs delete mode 100644 odra-casper/livenet/src/lib.rs diff --git a/core/src/event.rs b/core/src/event.rs index fb35d9de..71da37e9 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -14,5 +14,7 @@ pub enum EventError { /// Unexpected error while deserializing. Parsing, /// Could not extract event name. - CouldntExtractName + CouldntExtractName, + /// Could not extract event data. + CouldntExtractEventData } diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index 66fd0367..66f033bb 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -4,6 +4,9 @@ use casper_execution_engine::core::engine_state::ExecutableDeployItem; use casper_hashing::Digest; use itertools::Itertools; use jsonrpc_lite::JsonRpc; +use serde::de::DeserializeOwned; +use serde_json::{json, Value}; + use odra_core::casper_types::URef; use odra_core::{ casper_types::{ @@ -14,9 +17,10 @@ use odra_core::{ consts::*, Address, CLTyped, CallDef, ExecutionError, OdraError }; -use serde::de::DeserializeOwned; -use serde_json::{json, Value}; +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::{ @@ -26,10 +30,6 @@ use crate::casper_node_port::{ }, Deploy, DeployHash }; - -use crate::casper_node_port::query_balance::{ - PurseIdentifier, QueryBalanceParams, QueryBalanceResult, QUERY_BALANCE_METHOD -}; use crate::{casper_node_port, log}; pub const ENV_SECRET_KEY: &str = "ODRA_CASPER_LIVENET_SECRET_KEY_PATH"; @@ -87,10 +87,12 @@ impl CasperClient { } } + /// Gets a value from the storage pub fn get_value(&self, address: &Address, key: &[u8]) -> Option { self.query_state_dictionary(address, unsafe { from_utf8_unchecked(key) }) } + /// Sets amount of gas for the next deploy. pub fn set_gas(&mut self, gas: u64) { self.gas = gas.into(); } @@ -110,6 +112,7 @@ impl CasperClient { PublicKey::from(&self.secret_keys[self.active_account]) } + /// Secret key of the client account. pub fn secret_key(&self) -> &SecretKey { &self.secret_keys[self.active_account] } @@ -119,6 +122,7 @@ impl CasperClient { Address::from(self.public_key()) } + /// Address of the account loaded to the client. pub fn get_account(&self, index: usize) -> Address { if index >= self.secret_keys.len() { panic!("Key for account with index {} is not loaded", index); @@ -126,6 +130,7 @@ impl CasperClient { Address::from(PublicKey::from(&self.secret_keys[index])) } + /// Sets the caller account. pub fn set_caller(&mut self, address: Address) { match self .secret_keys @@ -139,6 +144,7 @@ impl CasperClient { } } + /// Returns the balance of the account. pub fn get_balance(&self, address: &Address) -> U512 { let query_balance_params = QueryBalanceParams::new( Some(GlobalStateIdentifier::StateRootHash( @@ -158,6 +164,7 @@ impl CasperClient { result.balance } + /// Returns the current block_time pub fn get_block_time(&self) -> u64 { let request = json!( { @@ -174,8 +181,24 @@ impl CasperClient { timestamp } + /// Get the event bytes from storage + pub fn get_event(&self, contract_address: &Address, index: i32) -> Result { + let event_bytes: Bytes = + self.query_dict(contract_address, "__events".to_string(), index.to_string())?; + Ok(event_bytes) + } + + /// Get the events count from storage + pub fn events_count(&self, contract_address: &Address) -> u32 { + let uref_str = self + .query_contract_named_key(contract_address, "__events_length") + .unwrap(); + self.query_uref(URef::from_formatted_str(uref_str.as_str()).unwrap()) + .unwrap() + } + /// Query the node for the current state root hash. - pub fn get_state_root_hash(&self) -> Digest { + fn get_state_root_hash(&self) -> Digest { let request = json!( { "jsonrpc": "2.0", @@ -187,7 +210,8 @@ impl CasperClient { result.state_root_hash.unwrap() } - pub fn query_dict( + /// Query the node for the dictionary item of a contract. + fn query_dict( &self, contract_address: &Address, dictionary_name: String, @@ -225,6 +249,7 @@ impl CasperClient { } } + /// Query the contract for the direct value of a named key pub fn query_contract_named_key>( &self, contract_address: &Address, @@ -268,32 +293,36 @@ impl CasperClient { } /// Discover the contract address by name. - pub fn get_contract_address(&self, key_name: &str) -> Address { + 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(); let result = self.query_global_state(&CasperKey::Account(account_hash)); - let result_as_json = serde_json::to_value(result.stored_value).unwrap(); - - let named_keys = result_as_json["Account"]["named_keys"].as_array().unwrap(); - for named_key in named_keys { - if named_key["name"].as_str().unwrap() == key_name { - let key = named_key["key"] - .as_str() - .unwrap() - .replace("hash-", "contract-package-wasm"); - let contract_hash = ContractPackageHash::from_formatted_str(&key).unwrap(); - return Address::try_from(contract_hash).unwrap(); - } + 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 + ) } - log::error(format!( - "Contract {:?} not found in {:#?}", - key_name, result_as_json - )); - panic!("get_contract_address failed"); + .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. - pub fn query_global_state_for_contract_hash(&self, address: &Address) -> ContractHash { + fn query_global_state_for_contract_hash(&self, address: &Address) -> ContractHash { let key = CasperKey::Hash(address.as_contract_package_hash().unwrap().value()); let result = self.query_global_state(&key); let result_as_json = serde_json::to_value(result).unwrap(); @@ -419,7 +448,7 @@ impl CasperClient { self.wait_for_deploy_hash(deploy_hash); } - pub fn query_global_state_path( + fn query_global_state_path( &self, address: &Address, _path: String @@ -443,7 +472,7 @@ impl CasperClient { self.post_request(request).unwrap() } - pub fn query_global_state(&self, key: &CasperKey) -> QueryGlobalStateResult { + fn query_global_state(&self, key: &CasperKey) -> QueryGlobalStateResult { let state_root_hash = self.get_state_root_hash(); let params = QueryGlobalStateParams { state_identifier: GlobalStateIdentifier::StateRootHash(state_root_hash), @@ -496,7 +525,7 @@ impl CasperClient { }) } - pub fn query_uref(&self, uref: URef) -> Result { + fn query_uref(&self, uref: URef) -> Result { let result = self.query_global_state(&CasperKey::URef(uref)); match result.stored_value { CLValue(value) => { @@ -617,62 +646,3 @@ fn find_wasm_file_path(wasm_file_name: &str) -> PathBuf { log::error(format!("Could not find wasm under {:?}.", checked_paths)); panic!("Wasm not found"); } - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use casper_hashing::Digest; - use odra_core::{ - casper_types::bytesrepr::{FromBytes, ToBytes}, - Address - }; - - use crate::casper_node_port::DeployHash; - - use super::CasperClient; - - const CONTRACT_PACKAGE_HASH: &str = - "hash-40dd2fef4e994d2b0d3d415ce515446d7a1e389d2e6fc7c51319a70acf6f42d0"; - const ACCOUNT_HASH: &str = - "hash-2c4a6ce0da5d175e9638ec0830e01dd6cf5f4b1fbb0724f7d2d9de12b1e0f840"; - - #[test] - #[ignore] - pub fn state_root_hash() { - CasperClient::new().get_state_root_hash(); - } - - #[test] - #[ignore] - pub fn get_deploy() { - let hash = DeployHash::new( - Digest::from_hex("98de69b3515fbefcd416e09b57642f721db354509c6d298f5f7cfa8b42714dba") - .unwrap() - ); - let result = CasperClient::new().get_deploy(hash); - assert_eq!(result.deploy.hash(), &hash); - CasperClient::new().wait_for_deploy_hash(hash); - } - - #[test] - #[ignore] - pub fn discover_contract_address() { - let address = CasperClient::new().get_contract_address("erc20"); - let contract_hash = Address::from_str(CONTRACT_PACKAGE_HASH).unwrap(); - assert_eq!(address, contract_hash); - } - - #[test] - #[ignore] - pub fn parsing() { - let name = String::from("DragonsNFT_2"); - let bytes = ToBytes::to_bytes(&name).unwrap(); - assert_eq!( - hex::encode(bytes.clone()), - "0c000000447261676f6e734e46545f32" - ); - let (name2, _): (String, _) = FromBytes::from_bytes(&bytes).unwrap(); - assert_eq!(name, name2); - } -} diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index bb4dde97..cc1d62ed 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -1,17 +1,19 @@ -use crate::livenet_contract_env::LivenetContractEnv; +use std::sync::{Arc, RwLock}; +use std::thread::sleep; + use odra_casper_client::casper_client::CasperClient; +use odra_core::{ + Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, + RuntimeArgs, U512 +}; use odra_core::callstack::{Callstack, CallstackElement, Entrypoint}; -use odra_core::casper_types::URef; use odra_core::contract_container::ContractContainer; use odra_core::contract_register::ContractRegister; use odra_core::event::EventError; +use odra_core::event::EventError::CouldntExtractEventData; use odra_core::prelude::*; -use odra_core::{ - Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, - RuntimeArgs, U512 -}; -use std::sync::{Arc, RwLock}; -use std::thread::sleep; + +use crate::livenet_contract_env::LivenetContractEnv; pub struct LivenetEnv { casper_client: Rc>, @@ -72,24 +74,15 @@ impl HostContext for LivenetEnv { } fn get_event(&self, contract_address: &Address, index: i32) -> Result { - let event_bytes: Bytes = self - .casper_client + // TODO: handle indices < 0 + self.casper_client .borrow() - .query_dict(contract_address, "__events".to_string(), index.to_string()) - .unwrap(); - Ok(event_bytes) + .get_event(contract_address, index) + .map_err(|_| CouldntExtractEventData) } fn get_events_count(&self, contract_address: &Address) -> u32 { - let uref_str = self - .casper_client - .borrow() - .query_contract_named_key(contract_address, "__events_length") - .unwrap(); - self.casper_client - .borrow() - .query_uref(URef::from_formatted_str(uref_str.as_str()).unwrap()) - .unwrap() + self.casper_client.borrow().events_count(contract_address) } fn call_contract( diff --git a/odra-casper/livenet/Cargo.toml b/odra-casper/livenet/Cargo.toml deleted file mode 100644 index d7237759..00000000 --- a/odra-casper/livenet/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "odra-casper-livenet" -edition = "2021" -description = "Odra Casper library for live Casper blockchain interaction." -keywords = ["wasm", "webassembly", "blockchain"] -categories = ["wasm", "smart contracts"] -version = { workspace = true } -authors = { workspace = true } -license = { workspace = true } -homepage = { workspace = true } -repository = { workspace = true } - -[dependencies] -casper-execution-engine = { workspace = true } -casper-hashing = "2.0.0" -odra-casper-shared = { path = "../shared", version = "0.8.0" } -odra-types = { path = "../../types", version = "0.8.0" } -ref_thread_local = "0.1.1" -serde = "1.0" -serde_json = { version = "1.0", features = ["raw_value"] } -hex = "0.4.3" -reqwest = { version = "0.11.16", features = ["blocking", "json"] } -jsonrpc-lite = "0.6.0" -blake2 = { version = "0.9.0", default-features = false } -dotenv = "0.15.0" -prettycli = "0.1.1" -schemars = "0.8.5" -datasize = "0.2.14" -thiserror = "1.0.40" -itertools = "0.10.5" diff --git a/odra-casper/livenet/README.md b/odra-casper/livenet/README.md deleted file mode 100644 index 4d6971f7..00000000 --- a/odra-casper/livenet/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Odra Casper Livenet - -Thanks to this crate Odra can interact with livenet Casper blockchains. diff --git a/odra-casper/livenet/resources/proxy_caller.wasm b/odra-casper/livenet/resources/proxy_caller.wasm deleted file mode 100755 index b20b9aef050ea79c5da41fdd24b847f32683707b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41349 zcmeI53z%KkRo~C!-aB{Z-jR-8*4VPGbFY= zPh!u=dW^(4rYp=cb+WSWH7aoqID2ktscb`lzU%nh)KAG76 zgS@yufa1&1$(r7Ht{apG14JjAE|;aLhUjDzooqN@q{Wo;i130;HuqLNncucG^(q@06h1|%A9}=0eAVFrbQUY^!%AqzVcgq0)(Hr03~y8T0Z-*D>=7v)>knAgPVitP}*uYTFrLc zti`P~Z8y_qvk7Eakanu1ZEeAtt+chh73-nhB3oO3nrWJ5trp<4rGG8{@wWkp{8>A0 z45#)y*rIqd-qCFBjO)9QB>vRu@wvEGtEX|aqj7zDIWAULuSBiOQE}y|%J;x+wZ_8> z51(CrG>K~J-Kae@H}}B&%KY5InFrFiH8=O*!u+|p`{yq#q{-l592HRVjc6$;oL;_Vl|NB2zSK<**khjN$yCzjE1pW4$!z$W$HSB^;_O{2(2ZX^oa6w=lSj&0 zb8kEs(eWs1$E_mnB*|=^7R|+?w%n~{|J0g_7gbpKF5f#$D`l0*YlmwswR=3u^i_;p zJRa?`r{={wq8&xliK#7`N=5*Jb0trfJ2l^;)zhsn>3g)B zyuo9T*YhO%?tsHcrw%#blGhjWn3N|U2fG_`@Hm)e(}~Brx!A4e^-*4v#V%AuFtWKQ z`l7t{+Toa@HF)&bRV`d46%N4dJTa1>zZ;34))GD9a?)}!zUaDiJ6!?%k5og{%Kq4W z8|OXj6LC)z-XAY@BQFcWB>P??C@P+b7is*H(c-~0nofo0(+2r?1+ogOK|U&esRzuX zgXvw<>1qu4_oGE9?db6{?om?oY$)|PgBcGfm8?vs&nl~GrM(;`y4=~f3nw}w8ciPT zA~=@Tqdh9#AA90kPbV>|aCYrq6^)AJ#cjYjXcUeU>WN?{Ubf)jYDxXj3Ce~uqzG*`W--H zQLWQ@>br?lYQN2ABgGOP-whvxIu{_8?gCMSpwLgV6}X(3Trya zZk8~S8eZ}SOfN^czd|Pb{=L#4 z&>3g{K>(;hDw}>_GN$O|E?g)^PcL@TR9zsMbmAshTknMNZf2vq)=pjtV&DjBeL9Ylpw6Jv+cAc5 zY;$TJ->nu^HH3@OM94uOEvi*}s#0nO3S4of24B0;D_&4NrOptcZ>_SlN2w&D6GN$b z-6-{wI+Uu*OdvL)6oS`Ln)WC)!TskZxVxCt#Ki15Cs28^y|o=8b4bp9Sg`G_y*`gR zEBg`4SIcYLTN{;iU6TDzSF#o{sDE>3np{pUH3M(2q)Xk{@m%7uSYVT`M#Y}&2UG~q z=wk6s1GTbud(g~c@vMPr*|QErzL~}BH+=eY2~2@c`mdJ#M?uwby}wF$ubbeDE6z-a z3rf*`H^C6;rZPq1JU*B{mvn2>=@p(wlOIbubz1tl$jp)S%hf_o>RowV!@0m_DSprp z+m`*U9xkAE!fYGY#(3Y`Xy04j_cqe^mif2DSEjG$FhkWW|AT(^6Kd3^gqSG-posek z!)Q4BP~d||xe^!K(QIUSCSA<_u}5|l7GS>`kcVy^l`ngcl>#Zu&@N@famjdODa_R_ zC0pz^ydmexNad&radsf2h_BDU;#Fa9ouyi`e`VNvbX`jJqaHKNM5@TkDr7e@I`Up? z0)U~q&mkrN#FR9IR-cKTo>YI23OzAtG}xc6kBkX|=lyVFf#4V_p&iv@3=kZHr3yFl z1;=plaG`M}_FV_rp3q-*gpNh4$fedP9{sp@cd}S^gC{aBz9%tDNKzVD5f|UN257B+ zOAkoX(c{T<`i#vJLTg6NOKfLK{EQvr0Y2UjH-0I9gO*yf+LC z#+I=JAP*%=H0F%^f>*dNxN%?b3isU@b(ODS$!8=o5=pVS!u}1_gLQx@(Y5Nqx?n@~ zV4bi=Jyg2ENvXucSmNr{s52=Q+<4c6o0JM(ky7PHqsazQN<~hUf<_zkDHUER1&uc3 zQ!3n23K~u3mTK!4p>lM?Q! zuBPl&5XWM@OE!Kg_f0X4g}~#LD6HficpUQp@UbwfIq0YcY+6k8Lm9?{Ko9mq8Nx$A zJN;0GuW-A*AIi|h7g_8mp*YW?!RhqAf>o3dzIRwoL1b!LCWdoS*gKsWY!G{=!<&9u zv3G=Gu=x6uDJNXC7_tkp6V#rq4&y2xnA#1T2&HW@$Mwm-Vf^CNvXY0=zYd40DGU&`&Ss#cu(c2bC$b& zf#n#kpo*M@abG^!I{*7jinJg@cl>J7^Bnb-TI=OBNRyUt;knlRQe^ett&!Elq_y2` zn7kf}5VNtUC0A_+>BK3DSBb9?q@&>$;dD+z9i>J5fc&}fa%ZqeI;`}OB=f#BD&Cj8 zHk`E6FUTzoq`vM}KR2CM+^yw9W6~1p)3P5*KEHfi{mK7_x%$yGt_P+1OVJie)rryz zC#oOoF*R{@k2<9~zF6$V?P5|;YAS$QiUF)>K7d+<0bHl}#wFIbvG71_W5ieTtjDX7 zRnvQWndHjWZ7wcN7)wXbm3mVeYuxF%ctZsTeda5{!E>!D4t<*z`+F*iDQErAsJQAr z8hbzf8fP6-d+Dl^wuL=XHLUWn|C3JI@3eDUGR{>Tym6^qGHh>IZY{V_hUWV2q$1kf zUX0x@?3uGo8!cJU_#wAB@JgTDAI5Pl2<0{U7c8jy39fMgC?&pqJ!?+>Pd1rz2vtVW#H6ZPv>>*0%3`K z!cbuSf6loYMUW9yA5B)Aj%SZ-EV^(!ItAMGL(%y=qIo@yCU2ce9^|Ppl{~;#?Ra$3 zRh#`AS81_|uM=XmbCvz3hrI?1sCX)tyHR?%UQB|9W_NqJO{t&7?(u-UelR^Zog7ft z)=}YL{25o#Xp(np#mMXAysgW~=l|Ol4<_c=sLPB3FxdlOtO9sV*)BCF2c}a;Y-+Y# zO|7KG<$FXoJCdafC^0V)xcwc&O$i+e@Qv3`u6ME_ z#7$Kp>9jYE1gM*xPTFMJE$pDe6JEoy4ge}jzySfSU>%MG8Zf!VLUX%e5V5z)mbtjW z02i~o^Ion`-m$6-xLZ8w4rr}VH+xh$+XieJ&^Xh@(v$fE8dg3Mh6Cy(Z4u;6d&&o= zp$0-|LG4y-opP^JT=XtvQz<_u7PWpV#&eAFL`@gChkxv;D=hd@jo!uLvbG%KrSsj$ zR5l*uWR^ikwht@YSKB)tyysJ;M{EHXxLhqyc5#+!ip%teho#DQK2PtgK(ZB@V$Hew z*m-Ng*C2G%@|0_b{tpPSqi%3RGfy!&bF9v&^&9f@Q><2{g0Y?BKQJm1sA%8_%hGhX;izDztw#l#K~&VMRMZ_6^&S=U=L);miKl`VdQ{ZF7CAF+X?%JFj0)gJg|N=+ zQe5>M+RTtl@80LdThi_}Yn;#f14-Nn%JY-V=(MBhiUdH~87Q_)EoWp~zeA`&%PE=F9C;fKIv7?c z3&E^zeBbuHuS=iCl2woqZ)W|;Sii}0MLsij9hTppAr22A4kp)ys@Dz1- z2bT0b+LcNMF(%#tA}IcTz+t2_06B_I8bITa@+52g4Wp9=@&P)UAf_%1Z-AMZ4qm#sWKV$zz41aGt60Q;ltq--4dPyjSUSbs&qmM&eM9!34 zbb~G=Vb}2pzcQ9lfSHfu?}{mz_i=nvFBFQ_QrDVIRMxv~CvIkGw->XwTT$m^i{)hN zCPmByiHauN1aV}7gUfBjg26+_8(AvWC9ZNd@#BEcuhrvLAoerWRjarjvoQG?H7Fe1 zU#r}UguEUmSWSsSGMoL9@+vw3G;Av^5x){}i?i>sG!}2nO-5MFSBNa+v5{9>%-&HU zZ@|c_7kfosUF2zj>d+DzHCY1K82hq+ty0DIurQH@+AOxKn7I=*-sBeC6Q)cDHUE8D z+|&=nSttZw(GSg?TX3u&dh23$n7or=GG{~`jD|687R&Dst_}A~c%>mWFX6BNc8m5Qu@} zkxW-Nu)Ke;jyw-*aa{3|@6mh3KX{YgEB?V(=)K|}WCfEJhXNlO!?BjQbP0mDAwn9; zT3eK3Sv+TLRthN8H2bZV(zZ^Z%a7E2pg#}q-p2WXq&KXtROK4#WJN=GUZBVy{PBivB0{6zDhic?47>Ch_#iA?=Ie(<$JkQ24gpBp2NgyFgL-WNZ!u0BS^`!XRcXy|CW}Hwdf6{FJ1*j&A*C z#Xo`eOB`4zI5bR^q*C-2z8muuR4;B4Il)1e$(hR6AX^JN5JJ|^%Fd-?OFLD`_J!0l zUxpc*Y=>e76WIDWh2YDJcf^pC*F};O6GOo>T^$}owx{=t(i;Bd%~lY}`ZYw+$)2h( znu7(cK-(TLk&!jfypiwyihe=U&fIbFJS$OSGz#=>^Po&kRTHo(+&2 zCE%T{4$SVEs3>>O9CTC@V$$0^b5JI^A)w{%nSYz-t!$8Xw zD^tJQJrlC`zIM2dT{FPssACO!%o{vnsnkO6=8+3wVsO{xNQKz@`jr-zV_3#o&#?_R zP*RKtpj^YYo28@1!<1?7h)9@H?Y%PP+B?D#w%zO<_7D3hSk#1VxB*m;)6Cc}rDo2F zU6#6NNUzYFw*NMWLSbPYCWlDskX3h>JnWtQwgkZ~*WT>6g~`X#*>6j#`)|keUfF-6 z+X0jtaM1}ZSL8{S=0xF^JHkoTU6rqyVxOqTgJEoG3#0CI8w4U6m=ApUp(Sq{@)p9f zX-lp)L@fkn8^r7`+3)HgL0nL`r3vGD|Dck0^{d=QHcxkTh6I@HC?>ixst2m$SwS1+ zNLHLir7hedu2l_+(3WchO&;s%F2bs<4Wh2K^B#C=7$Ql5=k|^yg+8%&Bq{Wnz4sdg z=6Vc$Zs|ypB+3gDgFHNW{61_N0Cc`e76B8!@Edmcv6RwUF#87~PpwO=HtRlFD+pLV zL^m@a^WmJR5$Th^($hVip~Q!KH!B;Cz%0@}VZ$(p2Qb^tw*p1@Z=yLOyjEohCQ@?4hcbXh5g^1PKwUXClLwe^JOR?|06*>sw@r;+JY(Zay; z=`?pwqtofAdt!5G+&#@Gl5-|#atKdP%sllTugn=q9r?u#JT986257em z?F4A23e5vFuR`|+=>977<^a993cV>nZ>mCX4A2{^P-6Jq;MiA%-VmTSRH4@g==D|T zNPv!1p?d>#Zxwo7fL>RH?g`L6Rp{;j-Cc$53ea6u=*|G$S%vNh&>dB17NA)bx;;R* zSE1VibXyfV9H7HhXgfeTRpm7hW(!2lhsLYo1~5wc#r0|7d~SwpAq zXsr>TjViPrpm_dzdF^-=mJTN_E6_CLO{>r(Ky|XQY%LB@;$sBO6I@te!Qly3e^Cd6 zTI;d9^5g_l+A5lU7O2iq6*{Nnyrs-qY$v(6r$la7EO2enex0V=spLKsImVG%8MhLlj~>|A`Y`26$FKRI~? zL#-Fc&yJbMv!)u#!GSZ3BG|;-ZlV75yzO!%i*yP`dvOUKiUG7#TN3ghXR{=GzOcyH zOFK=ud;8#Bo}c8r>i%cS-pMNh2<4WP_y4C1o2V zaY9XPH8)7YZK@=;j;hGhi`tWX`^L_!V7z#TAM7SJ;7auRVGS8|Twc4IVS(Mq-ZBYv zQuajYb@y_NQTe1`3e29tB66K!c9M0A*-rLk_LM!Djb%?}ciEHKV)kVAnT-9~(iF0c zOo6~uGE2k|{Y#h5>WhwdK0*)BmV`!sUyeSvR_kYzUNB3~RUWwDzM&kV!J~K#Im_cw zn8JX;+BWBSy=+t4Glm4qPq<~#5Adywe!%O73lj?)J|WBvT;YySWXk@xhO8PiccG>} zxo0P$=tTYw(^C1@`r~zYmb&#jq8K)yEnrB-6P3Y`HcxC!YoUm$kXzQpG|5-S>Oqb( zmcYsE2L4=Qk|;2)rM)xO!aw*%%Sk&|FN0H669S5=S7sVl9y*vqf(cN*cJb zF+ziECG-&1o4Pk)C%aW6Q59X%SacQj^s_3`TMsuMv*;+7j<$+8@T-cNt!FACzrL^4 z%XF`)b30Lsvbzhh?_o~&AxyS?M(2+D7MOk26gzth{#>@X>6V7yp=X@u3~wz1oyIMKxb1I@vqR(p8c_H4(_UAy;Ow|C_F8}{9J z)6M(yPWP4ruXyFHqX%F0>f2fcM=*<&4@WGEI-drg0X|JWgM3(+7hDulwD}D4*~Vu( zpN!8AK0EpB;3pIM5k28wJcX-5%hX2I^eu%Qv z;@tfuNkL62DFcYNOXo3}R{wrowcDf(l{97&_468G ztqTGsMXo7{+7CvtMcn{8$(TPNSdsIT>xl`R!A{#m>&N^j|CPPtJ&e6m)!r*U?T8&` zVQy8}g!aN*K=CiHLP2bmXT5^hDtAy2o91x}!X!ICEmx!^mEdKa;%zWLI+pW&FywWfymh$usHnTJTSY-f}j7BlVZr^qO&)zPh z$}V4FdaynU*Aft*MP``{WOEZitM&01+a{THsMul}q-xSShLWNLg{@?i$DE)wZcV|| z2YcR^x}eU-KTXEWH2E70TmqJii#_T01k4EjQ%0u zws9g#x*_5`zmXPo+HN-csrBcTj9umHX9!`iXg62&mG$Gi4ckIipAD%{ANg&M$T^dl ze?KZ!)C&nooaZwM#HB@dpf+$6ay5gn| zVQfxe;qDef;Xqo|B=Ac^6F>91$gW_D3cgu6ogIRRozU?{*AwlehGW&lAoK)F5$g~& zjwJcDC|yM0B1&8}Yv)#KDb_1C91>y%k5j(u|Id?c+ba{XM z%-l)A2qlzt}&=dM-|ERZtw#4N=X-SvQc5t;)YT+Dt?tm0V^8Hq|C z)hym9-pDovOLD6fzcmZ}BF49g?@L=VREP`Rtr*{>O4xrjYlXQyIHI)-Weuz4AYb06 zt|vwIT1`y!=2OS5;(Y4nA4p|XlT!NBfpVrtegwCU&?>XeE`n7n-24*LAJ!Ay0}!h< z4;xnh-o$}D;>^{tTSr&fX+0F}(9ZF$aDy&8%Jqa&@PcKGz6mpsdmohZ$`-HTAe4j# zJEOrpZ`lGe4i)W*mMd*exw4zit|y!wFt-U+J0Ct{$FJFFoUyXW0^ZJgyOw*^CQSHl z0pI$?I(Op5y*u$LM>K-T22*Uu%eJ~|?>jMisarom^wDr^0#tvGIvwd65DJarbJ`7l zy>`p#4A;*JTGS@kV~g4rmIH@95p|=kMP>@p<;-TYgnbl*#P6dF!d`bi$acU9hLF5n zLG6riazTONZi}S<$NbsS7`|g#|SF57{0_d5e-OK;V?kV z9S|_whAdxAErZ=kI{TDELkC!(oJ7U4 z>clcRbpt_!XzHwlBnADhz&hX~({Mz~a7skeCaqd{Tev6^rfzewq7pZ28E=WW=%Nk0 zfKZV?M~q#e-9V<=wi6d&$3HJIrnLOvvz`V>n=f!NvC=p0>{xJ{&wx$Ny*Ie{!^G8jDM+SC&Jj#u6MMtxd^=*g%jr49#ywXlK zFGF-pu(DZGU-BpLvfEsGy|6j1%{6P5N6o8O3hAv~Gms zZlJ&;(@K6$Q73#vj?JJ+JfXe)bNJ1$(*8MA8_BhG`{%T8+vnOZXPDG|*C}{i2YhVv zoYCY|s#^t>`{2MC9|(Wg2RA3Fy_IxH*d3j{139INgLyhOWH1J|c){Xxm-Z zAsw3u$%roSv$+OG;qbCnH6*sxL2o45!VwvZv!L!>a`m}~#NJhuXRwd}u{8kAog4E^aj&ld~6Ld3-r zj^5nk!i{4h#{JMFKwr}U;Ss)T39+!t%QO@d-EU#bF0%s&xz$({Q?mbVZ9=Wi4uXFO z&|5oq!-F*NV8GAacC-}5yd@jxxqdd7DdBEa(d1Mz0W_~-u6A7vf1Kjf1XM9oIveLzZ`;&X z_Uj_5_3d$@m~cN^U^J&Lmt%pr_i-bh=9=1NNUFSp6xe1lI0~&1^E9(F4~wNdz&tk- z*QQI-gglCeVa~VP07>DLNA9CB`&rhE<~%*PzX-?wJ$D2Bk+vLrEUlMgK*`FrT@LhH){p3g7wh)!Pie;c;;3e8<^{W@{6(kp zPvgx`FFMhGYHqG@l%iQ)teY!7U$=OHpm@H}H-;SJIN7#WKOfZlQ|{9?i3%#$gBT@t z;|17fpSJOclC))q>@+O1YsriDr0qV#uD>`(<1L44s*;8Ysk@^n-7L}Ylh0LS(_b9{ z$X~cRL#C5fN}>okTE^~pV1}Rj95Y;g7e`mxMqD?5edY3dbDMzd9*e1Rh$=%Cc=SF( zNAvW3PrAFMwy1h&gQ^J^Eqf2P$?&e1t+$%_t=#sZ`E0z`X zxfKtXNmA)$yCWcsR^MH}Bg3VLI`LYCVH5qfy#sxJ8V#o)1_lT+Brs0f-J-yJjCySP z)f8#kl-?{&a#W^~S9~kqjp1mgURCmQY?e%JljmOUz*a*_fq61sTED1$&i$d}vRbxlQZ1xD?i*HlD{sP>L)Dx#G_JG-VLa@Y~; z8W?>w~hlbtznVt);yF^)E+1)y_&|ezQ#k-I4^!*bAZL*@grKgVn}l!1C$neyu6wgc3fo( zS`CL52Cs>Rog20QqRnfnhC8V${jfPJu0%pq~_cK zTTLX_n#Sy9z7&fdPW0@si1y>8F$T|0MVwoQP) zMdloex7I$OG3)_MMz!`W6@a#O+g$+i=&JzQSH_KoY|3c_gbTMt)=MEi#Ia0M{5uU`*WctM+A3|mXDHviju*Tc2F z-@a}=TzeSpJ?r7xUS|$oSO0Z_@6xHsRUIWQ*pNl)|EpP`wXtY5rgv+j$&;~s!UJUc zUM_`2-;wr2+_eZ?P7fW59*?_?JL2!P%ujQnpbC9=jBlD-JfDi$`eKQ5oa*>)+(B-> zDg1=K>53=gys6DW?bT?F*VNqsgo`8QU9O}@;B|^rs~2+tae=++UyZxm2$9DMI&XyR z{HCDpkV%*b)JE}5jIwXDW=*?O5^;59B()S2#MLsDJjHrSrCrltGX%%zOGzLiAb%qg z)#i(ik-w3ClS0}98yyd`Uo_samf30Oa;C@sP$tGn$!1*jo3k0j3w{E0b2dLF>c2ED z%SjHGOSZvf?U=x2E=visqAe^3-NMp@ugAC@YZcPAp|?!w;Go-jkJ(>!|mQ~#fxQL5~eY^ z6kYh&w7$=0uUvDvz#WGfdk_t^RsVyif2ow*fT-Ri}~fZz~t zJ(w2X$iR^@O}K~8>(kMQ9<8z5DLYAP(;ADJ^&PXR#En$C#^inf8V)6;P%-apC{!AV z7HnzKG*TsdzsMFo8h;IL)5Vb%zLzI(Bz5dxQM1{|Wh}x>jSzI;_ zSV|OE9>2006|XKn{WiX{KhfaCP7TnHZwURlDzxl1S0y*7g~Crgu~C(u-MBF7mtT|b z6@I2#coTZ?s%%K8`s3N~Q$MX=srLfgv>9J_<}s@^B5~Z`5LZqR((|#qsDK|_7;vYz zlgc(yQ~7g(s&bBJXftjgY(3j`92T);Kf=HTUeZw9dUb-JpbX1ubg`(1;3L(N&KfY6 z#ZmKWhdV89!)D^9tkG`MZIRY}T+s*>x@e;PN}plb%hzlA+_!|A(YU)!wV?nWo$2T1 zbd%+VG-G`}(lC;t(gJ3K+ir6RpbrDiXAGBL&vo0}lkd56rG=NLf9^1|kZcZNy4dq8WQv_M|-~(&My}{SS#r)OsV^cDL}j8Wl)btn0l!+A5=zJ~urO-1 zqZ3d4?7Kd6<@f&h$B(z3Zg5d=GHcc}W@1u2N{p+@zkZ*EWARH&kDceX3jLnaogMuQ zWtz@vc&+`LV-zpqOvVJa3v^`b-9_(mgFo_`vLADvC+ub}5@Va8i1`W2Sog{H|HbtB z;5+C=RfQ*OTN=d)%S@CS23b!0uuvFcVP_Ta=#sGb-e8dkt;O4-sH6Mio~87*IIa9i z^rM)aL8!IrhEyHmOm!^3;(W*zb@))h(^OE8qbQ=sRSx@d`=a^Oldm2&_N7*Dhc}hovo+e~t_3^ho`PTFNtb+{cK7ceE^1w}24Q*ZYut>F06mSS^G+-5(NQkJA zmWd8)wJEG|=vJ`ib}F9{K5^HekaWGJ(weZHairTiv~6noC)aK$PtsdBI1pM+(rWkb@it~ zS;vRC7n$2S_OWhZfN)So%PBffM`o1HSQ{fC+sd*kId77x%YzI;P!Uv!xNKE}n(kIg zo?14&Kz4fGjV~VgH8rlAk+9=wCvL*hA3NVsuNVZj5t-zK=pMp|xj{;@XHQA_l-d&| z>}*erK(bD189Q;}i}#gEEF1JNvS`0MWZ4)+Q0Rz&Ojc`ja4d@1HlQ<1{<>PPinhl^ z8=WW9VpG!i0&2}++RxB{vKOTP2DupD4ORv2gKV_cMuAxeJskE;^QK{b01vk z#@o`AL+BD`61Qr)wo%jYtd$QsE+XCB>ZQxDQP&qc$d$G`RIMY0kxBzM!V9(2iIQ0f zsQ7_Ekc;|M4=nWTB1G~4dST;3?njqwfBG!{2XNEZ1+`?wFd4Jy|O zJk#Y|7AqRLpg4k`&5OnJkAH69JdgIhh|O3#&0TQJG;)qvhCqiS58L1(O8__T2c;U&9 z7oPlh;mMB|#mRV#m`q}akt3v13Ejs`;kwzoDlrsV_r&h>1d^M0b*Tx3xq`v2WwTeT zb{yGsB+-T3a7a2Wm@IrEO{?|(|fRoB~it1M`!4hc%}G#tp^qlMG?PX#9Vbrzi!tg`Sg;@ zxHbz)Ku3e46VLzDuOIq)jdywv0k)7XoyjeJOTX#r(V)IAH-idJr_%^UC|q8O4v z;b`%dV4?J*>&ymGE8(G;2B&PW!Q^j^-QuJOb?_5qx+VB|&1<0FJCDOH#M|If9&gazhpuVD$kIF6zPWMbfudt6(i>%iy0Wj zN^5fBCn!M;J@#i;bdCw0s0@mQT3G|pF!M1}q^f1+@5EM6Wj4-D-T@nMOx@sU7M?lR zF?FGt(h%#3^s{_Fkx}c1>YjU|FPRnJm&{HU{~;RyUkV$5w=ulcy~gCTckx;~JQZFu zLDflSCb2}%Yqp>JdCm2?2uPLJOv{1SHP3670$0y#9n9X4*Xc&Q*2&YW=e2UL<+Us) z&A&1pct9kYY#uD^v+to3j88)v_L4UOe1g1r9;oA`Y!Ec0idz*Wbjb5XFLWx1a%=Q>K((?o zu~C)QE8Y{mN*Op|-NI~QyEBp!ROOO>2$K28Rp5#4NuLFk5knLjH91ADJdf7OX}^F( ztq;Zi_;641wjSjWf$)pr{UYzjwYsh=0jI|jH{M)4oD7uH9ZlNg)Ud0JPAE5v$c+Gr zcy*wyZwsOMNHb>rym}>SeXx1z?3tD2`BN)%=jKl>&A)kJ?!oyB4=wdHdt~{-!rWZtC5o~rigxhX$!8azTlu`Q@~!mn zspjhd*Qe{h^*mU44(S_xpPD~?df|cmKoQ$HB!sN3lE=Nel)k&c`-9T`=&g< z|Iw9&3;CPoAA-w6Nfzy)j{STO^Zgm>zJc%W1IOz7_xpF6v_HH?DWCE2@rm)n<449P z$B&LrjZcryj31jApO~09JaJ@Va^mR3)Wr0}%*3(7A3l8K@Z{m6ho=rtAD%gU z?8x|$i6e)P962(1%#oSNnWHmPGt)COGsljB;xU>(M%BkCb_`JbGXHU0{Zjr String { - std::env::var(name).unwrap_or_else(|err| { - log::error(format!( - "{} must be set. Have you setup your .env file?", - name - )); - panic!("{}", err) - }) -} - -/// Client for interacting with Casper node. -pub struct CasperClient { - node_address: String, - chain_name: String, - secret_key: SecretKey -} - -impl CasperClient { - /// Creates new CasperClient. - pub fn new() -> Self { - dotenv::dotenv().ok(); - CasperClient { - node_address: get_env_variable(ENV_NODE_ADDRESS), - chain_name: get_env_variable(ENV_CHAIN_NAME), - secret_key: SecretKey::from_file(get_env_variable(ENV_SECRET_KEY)).unwrap() - } - } - - /// Node address. - pub fn node_address_rpc(&self) -> String { - format!("{}/rpc", self.node_address) - } - - /// Chain name. - pub fn chain_name(&self) -> &str { - &self.chain_name - } - - /// Public key of the client account. - pub fn public_key(&self) -> PublicKey { - PublicKey::from(&self.secret_key) - } - - /// Address of the client account. - pub fn caller(&self) -> Address { - Address::from(self.public_key()) - } - - /// Query the node for the current state root hash. - pub 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).unwrap(); - result.state_root_hash.unwrap() - } - - /// Query the node for the deploy state. - pub fn get_deploy(&self, deploy_hash: DeployHash) -> GetDeployResult { - let params = GetDeployParams { - deploy_hash, - finalized_approvals: false - }; - - let request = json!( - { - "jsonrpc": "2.0", - "method": "info_get_deploy", - "params": params, - "id": 1, - } - ); - self.post_request(request).unwrap() - } - - /// Query the contract for the variable. - pub fn get_variable_value(&self, address: Address, key: &[u8]) -> Option { - let key = LivenetKeyMaker::to_variable_key(key); - // SAFETY: we know the key maker creates a string of valid UTF-8 characters. - let key = unsafe { from_utf8_unchecked(&key) }; - self.query_dictionary(address, key) - } - - /// Query the contract for the dictionary value. - pub fn get_dict_value( - &self, - address: Address, - seed: &[u8], - key: &K - ) -> Option { - let key = LivenetKeyMaker::to_dictionary_key(seed, key).unwrap(); - // SAFETY: we know the key maker creates a string of valid UTF-8 characters. - let key = unsafe { from_utf8_unchecked(&key) }; - self.query_dictionary(address, key) - } - - /// Discover the contract address by name. - pub 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(); - let result = self.query_global_state(&CasperKey::Account(account_hash)); - let result_as_json = serde_json::to_value(result.stored_value).unwrap(); - - let named_keys = result_as_json["Account"]["named_keys"].as_array().unwrap(); - for named_key in named_keys { - if named_key["name"].as_str().unwrap() == key_name { - let key = named_key["key"] - .as_str() - .unwrap() - .replace("hash-", "contract-package-wasm"); - let contract_hash = ContractPackageHash::from_formatted_str(&key).unwrap(); - return Address::try_from(contract_hash).unwrap(); - } - } - log::error(format!( - "Contract {:?} not found in {:#?}", - key_name, result_as_json - )); - panic!("get_contract_address failed"); - } - - /// Find the contract hash by the contract package hash. - pub fn query_global_state_for_contract_hash(&self, address: Address) -> ContractHash { - let key = CasperKey::Hash(address.as_contract_package_hash().unwrap().value()); - let result = self.query_global_state(&key); - let result_as_json = serde_json::to_value(result).unwrap(); - let contract_hash: &str = result_as_json["stored_value"]["ContractPackage"]["versions"][0] - ["contract_hash"] - .as_str() - .unwrap(); - ContractHash::from_formatted_str(contract_hash).unwrap() - } - - /// Deploy the contract. - pub fn deploy_wasm(&self, wasm_file_name: &str, args: RuntimeArgs, gas: U512) -> Address { - log::info(format!("Deploying \"{}\".", wasm_file_name)); - let wasm_path = find_wasm_file_path(wasm_file_name); - let wasm_bytes = fs::read(wasm_path).unwrap(); - let session = ExecutableDeployItem::ModuleBytes { - module_bytes: Bytes::from(wasm_bytes), - args - }; - let deploy = self.new_deploy(session, gas); - let request = json!( - { - "jsonrpc": "2.0", - "method": "account_put_deploy", - "params": { - "deploy": deploy - }, - "id": 1, - } - ); - - let response: PutDeployResult = self.post_request(request).unwrap(); - let deploy_hash = response.deploy_hash; - self.wait_for_deploy_hash(deploy_hash); - - let address = self.get_contract_address(wasm_file_name.strip_suffix(".wasm").unwrap()); - log::info(format!("Contract {:?} deployed.", &address.to_string())); - address - } - - /// Deploy the entrypoint call. - pub fn deploy_entrypoint_call( - &self, - addr: Address, - entrypoint: &str, - args: &RuntimeArgs, - _amount: Option, - gas: U512 - ) { - log::info(format!( - "Calling {:?} with entrypoint \"{}\".", - addr.to_string(), - entrypoint - )); - let session = ExecutableDeployItem::StoredVersionedContractByHash { - hash: *addr.as_contract_package_hash().unwrap(), - version: None, - entry_point: String::from(entrypoint), - args: args.clone() - }; - let deploy = self.new_deploy(session, gas); - let request = json!( - { - "jsonrpc": "2.0", - "method": "account_put_deploy", - "params": { - "deploy": deploy - }, - "id": 1, - } - ); - let response: PutDeployResult = self.post_request(request).unwrap(); - let deploy_hash = response.deploy_hash; - self.wait_for_deploy_hash(deploy_hash); - } - - fn query_global_state(&self, key: &CasperKey) -> QueryGlobalStateResult { - let state_root_hash = self.get_state_root_hash(); - 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).unwrap() - } - - fn query_dictionary(&self, address: Address, key: &str) -> Option { - let state_root_hash = self.get_state_root_hash(); - let contract_hash = self.query_global_state_for_contract_hash(address); - 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 request = json!( - { - "jsonrpc": "2.0", - "method": "state_get_dictionary_item", - "params": params, - "id": 1, - } - ); - - let result: Option = self.post_request(request); - result.map(|result| { - let result_as_json = serde_json::to_value(result).unwrap(); - let result = result_as_json["stored_value"]["CLValue"]["bytes"] - .as_str() - .unwrap(); - let bytes = hex::decode(result).unwrap(); - let (value, _) = FromBytes::from_bytes(&bytes).unwrap(); - value - }) - } - - fn wait_for_deploy_hash(&self, deploy_hash: DeployHash) { - 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 - )); - std::thread::sleep(time_diff); - let result: GetDeployResult = self.get_deploy(deploy_hash); - if !result.execution_results.is_empty() { - final_result = result; - break; - } - } - - match &final_result.execution_results[0].result { - ExecutionResult::Failure { - effect: _, - transfers: _, - cost: _, - error_message - } => { - log::error(format!( - "Deploy {:?} failed with error: {:?}.", - deploy_hash_str, error_message - )); - panic!("Deploy failed"); - } - ExecutionResult::Success { - effect: _, - transfers: _, - cost: _ - } => { - log::info(format!( - "Deploy {:?} successfully executed.", - deploy_hash_str - )); - } - } - } - - fn new_deploy(&self, session: ExecutableDeployItem, gas: U512) -> Deploy { - let timestamp = Timestamp::now(); - let ttl = TimeDiff::from_seconds(1000); - let gas_price = 1; - let dependencies = vec![]; - let chain_name = String::from(self.chain_name()); - let payment = ExecutableDeployItem::ModuleBytes { - module_bytes: Default::default(), - args: runtime_args! { - "amount" => gas - } - }; - - Deploy::new( - timestamp, - ttl, - gas_price, - dependencies, - chain_name, - payment, - session, - &self.secret_key, - Some(self.public_key()) - ) - } - - fn post_request(&self, request: Value) -> Option { - let client = reqwest::blocking::Client::new(); - let response = client - .post(self.node_address_rpc()) - .json(&request) - .send() - .unwrap(); - let response: JsonRpc = response.json().unwrap(); - response - .get_result() - .map(|result| serde_json::from_value(result.clone()).unwrap()) - } -} - -impl Default for CasperClient { - fn default() -> Self { - Self::new() - } -} - -/// 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); - 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"); -} - -pub struct LivenetKeyMaker; - -impl KeyMaker for LivenetKeyMaker { - fn blake2b(preimage: &[u8]) -> [u8; 32] { - let mut result = [0; 32]; - let mut hasher = VarBlake2b::new(32).expect("should create hasher"); - - hasher.update(preimage); - hasher.finalize_variable(|slice| { - result.copy_from_slice(slice); - }); - result - } -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use casper_hashing::Digest; - use odra_core::{ - casper_types::bytesrepr::{FromBytes, ToBytes}, - Address, U256 - }; - - use crate::casper_node_port::DeployHash; - - use super::CasperClient; - - const CONTRACT_PACKAGE_HASH: &str = - "hash-40dd2fef4e994d2b0d3d415ce515446d7a1e389d2e6fc7c51319a70acf6f42d0"; - const ACCOUNT_HASH: &str = - "hash-2c4a6ce0da5d175e9638ec0830e01dd6cf5f4b1fbb0724f7d2d9de12b1e0f840"; - - #[test] - #[ignore] - pub fn client_works() { - let contract_hash = Address::from_str(CONTRACT_PACKAGE_HASH).unwrap(); - let result: Option = - CasperClient::new().get_variable_value(contract_hash, b"name_contract"); - assert_eq!(result.unwrap().as_str(), "Plascoin"); - - let account = Address::from_str(ACCOUNT_HASH).unwrap(); - let balance: Option = - CasperClient::new().get_dict_value(contract_hash, b"balances_contract", &account); - assert!(balance.is_some()); - } - - #[test] - #[ignore] - pub fn state_root_hash() { - CasperClient::new().get_state_root_hash(); - } - - #[test] - #[ignore] - pub fn get_deploy() { - let hash = DeployHash::new( - Digest::from_hex("98de69b3515fbefcd416e09b57642f721db354509c6d298f5f7cfa8b42714dba") - .unwrap() - ); - let result = CasperClient::new().get_deploy(hash); - assert_eq!(result.deploy.hash(), &hash); - CasperClient::new().wait_for_deploy_hash(hash); - } - - #[test] - #[ignore] - pub fn query_global_state_for_contract() { - let addr = Address::from_str(CONTRACT_PACKAGE_HASH).unwrap(); - let _result: Option = CasperClient::new().query_dictionary(addr, "name_contract"); - } - - #[test] - #[ignore] - pub fn discover_contract_address() { - let address = CasperClient::new().get_contract_address("erc20"); - let contract_hash = Address::from_str(CONTRACT_PACKAGE_HASH).unwrap(); - assert_eq!(address, contract_hash); - } - - #[test] - #[ignore] - pub fn parsing() { - let name = String::from("DragonsNFT_2"); - let bytes = ToBytes::to_bytes(&name).unwrap(); - assert_eq!( - hex::encode(bytes.clone()), - "0c000000447261676f6e734e46545f32" - ); - let (name2, _): (String, _) = FromBytes::from_bytes(&bytes).unwrap(); - assert_eq!(name, name2); - } -} diff --git a/odra-casper/livenet/src/casper_node_port/account.rs b/odra-casper/livenet/src/casper_node_port/account.rs deleted file mode 100644 index 56c438ae..00000000 --- a/odra-casper/livenet/src/casper_node_port/account.rs +++ /dev/null @@ -1,32 +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 -} - -#[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/livenet/src/casper_node_port/approval.rs b/odra-casper/livenet/src/casper_node_port/approval.rs deleted file mode 100644 index 0a915cbe..00000000 --- a/odra-casper/livenet/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/livenet/src/casper_node_port/block_hash.rs b/odra-casper/livenet/src/casper_node_port/block_hash.rs deleted file mode 100644 index 1870a93d..00000000 --- a/odra-casper/livenet/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/livenet/src/casper_node_port/contract_package.rs b/odra-casper/livenet/src/casper_node_port/contract_package.rs deleted file mode 100644 index d317cd17..00000000 --- a/odra-casper/livenet/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/livenet/src/casper_node_port/deploy.rs b/odra-casper/livenet/src/casper_node_port/deploy.rs deleted file mode 100644 index 3d531b1f..00000000 --- a/odra-casper/livenet/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/livenet/src/casper_node_port/deploy_hash.rs b/odra-casper/livenet/src/casper_node_port/deploy_hash.rs deleted file mode 100644 index 63275627..00000000 --- a/odra-casper/livenet/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/livenet/src/casper_node_port/deploy_header.rs b/odra-casper/livenet/src/casper_node_port/deploy_header.rs deleted file mode 100644 index f4f91b71..00000000 --- a/odra-casper/livenet/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`]. -#[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/livenet/src/casper_node_port/error.rs b/odra-casper/livenet/src/casper_node_port/error.rs deleted file mode 100644 index c8435721..00000000 --- a/odra-casper/livenet/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/livenet/src/casper_node_port/mod.rs b/odra-casper/livenet/src/casper_node_port/mod.rs deleted file mode 100644 index 7ae905ef..00000000 --- a/odra-casper/livenet/src/casper_node_port/mod.rs +++ /dev/null @@ -1,15 +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_package; -pub mod deploy; -pub mod deploy_hash; -pub mod deploy_header; -pub mod error; -pub mod rpcs; -pub mod utils; - -pub use deploy::Deploy; -pub use deploy_hash::DeployHash; diff --git a/odra-casper/livenet/src/casper_node_port/rpcs.rs b/odra-casper/livenet/src/casper_node_port/rpcs.rs deleted file mode 100644 index 5ef0f883..00000000 --- a/odra-casper/livenet/src/casper_node_port/rpcs.rs +++ /dev/null @@ -1,244 +0,0 @@ -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) -} diff --git a/odra-casper/livenet/src/casper_node_port/utils.rs b/odra-casper/livenet/src/casper_node_port/utils.rs deleted file mode 100644 index 35a9e2b0..00000000 --- a/odra-casper/livenet/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/livenet/src/client_env.rs b/odra-casper/livenet/src/client_env.rs deleted file mode 100644 index 2e24e50e..00000000 --- a/odra-casper/livenet/src/client_env.rs +++ /dev/null @@ -1,196 +0,0 @@ -use std::{collections::BTreeMap, sync::Mutex}; - -use odra_casper_shared::consts; -use odra_core::{ - casper_types::{ - bytesrepr::{FromBytes, ToBytes}, - CLType, CLTyped, CLValue, RuntimeArgs, U512 - }, - Address -}; -use ref_thread_local::RefThreadLocal; - -use crate::{casper_client::CasperClient, EntrypointArgs, EntrypointCall}; - -use self::{ - callstack::Callstack, contract_container::ContractContainer, - contract_register::ContractRegister -}; - -mod callstack; -mod contract_container; -mod contract_register; - -/// Casper Lievent client environment. -#[derive(Default)] -struct ClientEnv { - contracts: ContractRegister, - callstack: Callstack, - gas: Mutex> -} - -impl ClientEnv { - /// Returns a singleton instance of a client environment. - pub fn instance<'a>() -> ref_thread_local::Ref<'a, ClientEnv> { - ENV.borrow() - } - - /// Returns a mutable singleton instance of a client environment. - pub fn instance_mut<'a>() -> ref_thread_local::RefMut<'a, ClientEnv> { - ENV.borrow_mut() - } - - /// Register new contract. - pub fn register_contract(&mut self, contract: ContractContainer) { - self.contracts.add(contract); - } - - /// Push address to callstack. - pub fn push_on_stack(&mut self, addr: Address) { - self.callstack.push(addr); - } - - /// Pop address from callstack. - pub fn pull_from_stack(&mut self) -> Option
{ - self.callstack.pop() - } - - /// Call contract. - pub fn call_contract( - &self, - addr: Address, - entrypoint: &str, - args: &RuntimeArgs - ) -> T { - let result = self.contracts.call(&addr, String::from(entrypoint), args); - let bytes = result.unwrap(); - let (clvalue, _) = CLValue::from_bytes(&bytes).unwrap(); - clvalue.into_t().unwrap() - } - - /// Get current contract address. - pub fn current_contract(&self) -> Address { - self.callstack.current() - } - - /// Set gas. - pub fn set_gas>(&self, gas: T) { - let new_gas: U512 = gas.into(); - let mut gas = self.gas.lock().unwrap(); - *gas = Some(new_gas); - } - - /// Get gas and reset it, so it is not used twice. - pub fn get_gas(&self) -> U512 { - let mut gas = self.gas.lock().unwrap(); - let current_gas: U512 = gas.expect("Gas not set"); - *gas = None; - current_gas - } -} - -ref_thread_local::ref_thread_local!( - static managed ENV: ClientEnv = ClientEnv::default(); -); - -/// Register existing contract. -pub fn register_existing_contract( - address: Address, - entrypoints: BTreeMap -) { - let contract = ContractContainer::new(address, entrypoints); - ClientEnv::instance_mut().register_contract(contract); -} - -/// Deploy WASM file with arguments. -pub fn deploy_new_contract( - name: &str, - mut args: RuntimeArgs, - entrypoints: BTreeMap, - constructor_name: Option -) -> Address { - let gas = get_gas(); - let wasm_name = format!("{}.wasm", name); - let contract_package_hash_key = format!("{}_package_hash", name); - args.insert(consts::ALLOW_KEY_OVERRIDE_ARG, true).unwrap(); - args.insert(consts::IS_UPGRADABLE_ARG, false).unwrap(); - args.insert(consts::PACKAGE_HASH_KEY_NAME_ARG, contract_package_hash_key) - .unwrap(); - - if let Some(constructor_name) = constructor_name { - args.insert(consts::CONSTRUCTOR_NAME_ARG, constructor_name) - .unwrap(); - }; - - let address = CasperClient::new().deploy_wasm(&wasm_name, args, gas); - let contract = ContractContainer::new(address, entrypoints); - ClientEnv::instance_mut().register_contract(contract); - - address -} - -/// Call contract's entrypoint. -pub fn call_contract( - addr: Address, - entrypoint: &str, - args: &RuntimeArgs, - _amount: Option -) -> T { - match T::cl_type() { - CLType::Unit => { - call_contract_deploy(addr, entrypoint, args, _amount); - T::from_bytes(&[]).unwrap().0 - } - _ => call_contract_getter_entrypoint(addr, entrypoint, args, _amount) - } -} - -/// Query current contract for a variable's value. -pub fn get_var_from_current_contract(key: &[u8]) -> Option { - let address = ClientEnv::instance().current_contract(); - CasperClient::new().get_variable_value(address, key) -} - -/// Query current contract for a dictionary's value. -pub fn get_dict_value_from_current_contract( - seed: &[u8], - key: &K -) -> Option { - let address = ClientEnv::instance().current_contract(); - CasperClient::new().get_dict_value(address, seed, key) -} - -/// Current caller. -pub fn caller() -> Address { - CasperClient::new().caller() -} - -/// Set gas for the next call. -pub fn set_gas>(gas: T) { - ClientEnv::instance_mut().set_gas(gas); -} - -fn get_gas() -> U512 { - ClientEnv::instance().get_gas() -} - -fn call_contract_getter_entrypoint( - addr: Address, - entrypoint: &str, - args: &RuntimeArgs, - _amount: Option -) -> T { - { - ClientEnv::instance_mut().push_on_stack(addr); - } - let result = { ClientEnv::instance().call_contract(addr, entrypoint, args) }; - { - ClientEnv::instance_mut().pull_from_stack(); - }; - result -} - -fn call_contract_deploy(addr: Address, entrypoint: &str, args: &RuntimeArgs, amount: Option) { - let gas = get_gas(); - CasperClient::new().deploy_entrypoint_call(addr, entrypoint, args, amount, gas); -} diff --git a/odra-casper/livenet/src/client_env/callstack.rs b/odra-casper/livenet/src/client_env/callstack.rs deleted file mode 100644 index c8adee60..00000000 --- a/odra-casper/livenet/src/client_env/callstack.rs +++ /dev/null @@ -1,18 +0,0 @@ -use odra_core::Address; - -#[derive(Clone, Default)] -pub struct Callstack(Vec
); - -impl Callstack { - pub fn pop(&mut self) -> Option
{ - self.0.pop() - } - - pub fn push(&mut self, element: Address) { - self.0.push(element); - } - - pub fn current(&self) -> Address { - *self.0.last().unwrap() - } -} diff --git a/odra-casper/livenet/src/client_env/contract_container.rs b/odra-casper/livenet/src/client_env/contract_container.rs deleted file mode 100644 index 3e49d0cf..00000000 --- a/odra-casper/livenet/src/client_env/contract_container.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::collections::BTreeMap; - -use odra_core::{casper_types::RuntimeArgs, Address, OdraError, VmError}; - -use crate::{EntrypointArgs, EntrypointCall}; - -#[derive(Clone)] -pub struct ContractContainer { - address: Address, - entrypoints: BTreeMap -} - -impl ContractContainer { - pub fn new( - address: Address, - entrypoints: BTreeMap - ) -> Self { - Self { - address, - entrypoints - } - } - - pub fn call(&self, entrypoint: String, args: RuntimeArgs) -> Result, OdraError> { - match self.entrypoints.get(&entrypoint) { - Some((ep_args, call)) => { - self.validate_args(ep_args, &args)?; - Ok(call(String::new(), &args)) - } - None => Err(OdraError::VmError(VmError::NoSuchMethod(entrypoint))) - } - } - - pub fn address(&self) -> Address { - self.address - } - - fn validate_args(&self, args: &[String], input_args: &RuntimeArgs) -> Result<(), OdraError> { - let named_args = input_args - .named_args() - .map(|arg| arg.name().to_owned()) - .collect::>(); - - if args - .iter() - .filter(|arg| !named_args.contains(arg)) - .map(|arg| arg.to_owned()) - .next() - .is_none() - { - Ok(()) - } else { - Err(OdraError::VmError(VmError::MissingArg)) - } - } -} diff --git a/odra-casper/livenet/src/client_env/contract_register.rs b/odra-casper/livenet/src/client_env/contract_register.rs deleted file mode 100644 index 3ba990c7..00000000 --- a/odra-casper/livenet/src/client_env/contract_register.rs +++ /dev/null @@ -1,37 +0,0 @@ -use odra_core::{casper_types::RuntimeArgs, Address, OdraError, VmError}; - -use std::collections::BTreeMap; - -use super::contract_container::ContractContainer; - -#[derive(Default)] -pub struct ContractRegister { - contracts: BTreeMap -} - -impl ContractRegister { - pub fn add(&mut self, container: ContractContainer) { - self.contracts.insert(container.address(), container); - } - - pub fn call( - &self, - addr: &Address, - entrypoint: String, - args: &RuntimeArgs - ) -> Result, OdraError> { - self.internal_call(addr, |container| container.call(entrypoint, args.clone())) - } - - fn internal_call Result, OdraError>>( - &self, - addr: &Address, - call_fn: F - ) -> Result, OdraError> { - let contract = self.contracts.get(addr); - match contract { - Some(container) => call_fn(container), - None => Err(OdraError::VmError(VmError::InvalidContractAddress)) - } - } -} diff --git a/odra-casper/livenet/src/contract_env.rs b/odra-casper/livenet/src/contract_env.rs deleted file mode 100644 index 9c9db063..00000000 --- a/odra-casper/livenet/src/contract_env.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! Casper backend for Livenet. -//! -//! It provides all the required functions to communicate between Odra and Casper Livenets. - -use crate::casper_client::LivenetKeyMaker; -use odra_casper_shared::key_maker::KeyMaker; -use odra_casper_shared::native_token::NativeTokenMetadata; -use odra_core::{ - casper_types::{ - bytesrepr::{Bytes, FromBytes, ToBytes}, - U512 - }, - Address, ExecutionError, PublicKey -}; - -use crate::client_env; - -pub fn self_address() -> Address { - unimplemented!() -} - -pub fn caller() -> Address { - unimplemented!() -} - -pub fn set_var(_: &[u8], _: T) { - unimplemented!() -} - -pub fn get_var(key: &[u8]) -> Option { - client_env::get_var_from_current_contract(key) -} - -pub fn set_dict_value(_: &[u8], _: &K, _: V) { - unimplemented!() -} - -pub fn get_dict_value(seed: &[u8], key: &K) -> Option { - client_env::get_dict_value_from_current_contract(seed, key) -} - -pub fn emit_event(_: T) -where - T: ToBytes { - unimplemented!() -} - -pub fn revert(_: E) -> ! -where - E: Into -{ - unimplemented!() -} - -pub fn get_block_time() -> BlockTime { - unimplemented!() -} - -pub fn attached_value() -> U512 { - unimplemented!() -} - -pub fn one_token() -> U512 { - unimplemented!() -} - -pub fn transfer_tokens>(_: &Address, _: B) { - unimplemented!() -} - -pub fn self_balance() -> U512 { - unimplemented!() -} - -/// Returns the platform native token metadata -pub fn native_token_metadata() -> NativeTokenMetadata { - unimplemented!() -} - -/// Verifies the signature created using the Backend's default signature scheme. -pub fn verify_signature(_: &Bytes, _: &Bytes, _: &PublicKey) -> bool { - unimplemented!() -} - -/// Creates a hash of the given input. Uses default hash for given backend. -pub fn hash>(input: T) -> Vec { - LivenetKeyMaker::blake2b(input.as_ref()).to_vec() -} diff --git a/odra-casper/livenet/src/lib.rs b/odra-casper/livenet/src/lib.rs deleted file mode 100644 index daf6feed..00000000 --- a/odra-casper/livenet/src/lib.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Odra Casper Livenet capabilites. - -#![feature(once_cell)] - -pub mod casper_client; -mod casper_node_port; -pub mod client_env; -pub mod contract_env; - -use odra_core::casper_types::RuntimeArgs; - -pub type EntrypointCall = fn(String, &RuntimeArgs) -> Vec; -pub type EntrypointArgs = Vec; - -mod log { - /// Info message. - pub fn info>(message: T) { - prettycli::info(message.as_ref()); - } - - /// Error message. - pub fn error>(message: T) { - prettycli::error(message.as_ref()); - } - - /// Wait message. - pub fn wait>(message: T) { - prettycli::wait(message.as_ref()); - } -} From 25b76efd83ce539269fbc34104ac86441addaef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 24 Jan 2024 11:15:53 +0100 Subject: [PATCH 17/24] Handling event absolute position in host_env --- core/src/contract_container.rs | 2 +- core/src/host_context.rs | 2 +- core/src/host_env.rs | 19 ++++++++++--------- core/src/utils.rs | 15 +++++++++------ .../casper-client/src/casper_client.rs | 6 +++--- odra-casper/livenet-env/Cargo.toml | 3 ++- .../livenet-env/src/livenet_host_env.rs | 18 ++++++++++-------- odra-casper/test-vm/src/casper_host.rs | 2 +- odra-casper/test-vm/src/vm/casper_vm.rs | 17 +++++------------ odra-macros/src/ast/deployer_item.rs | 6 ++---- odra-vm/src/odra_vm_host.rs | 2 +- odra-vm/src/vm/odra_vm.rs | 2 +- odra-vm/src/vm/odra_vm_state.rs | 6 ++---- 13 files changed, 48 insertions(+), 52 deletions(-) diff --git a/core/src/contract_container.rs b/core/src/contract_container.rs index 579ee4ae..a675e2f8 100644 --- a/core/src/contract_container.rs +++ b/core/src/contract_container.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use crate::{CallDef, EntryPointsCaller, OdraError, VmError, EntryPointArgument}; +use crate::{CallDef, EntryPointArgument, EntryPointsCaller, OdraError, VmError}; use casper_types::bytesrepr::Bytes; use casper_types::{NamedArg, RuntimeArgs}; diff --git a/core/src/host_context.rs b/core/src/host_context.rs index df8920ed..b2362023 100644 --- a/core/src/host_context.rs +++ b/core/src/host_context.rs @@ -14,7 +14,7 @@ pub trait HostContext { fn balance_of(&self, address: &Address) -> U512; fn advance_block_time(&self, time_diff: u64); /// Returns event bytes by contract address and index. - fn get_event(&self, contract_address: &Address, index: i32) -> Result; + fn get_event(&self, contract_address: &Address, index: u32) -> Result; fn get_events_count(&self, contract_address: &Address) -> u32; fn call_contract( &self, diff --git a/core/src/host_env.rs b/core/src/host_env.rs index 4c52cfde..745dbad7 100644 --- a/core/src/host_env.rs +++ b/core/src/host_env.rs @@ -87,9 +87,7 @@ impl HostEnv { let new_events_count = backend.get_events_count(contract_address); let mut events = vec![]; for event_id in old_events_last_id..new_events_count { - let event = backend - .get_event(contract_address, event_id as i32) - .unwrap(); + let event = backend.get_event(contract_address, event_id).unwrap(); events.push(event); } @@ -133,8 +131,11 @@ impl HostEnv { 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, index)?; + let bytes = backend.get_event(contract_address, event_absolute_position)?; T::from_bytes(&bytes) .map_err(|_| EventError::Parsing) .map(|r| r.0) @@ -143,7 +144,7 @@ impl HostEnv { pub fn get_event_bytes( &self, contract_address: &Address, - index: i32 + index: u32 ) -> Result { let backend = self.backend.borrow(); backend.get_event(contract_address, index) @@ -156,7 +157,7 @@ impl HostEnv { (0..events_count) .map(|event_id| { backend - .get_event(contract_address, event_id as i32) + .get_event(contract_address, event_id) .and_then(|bytes| extract_event_name(&bytes)) .unwrap_or_else(|e| panic!("Couldn't extract event name: {:?}", e)) }) @@ -169,7 +170,7 @@ impl HostEnv { (0..events_count) .map(|event_id| { backend - .get_event(contract_address, event_id as i32) + .get_event(contract_address, event_id) .unwrap_or_else(|e| { panic!( "Couldn't get event at address {:?} with id {}: {:?}", @@ -198,7 +199,7 @@ impl HostEnv { ); (0..events_count) .map(|event_id| { - self.get_event_bytes(contract_address, event_id as i32) + self.get_event_bytes(contract_address, event_id) .unwrap_or_else(|e| { panic!( "Couldn't get event at address {:?} with id {}: {:?}", @@ -213,7 +214,7 @@ impl HostEnv { let events_count = self.events_count(contract_address); (0..events_count) .map(|event_id| { - self.get_event_bytes(contract_address, event_id as i32) + self.get_event_bytes(contract_address, event_id) .unwrap_or_else(|e| { panic!( "Couldn't get event at address {:?} with id {}: {:?}", diff --git a/core/src/utils.rs b/core/src/utils.rs index d51cca12..9677ed43 100644 --- a/core/src/utils.rs +++ b/core/src/utils.rs @@ -28,18 +28,21 @@ pub(crate) fn extract_event_name(bytes: &[u8]) -> Result { /// assert_eq!(event_absolute_position(10, -1), Some(9)); /// assert_eq!(event_absolute_position(10, 10), None); /// ``` -pub fn event_absolute_position(len: usize, index: i32) -> Option { +pub fn event_absolute_position(len: u32, index: i32) -> Option { if index.is_negative() { - let abs_idx = index.wrapping_abs() as usize; - if abs_idx > len { + let abs_idx = index.wrapping_abs(); + if abs_idx > len as i32 { return None; } - Some(len - abs_idx) + Some( + len.checked_sub(abs_idx as u32) + .expect("Checked sub failed, it shouldn't happen") + ) } else { - if index as usize >= len { + if index >= len as i32 { return None; } - Some(index as usize) + Some(index as u32) } } diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index 66f033bb..9fb660b6 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -15,7 +15,7 @@ use odra_core::{ PublicKey, RuntimeArgs, SecretKey, TimeDiff, Timestamp, U512 }, consts::*, - Address, CLTyped, CallDef, ExecutionError, OdraError + Address, CLTyped, CallDef, ExecutionError, OdraError, OdraResult }; use crate::casper_node_port::query_balance::{ @@ -182,7 +182,7 @@ impl CasperClient { } /// Get the event bytes from storage - pub fn get_event(&self, contract_address: &Address, index: i32) -> Result { + pub fn get_event(&self, contract_address: &Address, index: u32) -> Result { let event_bytes: Bytes = self.query_dict(contract_address, "__events".to_string(), index.to_string())?; Ok(event_bytes) @@ -525,7 +525,7 @@ impl CasperClient { }) } - fn query_uref(&self, uref: URef) -> Result { + fn query_uref(&self, uref: URef) -> OdraResult { let result = self.query_global_state(&CasperKey::URef(uref)); match result.stored_value { CLValue(value) => { diff --git a/odra-casper/livenet-env/Cargo.toml b/odra-casper/livenet-env/Cargo.toml index 418edeeb..9269752d 100644 --- a/odra-casper/livenet-env/Cargo.toml +++ b/odra-casper/livenet-env/Cargo.toml @@ -8,4 +8,5 @@ edition = "2021" [dependencies] odra-core = { path = "../../core", default-features = false } odra-casper-client = { path = "../casper-client" } -blake2 = { workspace = true } \ No newline at end of file +blake2 = { workspace = true } +log = "0.4.20" \ No newline at end of file diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index cc1d62ed..fcf62f89 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -2,16 +2,17 @@ use std::sync::{Arc, RwLock}; use std::thread::sleep; use odra_casper_client::casper_client::CasperClient; -use odra_core::{ - Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, - RuntimeArgs, U512 -}; +use odra_casper_client::log::info; use odra_core::callstack::{Callstack, CallstackElement, Entrypoint}; use odra_core::contract_container::ContractContainer; use odra_core::contract_register::ContractRegister; use odra_core::event::EventError; use odra_core::event::EventError::CouldntExtractEventData; use odra_core::prelude::*; +use odra_core::{ + Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, + RuntimeArgs, U512 +}; use crate::livenet_contract_env::LivenetContractEnv; @@ -68,13 +69,14 @@ impl HostContext for LivenetEnv { } fn advance_block_time(&self, time_diff: u64) { - // Todo: implement logging, especially for such cases: - // info!("advance_block_time called - Waiting for {} ms", time_diff); + info(format!( + "advance_block_time called - Waiting for {} ms", + time_diff + )); sleep(std::time::Duration::from_millis(time_diff)); } - fn get_event(&self, contract_address: &Address, index: i32) -> Result { - // TODO: handle indices < 0 + fn get_event(&self, contract_address: &Address, index: u32) -> Result { self.casper_client .borrow() .get_event(contract_address, index) diff --git a/odra-casper/test-vm/src/casper_host.rs b/odra-casper/test-vm/src/casper_host.rs index 16c20fdb..ceaadbc9 100644 --- a/odra-casper/test-vm/src/casper_host.rs +++ b/odra-casper/test-vm/src/casper_host.rs @@ -53,7 +53,7 @@ impl HostContext for CasperHost { self.vm.borrow_mut().advance_block_time(time_diff) } - fn get_event(&self, contract_address: &Address, index: i32) -> Result { + fn get_event(&self, contract_address: &Address, index: u32) -> Result { self.vm.borrow().get_event(contract_address, index) } diff --git a/odra-casper/test-vm/src/vm/casper_vm.rs b/odra-casper/test-vm/src/vm/casper_vm.rs index 75e981b5..1ea77c04 100644 --- a/odra-casper/test-vm/src/vm/casper_vm.rs +++ b/odra-casper/test-vm/src/vm/casper_vm.rs @@ -147,7 +147,7 @@ impl CasperVm { self.block_time += time_diff } - pub fn get_event(&self, contract_address: &Address, index: i32) -> Result { + 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); @@ -161,17 +161,10 @@ impl CasperVm { .as_uref() .unwrap(); - let events_length = self.events_length(&contract_hash); - - let event_position = - odra_core::utils::event_absolute_position(events_length as usize, index) - .ok_or(EventError::IndexOutOfBounds)?; - - match self.context.query_dictionary_item( - None, - dictionary_seed_uref, - &event_position.to_string() - ) { + match self + .context + .query_dictionary_item(None, dictionary_seed_uref, &index.to_string()) + { Ok(val) => { let bytes = val .as_cl_value() diff --git a/odra-macros/src/ast/deployer_item.rs b/odra-macros/src/ast/deployer_item.rs index 8289fd7c..18555537 100644 --- a/odra-macros/src/ast/deployer_item.rs +++ b/odra-macros/src/ast/deployer_item.rs @@ -3,10 +3,8 @@ use derive_try_from_ref::TryFromRef; use crate::{ir::ModuleImplIR, utils}; use super::deployer_utils::{ - CallEpcExpr, DeployerLoadSignature, EpcSignature, - LoadContractExpr, - DeployerInitSignature, EntrypointCallerExpr, EntrypointsInitExpr, HostRefInstanceExpr, - NewContractExpr + CallEpcExpr, DeployerInitSignature, DeployerLoadSignature, EntrypointCallerExpr, + EntrypointsInitExpr, EpcSignature, HostRefInstanceExpr, LoadContractExpr, NewContractExpr }; #[derive(syn_derive::ToTokens)] diff --git a/odra-vm/src/odra_vm_host.rs b/odra-vm/src/odra_vm_host.rs index 4b0b5b52..20f02de9 100644 --- a/odra-vm/src/odra_vm_host.rs +++ b/odra-vm/src/odra_vm_host.rs @@ -36,7 +36,7 @@ impl HostContext for OdraVmHost { self.vm.borrow().advance_block_time_by(time_diff) } - fn get_event(&self, contract_address: &Address, index: i32) -> Result { + fn get_event(&self, contract_address: &Address, index: u32) -> Result { self.vm.borrow().get_event(contract_address, index) } diff --git a/odra-vm/src/vm/odra_vm.rs b/odra-vm/src/vm/odra_vm.rs index 59b06b51..123b7ef5 100644 --- a/odra-vm/src/vm/odra_vm.rs +++ b/odra-vm/src/vm/odra_vm.rs @@ -205,7 +205,7 @@ impl OdraVm { self.state.write().unwrap().emit_event(event_data); } - pub fn get_event(&self, address: &Address, index: i32) -> Result { + pub fn get_event(&self, address: &Address, index: u32) -> Result { self.state.read().unwrap().get_event(address, index) } diff --git a/odra-vm/src/vm/odra_vm_state.rs b/odra-vm/src/vm/odra_vm_state.rs index dca2bebd..95fcde0d 100644 --- a/odra-vm/src/vm/odra_vm_state.rs +++ b/odra-vm/src/vm/odra_vm_state.rs @@ -80,16 +80,14 @@ impl OdraVmState { } } - pub fn get_event(&self, address: &Address, index: i32) -> Result { + pub fn get_event(&self, address: &Address, index: u32) -> Result { let events = self.events.get(address); if events.is_none() { return Err(EventError::IndexOutOfBounds); } let events: &Vec = events.unwrap(); - let event_position = odra_core::utils::event_absolute_position(events.len(), index) - .ok_or(EventError::IndexOutOfBounds)?; let event = events - .get(event_position) + .get(index as usize) .ok_or(EventError::IndexOutOfBounds)?; Ok(event.clone()) } From d9c2511a9863b3b016b4edb14e41c9b841b41c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 24 Jan 2024 11:31:29 +0100 Subject: [PATCH 18/24] Removed _name from ContractContainer. Removed `pub` from CallDef fields. --- core/src/call_def.rs | 16 ++++++++++++---- core/src/callstack.rs | 4 ++-- core/src/contract_container.rs | 6 +----- odra-casper/casper-client/src/casper_client.rs | 16 ++++++++-------- .../livenet-env/src/livenet_contract_env.rs | 4 ++-- odra-casper/livenet-env/src/livenet_host_env.rs | 8 ++++---- odra-casper/test-vm/src/vm/casper_vm.rs | 16 ++++++++-------- odra-casper/wasm-env/src/host_functions.rs | 4 ++-- odra-vm/src/vm/odra_vm.rs | 11 +++++------ 9 files changed, 44 insertions(+), 41 deletions(-) diff --git a/core/src/call_def.rs b/core/src/call_def.rs index 32ddc389..6da2afea 100644 --- a/core/src/call_def.rs +++ b/core/src/call_def.rs @@ -4,9 +4,9 @@ use casper_types::{CLTyped, RuntimeArgs, U512}; #[derive(Clone, Debug)] pub struct CallDef { - pub entry_point: String, - pub args: RuntimeArgs, - pub amount: U512, + entry_point: String, + args: RuntimeArgs, + amount: U512, is_mut: bool } @@ -20,11 +20,19 @@ impl CallDef { } } - pub fn with_amount(mut self, amount: U512) -> Self { + pub fn with_amount(mut self, amount: U512) -> CallDef { self.amount = amount; self } + pub fn entry_point(&self) -> &str { + &self.entry_point + } + + pub fn amount(&self) -> U512 { + self.amount + } + pub fn args(&self) -> &RuntimeArgs { &self.args } diff --git a/core/src/callstack.rs b/core/src/callstack.rs index ef64d5a3..e7f0868a 100644 --- a/core/src/callstack.rs +++ b/core/src/callstack.rs @@ -51,13 +51,13 @@ impl Callstack { let ce = self.0.last().unwrap(); match ce { CallstackElement::Account(_) => U512::zero(), - CallstackElement::Entrypoint(e) => e.call_def.amount + CallstackElement::Entrypoint(e) => e.call_def.amount() } } pub fn attach_value(&mut self, amount: U512) { if let Some(CallstackElement::Entrypoint(entrypoint)) = self.0.last_mut() { - entrypoint.call_def.amount = amount; + entrypoint.call_def = entrypoint.call_def.clone().with_amount(amount); } } diff --git a/core/src/contract_container.rs b/core/src/contract_container.rs index a675e2f8..7b333510 100644 --- a/core/src/contract_container.rs +++ b/core/src/contract_container.rs @@ -10,14 +10,12 @@ pub type EntrypointArgs = Vec; #[derive(Clone)] pub struct ContractContainer { - _name: String, entry_points_caller: EntryPointsCaller } impl ContractContainer { - pub fn new(name: &str, entry_points_caller: EntryPointsCaller) -> Self { + pub fn new(entry_points_caller: EntryPointsCaller) -> Self { Self { - _name: String::from(name), entry_points_caller } } @@ -175,7 +173,6 @@ mod tests { ))) }); Self { - _name: String::from("contract"), entry_points_caller } } @@ -203,7 +200,6 @@ mod tests { ); Self { - _name: String::from("contract"), entry_points_caller } } diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index 9fb660b6..01da95de 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -371,15 +371,15 @@ impl CasperClient { log::info(format!( "Calling {:?} with entrypoint \"{}\" through proxy.", addr.to_string(), - call_def.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()), - ATTACHED_VALUE_ARG => call_def.amount, - AMOUNT_ARG => call_def.amount, + ENTRY_POINT_ARG => call_def.entry_point(), + ARGS_ARG => Bytes::from(call_def.args().to_bytes().unwrap()), + ATTACHED_VALUE_ARG => call_def.amount(), + AMOUNT_ARG => call_def.amount(), }; let session = ExecutableDeployItem::ModuleBytes { @@ -424,13 +424,13 @@ impl CasperClient { log::info(format!( "Calling {:?} with entrypoint \"{}\".", addr.to_string(), - call_def.entry_point + call_def.entry_point() )); let session = ExecutableDeployItem::StoredVersionedContractByHash { hash: *addr.as_contract_package_hash().unwrap(), version: None, - entry_point: call_def.entry_point, - args: call_def.args + entry_point: call_def.entry_point().to_string(), + args: call_def.args().clone() }; let deploy = self.new_deploy(session, self.gas); let request = json!( diff --git a/odra-casper/livenet-env/src/livenet_contract_env.rs b/odra-casper/livenet-env/src/livenet_contract_env.rs index 3be95cba..b8bd0dc0 100644 --- a/odra-casper/livenet-env/src/livenet_contract_env.rs +++ b/odra-casper/livenet-env/src/livenet_contract_env.rs @@ -73,7 +73,7 @@ impl ContractContext for LivenetContractEnv { fn revert(&self, error: OdraError) -> ! { let mut revert_msg = String::from(""); if let CallstackElement::Entrypoint(ep) = self.callstack.borrow().current() { - revert_msg = format!("{:?}::{}", ep.address, ep.call_def.entry_point); + revert_msg = format!("{:?}::{}", ep.address, ep.call_def.entry_point()); } panic!("Revert: {:?} - {}", error, revert_msg); @@ -83,7 +83,7 @@ impl ContractContext for LivenetContractEnv { match self.callstack.borrow().current() { CallstackElement::Account(_) => todo!("get_named_arg_bytes"), CallstackElement::Entrypoint(ep) => { - Bytes::from(ep.call_def.args.get(name).unwrap().inner_bytes().to_vec()) + Bytes::from(ep.call_def.args().get(name).unwrap().inner_bytes().to_vec()) } } } diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host_env.rs index fcf62f89..aabd82b0 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host_env.rs @@ -123,10 +123,10 @@ impl HostContext for LivenetEnv { } fn register_contract(&self, address: Address, entry_points_caller: EntryPointsCaller) { - self.contract_register.write().unwrap().add( - address, - ContractContainer::new(&address.to_string(), entry_points_caller) - ); + self.contract_register + .write() + .unwrap() + .add(address, ContractContainer::new(entry_points_caller)); } fn new_contract( diff --git a/odra-casper/test-vm/src/vm/casper_vm.rs b/odra-casper/test-vm/src/vm/casper_vm.rs index 1ea77c04..5304861f 100644 --- a/odra-casper/test-vm/src/vm/casper_vm.rs +++ b/odra-casper/test-vm/src/vm/casper_vm.rs @@ -202,14 +202,14 @@ impl CasperVm { let deploy_item = if use_proxy { let session_code = include_bytes!("../../resources/proxy_caller_with_return.wasm").to_vec(); - let args_bytes: Vec = call_def.args.to_bytes().unwrap(); - let entry_point = call_def.entry_point.clone(); + let args_bytes: Vec = call_def.args().to_bytes().unwrap(); + let entry_point = call_def.entry_point(); let args = runtime_args! { CONTRACT_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, + ATTACHED_VALUE_ARG => call_def.amount(), + AMOUNT_ARG => call_def.amount(), }; DeployItemBuilder::new() @@ -227,8 +227,8 @@ impl CasperVm { .with_stored_versioned_contract_by_hash( hash.value(), None, - &call_def.entry_point, - call_def.args.clone() + &call_def.entry_point(), + call_def.args().clone() ) .with_deploy_hash(self.next_hash()) .build() @@ -240,7 +240,7 @@ impl CasperVm { self.context.exec(execute_request).commit(); self.collect_gas(); self.gas_cost.push(( - format!("call_entrypoint {}", call_def.entry_point), + format!("call_entrypoint {}", call_def.entry_point()), self.last_call_contract_gas_cost() )); @@ -248,7 +248,7 @@ impl CasperVm { if let Some(error) = self.context.get_error() { let odra_error = parse_error(error); self.error = Some(odra_error.clone()); - self.panic_with_error(odra_error, &call_def.entry_point, hash); + self.panic_with_error(odra_error, call_def.entry_point(), hash); } else { self.get_active_account_result() } diff --git a/odra-casper/wasm-env/src/host_functions.rs b/odra-casper/wasm-env/src/host_functions.rs index 651176c3..5b20535f 100644 --- a/odra-casper/wasm-env/src/host_functions.rs +++ b/odra-casper/wasm-env/src/host_functions.rs @@ -289,13 +289,13 @@ pub fn call_contract(address: Address, call_def: CallDef) -> Bytes { let contract_package_hash = *address.as_contract_package_hash().unwrap_or_revert(); let method = call_def.method(); let mut args = call_def.args().to_owned(); - if call_def.amount == U512::zero() { + if call_def.amount() == U512::zero() { call_versioned_contract(contract_package_hash, None, method, args) } else { let cargo_purse = get_or_create_cargo_purse(); let main_purse = get_main_purse().unwrap_or_revert(); - transfer_from_purse_to_purse(main_purse, cargo_purse, call_def.amount, None) + transfer_from_purse_to_purse(main_purse, cargo_purse, call_def.amount(), None) .unwrap_or_revert_with(ApiError::Transfer); args.insert(consts::CARGO_PURSE_ARG, cargo_purse) .unwrap_or_revert(); diff --git a/odra-vm/src/vm/odra_vm.rs b/odra-vm/src/vm/odra_vm.rs index 123b7ef5..c5133cae 100644 --- a/odra-vm/src/vm/odra_vm.rs +++ b/odra-vm/src/vm/odra_vm.rs @@ -39,8 +39,7 @@ impl OdraVm { let address = { self.state.write().unwrap().next_contract_address() }; // Register new contract under the new address. { - let contract_namespace = self.state.read().unwrap().get_contract_namespace(); - let contract = ContractContainer::new(&contract_namespace, entry_points_caller); + let contract = ContractContainer::new(entry_points_caller); self.contract_register .write() .unwrap() @@ -57,8 +56,8 @@ impl OdraVm { pub fn call_contract(&self, address: Address, call_def: CallDef) -> Bytes { self.prepare_call(address, &call_def); // Call contract from register. - if call_def.amount > U512::zero() { - let status = self.checked_transfer_tokens(&self.caller(), &address, &call_def.amount); + if call_def.amount() > U512::zero() { + let status = self.checked_transfer_tokens(&self.caller(), &address, &call_def.amount()); if let Err(err) = status { self.revert(err); } @@ -117,7 +116,7 @@ impl OdraVm { pub fn revert(&self, error: OdraError) -> ! { let mut revert_msg = String::from(""); if let CallstackElement::Entrypoint(ep) = self.callstack_tip() { - revert_msg = format!("{:?}::{}", ep.address, ep.call_def.entry_point); + revert_msg = format!("{:?}::{}", ep.address, ep.call_def.entry_point()); } let mut state = self.state.write().unwrap(); @@ -156,7 +155,7 @@ impl OdraVm { match self.state.read().unwrap().callstack_tip() { CallstackElement::Account(_) => todo!(), CallstackElement::Entrypoint(ep) => { - ep.call_def.args.get(name).unwrap().inner_bytes().to_vec() + ep.call_def.args().get(name).unwrap().inner_bytes().to_vec() } } } From 8479f1ad0fd566d623468a146094119ee67261cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 24 Jan 2024 11:38:25 +0100 Subject: [PATCH 19/24] Loading secret keys in a separate method --- odra-casper/casper-client/Cargo.toml | 2 -- .../casper-client/src/casper_client.rs | 22 +++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/odra-casper/casper-client/Cargo.toml b/odra-casper/casper-client/Cargo.toml index 92481c20..25236d79 100644 --- a/odra-casper/casper-client/Cargo.toml +++ b/odra-casper/casper-client/Cargo.toml @@ -3,8 +3,6 @@ name = "odra-casper-client" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] odra-core = { path = "../../core" } casper-execution-engine = { workspace = true } diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index 01da95de..8b22b0e8 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -70,13 +70,7 @@ impl CasperClient { // Load .env dotenv::dotenv().ok(); - let mut secret_keys = vec![]; - secret_keys.push(SecretKey::from_file(get_env_variable(ENV_SECRET_KEY)).unwrap()); - let mut i = 0; - while let Ok(key_filename) = std::env::var(format!("{}{}", ENV_ACCOUNT_PREFIX, i + 1)) { - secret_keys.push(SecretKey::from_file(key_filename).unwrap()); - i += 1; - } + let secret_keys = Self::load_secret_keys(); CasperClient { node_address: get_env_variable(ENV_NODE_ADDRESS), @@ -87,6 +81,20 @@ impl CasperClient { } } + /// Loads secret keys from ENV_SECRET_KEY file and ENV_ACCOUNT_PREFIX files. + /// e.g. ENV_SECRET_KEY=secret_key.pem, ENV_ACCOUNT_PREFIX=account_1_key.pem + /// This will load secret_key.pem as an account 0 and account_1_key.pem as account 1. + fn load_secret_keys() -> Vec { + let mut secret_keys = vec![]; + secret_keys.push(SecretKey::from_file(get_env_variable(ENV_SECRET_KEY)).unwrap()); + let mut i = 1; + while let Ok(key_filename) = std::env::var(format!("{}{}", ENV_ACCOUNT_PREFIX, i)) { + secret_keys.push(SecretKey::from_file(key_filename).unwrap()); + i+= 1; + } + secret_keys + } + /// Gets a value from the storage pub fn get_value(&self, address: &Address, key: &[u8]) -> Option { self.query_state_dictionary(address, unsafe { from_utf8_unchecked(key) }) From f905c9d200e77d1e2496689e2ce0bbd34cfb9c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 24 Jan 2024 11:52:15 +0100 Subject: [PATCH 20/24] Renames for pr fixes --- examples/bin/erc20_on_livenet.rs | 2 +- examples/bin/livenet_tests.rs | 2 +- odra-casper/casper-client/src/casper_client.rs | 2 +- odra-casper/livenet-env/src/lib.rs | 8 ++++---- .../src/{livenet_host_env.rs => livenet_host.rs} | 13 +++++-------- odra-test/src/lib.rs | 2 +- 6 files changed, 13 insertions(+), 16 deletions(-) rename odra-casper/livenet-env/src/{livenet_host_env.rs => livenet_host.rs} (95%) diff --git a/examples/bin/erc20_on_livenet.rs b/examples/bin/erc20_on_livenet.rs index 52becb5d..a7b07dee 100644 --- a/examples/bin/erc20_on_livenet.rs +++ b/examples/bin/erc20_on_livenet.rs @@ -3,7 +3,7 @@ use odra_modules::erc20::{Erc20Deployer, Erc20HostRef}; use std::str::FromStr; fn main() { - let env = odra_casper_livenet_env::livenet_env(); + let env = odra_casper_livenet_env::env(); let owner = env.caller(); let recipient = "hash-2c4a6ce0da5d175e9638ec0830e01dd6cf5f4b1fbb0724f7d2d9de12b1e0f840"; diff --git a/examples/bin/livenet_tests.rs b/examples/bin/livenet_tests.rs index 08be691c..024c6e0a 100644 --- a/examples/bin/livenet_tests.rs +++ b/examples/bin/livenet_tests.rs @@ -5,7 +5,7 @@ use odra_modules::erc20::Erc20Deployer; use std::str::FromStr; fn main() { - let env = odra_casper_livenet_env::livenet_env(); + let env = odra_casper_livenet_env::env(); let owner = env.caller(); diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index 8b22b0e8..12fd0357 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -90,7 +90,7 @@ impl CasperClient { let mut i = 1; while let Ok(key_filename) = std::env::var(format!("{}{}", ENV_ACCOUNT_PREFIX, i)) { secret_keys.push(SecretKey::from_file(key_filename).unwrap()); - i+= 1; + i += 1; } secret_keys } diff --git a/odra-casper/livenet-env/src/lib.rs b/odra-casper/livenet-env/src/lib.rs index ace86896..30fade88 100644 --- a/odra-casper/livenet-env/src/lib.rs +++ b/odra-casper/livenet-env/src/lib.rs @@ -1,10 +1,10 @@ #![feature(once_cell)] pub mod livenet_contract_env; -pub mod livenet_host_env; -use livenet_host_env::LivenetEnv; +pub mod livenet_host; +use livenet_host::LivenetHost; use odra_core::HostEnv; -pub fn livenet_env() -> HostEnv { - let env = LivenetEnv::new(); +pub fn env() -> HostEnv { + let env = LivenetHost::new(); HostEnv::new(env) } diff --git a/odra-casper/livenet-env/src/livenet_host_env.rs b/odra-casper/livenet-env/src/livenet_host.rs similarity index 95% rename from odra-casper/livenet-env/src/livenet_host_env.rs rename to odra-casper/livenet-env/src/livenet_host.rs index aabd82b0..88b18286 100644 --- a/odra-casper/livenet-env/src/livenet_host_env.rs +++ b/odra-casper/livenet-env/src/livenet_host.rs @@ -9,21 +9,18 @@ use odra_core::contract_register::ContractRegister; use odra_core::event::EventError; use odra_core::event::EventError::CouldntExtractEventData; use odra_core::prelude::*; -use odra_core::{ - Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, - RuntimeArgs, U512 -}; +use odra_core::{Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, RuntimeArgs, ToBytes, U512}; use crate::livenet_contract_env::LivenetContractEnv; -pub struct LivenetEnv { +pub struct LivenetHost { casper_client: Rc>, contract_register: Arc>, contract_env: Rc, callstack: Rc> } -impl LivenetEnv { +impl LivenetHost { pub fn new() -> Rc> { Rc::new(RefCell::new(Self::new_instance())) } @@ -47,7 +44,7 @@ impl LivenetEnv { } } -impl HostContext for LivenetEnv { +impl HostContext for LivenetHost { fn set_caller(&self, caller: Address) { self.casper_client.borrow_mut().set_caller(caller); } @@ -117,7 +114,7 @@ impl HostContext for LivenetEnv { self.casper_client .borrow_mut() .deploy_entrypoint_call(*address, call_def); - Ok(Default::default()) + Ok(().to_bytes().unwrap().into()) } } } diff --git a/odra-test/src/lib.rs b/odra-test/src/lib.rs index 7ab30a7b..d1b5669a 100644 --- a/odra-test/src/lib.rs +++ b/odra-test/src/lib.rs @@ -13,7 +13,7 @@ pub fn env() -> odra_core::HostEnv { let backend: String = std::env::var("ODRA_BACKEND").unwrap_or(String::from("odra-vm")); match backend.as_str() { "casper" => casper_env(), - "livenet" => odra_casper_livenet_env::livenet_env(), + "livenet" => odra_casper_livenet_env::env(), _ => odra_env() } } From da1de7ffb6a71051730737cba5b8278ef8cff840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 24 Jan 2024 13:28:08 +0100 Subject: [PATCH 21/24] Moved init_args to host_env --- core/src/host_context.rs | 2 +- core/src/host_env.rs | 18 ++++++++++++++++-- odra-casper/livenet-env/src/livenet_host.rs | 21 ++++++--------------- odra-casper/test-vm/src/casper_host.rs | 2 +- odra-casper/test-vm/src/vm/casper_vm.rs | 14 +++++++------- odra-vm/src/odra_vm_host.rs | 10 +++++++--- 6 files changed, 38 insertions(+), 29 deletions(-) diff --git a/core/src/host_context.rs b/core/src/host_context.rs index b2362023..3b37456c 100644 --- a/core/src/host_context.rs +++ b/core/src/host_context.rs @@ -25,7 +25,7 @@ pub trait HostContext { fn new_contract( &self, name: &str, - init_args: Option, + init_args: RuntimeArgs, entry_points_caller: EntryPointsCaller ) -> Address; fn register_contract(&self, address: Address, entry_points_caller: EntryPointsCaller); diff --git a/core/src/host_env.rs b/core/src/host_env.rs index 745dbad7..1582b670 100644 --- a/core/src/host_env.rs +++ b/core/src/host_env.rs @@ -1,4 +1,5 @@ use crate::call_result::CallResult; +use crate::consts::{ALLOW_KEY_OVERRIDE_ARG, IS_UPGRADABLE_ARG, PACKAGE_HASH_KEY_NAME_ARG}; use crate::entry_point_callback::EntryPointsCaller; use crate::event::EventError; use crate::host_context::HostContext; @@ -51,7 +52,18 @@ impl HostEnv { entry_points_caller: EntryPointsCaller ) -> Address { let backend = self.backend.borrow(); - let deployed_contract = backend.new_contract(name, init_args, entry_points_caller); + + 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 @@ -61,7 +73,9 @@ impl HostEnv { 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, 0); + self.events_count + .borrow_mut() + .insert(address, self.events_count(&address)); } pub fn call_contract( diff --git a/odra-casper/livenet-env/src/livenet_host.rs b/odra-casper/livenet-env/src/livenet_host.rs index 88b18286..99b2d52c 100644 --- a/odra-casper/livenet-env/src/livenet_host.rs +++ b/odra-casper/livenet-env/src/livenet_host.rs @@ -9,7 +9,10 @@ use odra_core::contract_register::ContractRegister; use odra_core::event::EventError; use odra_core::event::EventError::CouldntExtractEventData; use odra_core::prelude::*; -use odra_core::{Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, RuntimeArgs, ToBytes, U512}; +use odra_core::{ + Address, Bytes, CallDef, ContractEnv, EntryPointsCaller, HostContext, OdraError, PublicKey, + RuntimeArgs, ToBytes, U512 +}; use crate::livenet_contract_env::LivenetContractEnv; @@ -129,22 +132,10 @@ impl HostContext for LivenetHost { fn new_contract( &self, name: &str, - init_args: Option, + init_args: RuntimeArgs, entry_points_caller: EntryPointsCaller ) -> Address { - let mut args = match init_args { - None => RuntimeArgs::new(), - Some(args) => args - }; - // todo: move this up the stack - args.insert("odra_cfg_is_upgradable", false).unwrap(); - args.insert("odra_cfg_allow_key_override", true).unwrap(); - args.insert( - "odra_cfg_package_hash_key_name", - format!("{}_package_hash", name) - ) - .unwrap(); - let address = self.casper_client.borrow_mut().deploy_wasm(name, args); + let address = self.casper_client.borrow_mut().deploy_wasm(name, init_args); self.register_contract(address, entry_points_caller); address } diff --git a/odra-casper/test-vm/src/casper_host.rs b/odra-casper/test-vm/src/casper_host.rs index ceaadbc9..624ef1d7 100644 --- a/odra-casper/test-vm/src/casper_host.rs +++ b/odra-casper/test-vm/src/casper_host.rs @@ -89,7 +89,7 @@ impl HostContext for CasperHost { fn new_contract( &self, name: &str, - init_args: Option, + init_args: RuntimeArgs, entry_points_caller: EntryPointsCaller ) -> Address { self.vm diff --git a/odra-casper/test-vm/src/vm/casper_vm.rs b/odra-casper/test-vm/src/vm/casper_vm.rs index 5304861f..4eca93a0 100644 --- a/odra-casper/test-vm/src/vm/casper_vm.rs +++ b/odra-casper/test-vm/src/vm/casper_vm.rs @@ -257,18 +257,18 @@ impl CasperVm { pub fn new_contract( &mut self, name: &str, - init_args: Option, + init_args: RuntimeArgs, entry_points_caller: EntryPointsCaller ) -> Address { let wasm_path = format!("{}.wasm", name); - let package_hash_key_name = format!("{}_package_hash", name); - let mut args = init_args.unwrap_or(runtime_args! {}); - args.insert(PACKAGE_HASH_KEY_NAME_ARG, package_hash_key_name.clone()) + let package_hash_key_name: String = init_args + .get(PACKAGE_HASH_KEY_NAME_ARG) + .unwrap() + .clone() + .into_t() .unwrap(); - args.insert(ALLOW_KEY_OVERRIDE_ARG, true).unwrap(); - args.insert(IS_UPGRADABLE_ARG, false).unwrap(); - self.deploy_contract(&wasm_path, &args); + self.deploy_contract(&wasm_path, &init_args); let contract_package_hash = self.contract_package_hash_from_name(&package_hash_key_name); contract_package_hash.try_into().unwrap() } diff --git a/odra-vm/src/odra_vm_host.rs b/odra-vm/src/odra_vm_host.rs index 20f02de9..641d853a 100644 --- a/odra-vm/src/odra_vm_host.rs +++ b/odra-vm/src/odra_vm_host.rs @@ -67,16 +67,20 @@ impl HostContext for OdraVmHost { fn new_contract( &self, name: &str, - init_args: Option, + init_args: RuntimeArgs, entry_points_caller: EntryPointsCaller ) -> Address { // TODO: panic in nice way let address = self .vm .borrow() - .register_contract(name, entry_points_caller); + .register_contract(name, entry_points_caller.clone()); - if let Some(init_args) = init_args { + if entry_points_caller + .entry_points() + .iter() + .any(|ep| ep.name == "init") + { let _ = self.call_contract( &address, CallDef::new(String::from("init"), true, init_args), From e1e52d6fe9aa5d8877113b9bc2784abd6669762e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 24 Jan 2024 13:47:54 +0100 Subject: [PATCH 22/24] Message signing --- examples/bin/livenet_tests.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/bin/livenet_tests.rs b/examples/bin/livenet_tests.rs index 024c6e0a..c929bda8 100644 --- a/examples/bin/livenet_tests.rs +++ b/examples/bin/livenet_tests.rs @@ -18,7 +18,9 @@ fn main() { // let mut contract = load(&env, *contract.address()); // Uncomment to load existing contract - let address = Address::from_str("hash-bd80e12b6d189e3b492932fc08e1342b540555c60e299ea3563d81ad700997e0").unwrap(); + let address = + Address::from_str("hash-bd80e12b6d189e3b492932fc08e1342b540555c60e299ea3563d81ad700997e0") + .unwrap(); let mut contract = load(&env, address); // Set gas will be used for all subsequent calls @@ -46,9 +48,10 @@ fn main() { assert_eq!(contract.immutable_cross_call(), 10_000.into()); // - mutable crosscalls will require a deploy - let pre_call_balance = Erc20Deployer::load(&env, erc20_address()).balance_of(env.caller()); + let erc20 = Erc20Deployer::load(&env, erc20_address()); + let pre_call_balance = erc20.balance_of(env.caller()); contract.mutable_cross_call(); - let post_call_balance = Erc20Deployer::load(&env, erc20_address()).balance_of(env.caller()); + let post_call_balance = erc20.balance_of(env.caller()); assert_eq!(post_call_balance, pre_call_balance + 1); // We can change the caller From 2ed06d098f86df0d8ad51f79b94a4f265a60dc4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 24 Jan 2024 15:11:31 +0100 Subject: [PATCH 23/24] Fixed some unwraps in livenet --- core/src/error.rs | 1 + examples/bin/livenet_tests.rs | 3 +- .../casper-client/src/casper_client.rs | 82 +++++++++++++++---- odra-casper/livenet-env/src/livenet_host.rs | 23 ++++-- odra-casper/test-vm/src/vm/casper_vm.rs | 2 +- 5 files changed, 83 insertions(+), 28 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index 17d5ea30..2f0994a0 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -98,6 +98,7 @@ pub enum ExecutionError { KeyNotFound = 117, CouldNotDeserializeSignature = 118, TypeMismatch = 119, + CouldnNotSignMessage = 120, MaxUserError = 32767, /// User error too high. The code should be in range 0..32767. UserErrorTooHigh = 32768, diff --git a/examples/bin/livenet_tests.rs b/examples/bin/livenet_tests.rs index c929bda8..2b2990b3 100644 --- a/examples/bin/livenet_tests.rs +++ b/examples/bin/livenet_tests.rs @@ -44,7 +44,7 @@ fn main() { let event: OwnershipTransferred = env.get_event(contract.address(), 0).unwrap(); assert_eq!(event.new_owner, Some(owner)); - // - we can test immutable crosscalls without deploying + // - we can test immutable crosscalls without deploying (but crosscall contracts needs to be registered) assert_eq!(contract.immutable_cross_call(), 10_000.into()); // - mutable crosscalls will require a deploy @@ -75,5 +75,6 @@ fn erc20_address() -> Address { } fn load(env: &HostEnv, address: Address) -> LivenetContractHostRef { + Erc20Deployer::load(env, erc20_address()); LivenetContractDeployer::load(env, address) } diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index 12fd0357..8a899a84 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -7,7 +7,7 @@ use jsonrpc_lite::JsonRpc; use serde::de::DeserializeOwned; use serde_json::{json, Value}; -use odra_core::casper_types::URef; +use odra_core::casper_types::{sign, URef}; use odra_core::{ casper_types::{ bytesrepr::{Bytes, FromBytes, ToBytes}, @@ -86,10 +86,20 @@ impl CasperClient { /// This will load secret_key.pem as an account 0 and account_1_key.pem as account 1. fn load_secret_keys() -> Vec { let mut secret_keys = vec![]; - secret_keys.push(SecretKey::from_file(get_env_variable(ENV_SECRET_KEY)).unwrap()); + secret_keys.push( + SecretKey::from_file(get_env_variable(ENV_SECRET_KEY)).unwrap_or_else(|_| { + panic!( + "Couldn't load secret key from file {:?}", + get_env_variable(ENV_SECRET_KEY) + ) + }) + ); + let mut i = 1; while let Ok(key_filename) = std::env::var(format!("{}{}", ENV_ACCOUNT_PREFIX, i)) { - secret_keys.push(SecretKey::from_file(key_filename).unwrap()); + secret_keys.push(SecretKey::from_file(&key_filename).unwrap_or_else(|_| { + panic!("Couldn't load secret key from file {:?}", key_filename) + })); i += 1; } secret_keys @@ -120,11 +130,27 @@ impl CasperClient { PublicKey::from(&self.secret_keys[self.active_account]) } + /// Public key of the account address. + pub fn address_public_key(&self, address: &Address) -> PublicKey { + PublicKey::from(self.address_secret_key(address)) + } + /// Secret key of the client account. pub fn secret_key(&self) -> &SecretKey { &self.secret_keys[self.active_account] } + /// Signs the message using keys associated with an address. + pub fn sign_message(&self, message: &Bytes, address: &Address) -> OdraResult { + 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::CouldnNotSignMessage))?; + + Ok(Bytes::from(signature)) + } + /// Address of the client account. pub fn caller(&self) -> Address { Address::from(self.public_key()) @@ -158,7 +184,11 @@ impl CasperClient { Some(GlobalStateIdentifier::StateRootHash( self.get_state_root_hash() )), - PurseIdentifier::MainPurseUnderAccountHash(*address.as_account_hash().unwrap()) + PurseIdentifier::MainPurseUnderAccountHash( + *address + .as_account_hash() + .unwrap_or_else(|| panic!("Address {:?} is not an account address", address)) + ) ); let request = json!( { @@ -168,7 +198,7 @@ impl CasperClient { "id": 1, } ); - let result: QueryBalanceResult = self.post_request(request).unwrap(); + let result: QueryBalanceResult = self.post_request(request); result.balance } @@ -181,11 +211,16 @@ impl CasperClient { "id": 1, } ); - let result: serde_json::Value = self.post_request(request).unwrap(); + let result: serde_json::Value = self.post_request(request); let result = result["result"]["last_added_block_info"]["timestamp"] .as_str() - .unwrap(); - let timestamp: u64 = result.parse().unwrap(); + .unwrap_or_else(|| { + panic!( + "Couldn't get block time - malformed JSON response: {:?}", + result + ) + }); + let timestamp: u64 = result.parse().expect("Couldn't parse block time"); timestamp } @@ -214,7 +249,7 @@ impl CasperClient { "id": 1, } ); - let result: GetStateRootHashResult = self.post_request(request).unwrap(); + let result: GetStateRootHashResult = self.post_request(request); result.state_root_hash.unwrap() } @@ -247,7 +282,7 @@ impl CasperClient { "id": 1, } ); - let result: GetDictionaryItemResult = self.post_request(request).unwrap(); + let result: GetDictionaryItemResult = self.post_request(request); match result.stored_value { CLValue(value) => { let return_value: T = value.into_t().unwrap(); @@ -297,7 +332,7 @@ impl CasperClient { "id": 1, } ); - self.post_request(request).unwrap() + self.post_request(request) } /// Discover the contract address by name. @@ -362,7 +397,7 @@ impl CasperClient { } ); - let response: PutDeployResult = self.post_request(request).unwrap(); + let response: PutDeployResult = self.post_request(request); let deploy_hash = response.deploy_hash; self.wait_for_deploy_hash(deploy_hash); @@ -408,7 +443,7 @@ impl CasperClient { "id": 1, } ); - let response: PutDeployResult = self.post_request(request).unwrap(); + let response: PutDeployResult = self.post_request(request); let deploy_hash = response.deploy_hash; self.wait_for_deploy_hash(deploy_hash); @@ -451,7 +486,7 @@ impl CasperClient { "id": 1, } ); - let response: PutDeployResult = self.post_request(request).unwrap(); + let response: PutDeployResult = self.post_request(request); let deploy_hash = response.deploy_hash; self.wait_for_deploy_hash(deploy_hash); } @@ -477,7 +512,7 @@ impl CasperClient { "id": 1, } ); - self.post_request(request).unwrap() + self.post_request(request) } fn query_global_state(&self, key: &CasperKey) -> QueryGlobalStateResult { @@ -495,7 +530,7 @@ impl CasperClient { "id": 1, } ); - self.post_request(request).unwrap() + self.post_request(request) } fn query_state_dictionary(&self, address: &Address, key: &str) -> Option { @@ -615,8 +650,7 @@ impl CasperClient { ) } - fn post_request(&self, request: Value) -> Option { - // TODO: change result to Result + fn post_request(&self, request: Value) -> T { let client = reqwest::blocking::Client::new(); let response = client .post(self.node_address_rpc()) @@ -627,6 +661,18 @@ impl CasperClient { response .get_result() .map(|result| serde_json::from_value(result.clone()).unwrap()) + .unwrap() + } + + fn address_secret_key(&self, address: &Address) -> &SecretKey { + match self + .secret_keys + .iter() + .find(|key| Address::from(PublicKey::from(*key)) == *address) + { + Some(secret_key) => secret_key, + None => panic!("Key for address {:?} is not loaded", address) + } } } diff --git a/odra-casper/livenet-env/src/livenet_host.rs b/odra-casper/livenet-env/src/livenet_host.rs index 99b2d52c..3c0c0171 100644 --- a/odra-casper/livenet-env/src/livenet_host.rs +++ b/odra-casper/livenet-env/src/livenet_host.rs @@ -28,7 +28,7 @@ impl LivenetHost { Rc::new(RefCell::new(Self::new_instance())) } - pub fn new_instance() -> Self { + fn new_instance() -> Self { let casper_client: Rc> = Default::default(); let callstack: Rc> = Default::default(); let contract_register = Arc::new(RwLock::new(Default::default())); @@ -103,7 +103,7 @@ impl HostContext for LivenetHost { let result = self .contract_register .read() - .unwrap() + .expect("Couldn't read contract register.") .call(address, call_def); self.callstack.borrow_mut().pop(); return result; @@ -117,7 +117,11 @@ impl HostContext for LivenetHost { self.casper_client .borrow_mut() .deploy_entrypoint_call(*address, call_def); - Ok(().to_bytes().unwrap().into()) + Ok( + ().to_bytes() + .expect("Couldn't serialize (). This shouldn't happen.") + .into() + ) } } } @@ -125,7 +129,7 @@ impl HostContext for LivenetHost { fn register_contract(&self, address: Address, entry_points_caller: EntryPointsCaller) { self.contract_register .write() - .unwrap() + .expect("Couldn't write contract register.") .add(address, ContractContainer::new(entry_points_caller)); } @@ -154,11 +158,14 @@ impl HostContext for LivenetHost { 0 } - fn sign_message(&self, _message: &Bytes, _address: &Address) -> Bytes { - todo!() + fn sign_message(&self, message: &Bytes, address: &Address) -> Bytes { + self.casper_client + .borrow() + .sign_message(message, address) + .unwrap() } - fn public_key(&self, _address: &Address) -> PublicKey { - todo!() + fn public_key(&self, address: &Address) -> PublicKey { + self.casper_client.borrow().address_public_key(address) } } diff --git a/odra-casper/test-vm/src/vm/casper_vm.rs b/odra-casper/test-vm/src/vm/casper_vm.rs index 4eca93a0..c651ae49 100644 --- a/odra-casper/test-vm/src/vm/casper_vm.rs +++ b/odra-casper/test-vm/src/vm/casper_vm.rs @@ -227,7 +227,7 @@ impl CasperVm { .with_stored_versioned_contract_by_hash( hash.value(), None, - &call_def.entry_point(), + call_def.entry_point(), call_def.args().clone() ) .with_deploy_hash(self.next_hash()) From c05cca296f7fe5916a260c21b877ccb7df187a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20P=C5=82askonka?= Date: Wed, 24 Jan 2024 15:20:41 +0100 Subject: [PATCH 24/24] Update core/src/error.rs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- core/src/error.rs | 2 +- odra-casper/casper-client/src/casper_client.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index 2f0994a0..08c58e8b 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -98,7 +98,7 @@ pub enum ExecutionError { KeyNotFound = 117, CouldNotDeserializeSignature = 118, TypeMismatch = 119, - CouldnNotSignMessage = 120, + CouldNotSignMessage = 120, MaxUserError = 32767, /// User error too high. The code should be in range 0..32767. UserErrorTooHigh = 32768, diff --git a/odra-casper/casper-client/src/casper_client.rs b/odra-casper/casper-client/src/casper_client.rs index 8a899a84..c95c12ab 100644 --- a/odra-casper/casper-client/src/casper_client.rs +++ b/odra-casper/casper-client/src/casper_client.rs @@ -146,7 +146,7 @@ impl CasperClient { let public_key = &PublicKey::from(secret_key); let signature = sign(message, secret_key, public_key) .to_bytes() - .map_err(|_| OdraError::ExecutionError(ExecutionError::CouldnNotSignMessage))?; + .map_err(|_| OdraError::ExecutionError(ExecutionError::CouldNotSignMessage))?; Ok(Bytes::from(signature)) }