diff --git a/flake.lock b/flake.lock index 1adc3b0..c75a223 100644 --- a/flake.lock +++ b/flake.lock @@ -12,11 +12,11 @@ ] }, "locked": { - "lastModified": 1670284777, - "narHash": "sha256-JF0pc0s4z/X+Iy+lNHOwUQ8I5bz+q7uX4HrKTNIEj24=", + "lastModified": 1693787605, + "narHash": "sha256-rwq5U8dy+a9JFny/73L0SJu1GfWwATMPMTp7D+mjHy8=", "owner": "ipetkov", "repo": "crane", - "rev": "2243fb9c872de25cb564a02d324ea6a5b9853052", + "rev": "8b4f7a4dab2120cf41e7957a28a853f45016bd9d", "type": "github" }, "original": { @@ -28,11 +28,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1668681692, - "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=", + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", "owner": "edolstra", "repo": "flake-compat", - "rev": "009399224d5e398d03b22badca40a37ac85412a1", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", "type": "github" }, "original": { @@ -42,12 +42,15 @@ } }, "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1667395993, - "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "lastModified": 1692799911, + "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", "owner": "numtide", "repo": "flake-utils", - "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", "type": "github" }, "original": { @@ -57,12 +60,15 @@ } }, "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, "locked": { - "lastModified": 1667395993, - "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "lastModified": 1692799911, + "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", "owner": "numtide", "repo": "flake-utils", - "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", "type": "github" }, "original": { @@ -72,12 +78,15 @@ } }, "flake-utils_3": { + "inputs": { + "systems": "systems_3" + }, "locked": { - "lastModified": 1659877975, - "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", "owner": "numtide", "repo": "flake-utils", - "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", "type": "github" }, "original": { @@ -88,15 +97,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1670408352, - "narHash": "sha256-2MEkNG6CPdJoKHa7TUvAx/EiDZnHYwWrVC4RTSRtxaE=", - "owner": "nixos", + "lastModified": 1693663421, + "narHash": "sha256-ImMIlWE/idjcZAfxKK8sQA7A1Gi/O58u5/CJA+mxvl8=", + "owner": "NixOS", "repo": "nixpkgs", - "rev": "9cc7f80dea4cde202c1ee514532b53953b1f3510", + "rev": "e56990880811a451abd32515698c712788be5720", "type": "github" }, "original": { - "owner": "nixos", + "owner": "NixOS", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } @@ -117,11 +127,11 @@ ] }, "locked": { - "lastModified": 1670380307, - "narHash": "sha256-7fJN5ndnE8YbrrtYdqMo3gDV/BW37M4wNBRhjdfP/XY=", + "lastModified": 1693966243, + "narHash": "sha256-a2CA1aMIPE67JWSVIGoGtD3EGlFdK9+OlJQs0FOWCKY=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "fc98242f5f49d39b8fd3a611c146741a35dc012d", + "rev": "a8b4bb4cbb744baaabc3e69099f352f99164e2c1", "type": "github" }, "original": { @@ -129,6 +139,51 @@ "repo": "rust-overlay", "type": "github" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 475d435..7134e7e 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "Cosmwasm VM"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; rust-overlay = { url = "github:oxalica/rust-overlay"; diff --git a/research/Cargo.toml b/research/Cargo.toml deleted file mode 100644 index 72f0685..0000000 --- a/research/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "research" -version = "0.2.0" -edition = "2021" - - -[[bin]] -name = "research" -path = "bin/research.rs" - -[features] -default = ["iterator", "stargate", "ibc3"] -iterator = ["cosmwasm-vm/iterator"] -stargate = ["cosmwasm-vm/stargate"] -ibc3 = ["cosmwasm-vm/ibc3"] -std = ["cosmwasm-std/std"] - -[dependencies] -cosmwasm-crypto.workspace = true -cosmwasm-std = { workspace = true, default-features = false, features = [ "iterator", "cosmwasm_1_2" ] } -cosmwasm-vm = { path = "../vm", default-features = false } -cosmwasm-vm-wasmi = { path = "../vm-wasmi" } -either = { version = "1.8", default-features = false } -env_logger = "0.9" -hex = { version = "0.4", default-features = false, features = ["alloc"] } -log = { workspace = true, default-features = false } -serde = { workspace = true, default-features = false, features = ["derive"] } -serde_json = { workspace = true, default-features = false, features = ["alloc"] } -wasm-instrument = { workspace = true, default-features = false } -wasmi = { workspace = true, default-features = false } -wasmi-validation = { workspace = true, default-features = false } -wat = "1.0" - -# benches -tracing = "0.1" -tracing-subscriber = "0.3" -tracing-opentelemetry = "*" -opentelemetry = "*" diff --git a/research/bin/research.rs b/research/bin/research.rs deleted file mode 100644 index f8dc7a1..0000000 --- a/research/bin/research.rs +++ /dev/null @@ -1,1145 +0,0 @@ -#![feature(trait_alias)] -#![feature(assert_matches)] -#![allow(soft_unstable)] -#![feature(test)] - -extern crate alloc; - -extern crate std; - -use cosmwasm_vm::vm::VMBase; - -use alloc::{ - collections::BTreeMap, - string::{String, ToString}, - vec, - vec::Vec, -}; -use core::{ - assert_matches::assert_matches, - fmt::{Debug, Display}, - num::NonZeroU32, - str::FromStr, -}; -#[cfg(feature = "stargate")] -use cosmwasm_std::IbcTimeout; -#[cfg(feature = "iterator")] -use cosmwasm_std::Order; -use cosmwasm_std::{ - Addr, Binary, BlockInfo, CanonicalAddr, CodeInfoResponse, Coin, ContractInfo, - ContractInfoResponse, Empty, Env, Event, MessageInfo, Reply, SystemResult, Timestamp, -}; -use cosmwasm_vm::{ - executor::{ - cosmwasm_call, CosmwasmExecutionResult, CosmwasmQueryResult, ExecuteCall, ExecutorError, - InstantiateCall, InstantiateResult, MigrateCall, QueryCall, QueryResult, ReplyCall, - }, - has::Has, - memory::{MemoryReadError, MemoryWriteError}, - system::{ - cosmwasm_system_entrypoint, cosmwasm_system_run, CosmwasmCodeId, CosmwasmContractMeta, - SystemError, - }, - transaction::Transactional, - vm::{VmErrorOf, VmGas, VmGasCheckpoint}, -}; -use cosmwasm_vm_wasmi::{ - new_wasmi_vm, OwnedWasmiVM, WasmiContext, WasmiInput, WasmiModule, WasmiOutput, WasmiVMError, -}; -use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; -use wasm_instrument::gas_metering::Rules; -use wasmi::core::HostError; - -const CANONICAL_LENGTH: usize = 54; -const SHUFFLES_ENCODE: usize = 18; -const SHUFFLES_DECODE: usize = 2; - -#[derive(PartialEq, Debug)] -enum SimpleVMError { - Interpreter, - VMError(WasmiVMError), - CodeNotFound(CosmwasmCodeId), - ContractNotFound(BankAccount), - InvalidAddress, - InvalidAccountFormat, - NoCustomQuery, - NoCustomMessage, - Unsupported, - OutOfGas, - #[cfg(feature = "iterator")] - IteratorDoesNotExist, - CannotDeserialize, - Crypto, -} - -impl HostError for SimpleVMError {} - -impl From for SimpleVMError { - fn from(_: wasmi::Error) -> Self { - Self::Interpreter - } -} -impl From for SimpleVMError { - fn from(e: WasmiVMError) -> Self { - SimpleVMError::VMError(e) - } -} -impl From for SimpleVMError { - fn from(e: SystemError) -> Self { - SimpleVMError::VMError(e.into()) - } -} -impl From for SimpleVMError { - fn from(e: ExecutorError) -> Self { - SimpleVMError::VMError(e.into()) - } -} -impl From for SimpleVMError { - fn from(e: MemoryReadError) -> Self { - SimpleVMError::VMError(e.into()) - } -} -impl From for SimpleVMError { - fn from(e: MemoryWriteError) -> Self { - SimpleVMError::VMError(e.into()) - } -} -impl Display for SimpleVMError { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "{self:?}") - } -} - -#[derive(Default, Clone, PartialEq, Eq, Debug)] -struct Gas { - checkpoints: Vec, -} - -impl Gas { - fn new(initial_value: u64) -> Self { - Gas { - checkpoints: vec![initial_value], - } - } - - fn current(&self) -> &u64 { - self.checkpoints.last().expect("impossible") - } - fn current_mut(&mut self) -> &mut u64 { - self.checkpoints.last_mut().expect("impossible") - } - fn push(&mut self, checkpoint: &VmGasCheckpoint) -> Result<(), SimpleVMError> { - match checkpoint { - VmGasCheckpoint::Unlimited => { - let parent = self.current_mut(); - let value = *parent; - *parent = 0; - self.checkpoints.push(value); - Ok(()) - } - VmGasCheckpoint::Limited(limit) if limit <= self.current() => { - *self.current_mut() -= limit; - self.checkpoints.push(*limit); - Ok(()) - } - VmGasCheckpoint::Limited(_) => Err(SimpleVMError::OutOfGas), - } - } - fn pop(&mut self) { - let child = self.checkpoints.pop().expect("impossible"); - let parent = self.current_mut(); - *parent += child; - } - fn charge(&mut self, value: u64) -> Result<(), SimpleVMError> { - let current = self.current_mut(); - if *current >= value { - *current -= value; - Ok(()) - } else { - Err(SimpleVMError::OutOfGas) - } - } -} - -#[cfg(feature = "iterator")] -#[derive(Default, Clone, Debug)] -struct Iter { - data: Vec<(Vec, Vec)>, - position: usize, -} - -#[derive(Default, Clone, Debug)] -struct SimpleWasmiVMStorage { - data: BTreeMap, Vec>, - #[cfg(feature = "iterator")] - iterators: BTreeMap, -} - -#[cfg(feature = "stargate")] -#[derive(Debug, Clone, PartialEq, Eq)] -struct SimpleIBCPacket { - channel_id: String, - data: Binary, - timeout: IbcTimeout, -} - -#[cfg(feature = "stargate")] -#[derive(Default, Clone, Debug, PartialEq, Eq)] -struct SimpleIBCState { - packets_sent: Vec, -} - -#[derive(Default, Clone)] -struct SimpleWasmiVMExtension { - #[cfg(feature = "stargate")] - ibc: BTreeMap, - storage: BTreeMap, - codes: BTreeMap>, - contracts: BTreeMap>, - next_account_id: BankAccount, - transaction_depth: u32, - gas: Gas, - call_depth: u32, -} - -struct SimpleWasmiVM<'a> { - /// module which is in context of vm and executable now - executing_module: Option, - pub env: Env, - info: MessageInfo, - extension: &'a mut SimpleWasmiVMExtension, -} - -impl<'a> WasmiContext for SimpleWasmiVM<'a> { - fn executing_module(&self) -> Option { - self.executing_module.clone() - } - - fn set_wasmi_context(&mut self, instance: wasmi::Instance, memory: wasmi::Memory) { - self.executing_module = Some(WasmiModule { instance, memory }); - } - - fn call_depth_mut(&mut self) -> &mut u32 { - &mut self.extension.call_depth - } -} - -impl<'a> SimpleWasmiVM<'a> { - pub fn load_subvm( - &mut self, - address: ::Address, - funds: Vec, - ) -> Result, VmErrorOf> { - log::debug!("Loading sub-vm, contract address: {:?}", address); - let code = (|| { - let CosmwasmContractMeta { code_id, .. } = self - .extension - .contracts - .get(&address) - .cloned() - .ok_or(SimpleVMError::ContractNotFound(address))?; - self.extension - .codes - .get(&code_id) - .ok_or(SimpleVMError::CodeNotFound(code_id)) - .cloned() - })()?; - let sub_vm = SimpleWasmiVM { - executing_module: None, - env: Env { - block: self.env.block.clone(), - transaction: self.env.transaction.clone(), - contract: ContractInfo { - address: address.into(), - }, - }, - info: MessageInfo { - sender: self.env.contract.address.clone(), - funds, - }, - extension: self.extension, - }; - let sub_vm = new_wasmi_vm::(&code, sub_vm)?; - Ok(sub_vm) - } -} - -#[derive(Debug, Clone)] -struct CanonicalAddress(pub CanonicalAddr); - -impl TryFrom> for CanonicalAddress { - type Error = SimpleVMError; - fn try_from(value: Vec) -> Result { - Ok(CanonicalAddress(CanonicalAddr(Binary::from(value)))) - } -} - -impl From for Vec { - fn from(addr: CanonicalAddress) -> Self { - addr.0.into() - } -} - -impl From for CanonicalAddr { - fn from(addr: CanonicalAddress) -> Self { - addr.0 - } -} - -impl<'a> VMBase for SimpleWasmiVM<'a> { - type Input<'x> = WasmiInput>; - type Output<'x> = WasmiOutput>; - type QueryCustom = Empty; - type MessageCustom = Empty; - type ContractMeta = CosmwasmContractMeta; - type Address = BankAccount; - type CanonicalAddress = CanonicalAddress; - type StorageKey = Vec; - type StorageValue = Vec; - type Error = SimpleVMError; - - fn running_contract_meta(&mut self) -> Result { - Ok(self - .extension - .contracts - .get( - &BankAccount::try_from(self.env.contract.address.clone()) - .expect("contract address is set by vm, this should never happen"), - ) - .cloned() - .expect("contract is inserted by vm, this should never happen")) - } - - fn set_contract_meta( - &mut self, - address: Self::Address, - contract_meta: Self::ContractMeta, - ) -> Result<(), Self::Error> { - let meta = self - .extension - .contracts - .get_mut(&address) - .ok_or(SimpleVMError::ContractNotFound(address))?; - - *meta = contract_meta; - - Ok(()) - } - - fn contract_meta(&mut self, address: Self::Address) -> Result { - self.extension - .contracts - .get_mut(&address) - .ok_or(SimpleVMError::ContractNotFound(address)) - .cloned() - } - - fn continue_query( - &mut self, - address: Self::Address, - message: &[u8], - ) -> Result { - let mut sub_vm = self.load_subvm(address, vec![])?; - cosmwasm_call::>(&mut sub_vm, message) - } - - fn continue_execute( - &mut self, - address: Self::Address, - funds: Vec, - message: &[u8], - event_handler: &mut dyn FnMut(Event), - ) -> Result, Self::Error> { - let mut sub_vm = self.load_subvm(address, funds)?; - cosmwasm_system_run::, _>( - &mut sub_vm, - message, - event_handler, - ) - } - - fn continue_instantiate( - &mut self, - contract_meta: Self::ContractMeta, - funds: Vec, - message: &[u8], - event_handler: &mut dyn FnMut(Event), - ) -> Result<(Self::Address, Option), Self::Error> { - let BankAccount(address) = self.extension.next_account_id; - self.extension.next_account_id = BankAccount(address + 1); - self.extension - .contracts - .insert(BankAccount(address), contract_meta); - - let mut sub_vm = self.load_subvm(BankAccount(address), funds)?; - cosmwasm_system_run::, _>( - &mut sub_vm, - message, - event_handler, - ) - .map(|data| (BankAccount(address), data)) - } - - fn continue_instantiate2( - &mut self, - contract_meta: Self::ContractMeta, - funds: Vec, - message: &[u8], - _salt: &[u8], - event_handler: &mut dyn FnMut(Event), - ) -> Result<(Self::Address, Option), Self::Error> { - let BankAccount(address) = self.extension.next_account_id; - self.extension.next_account_id = BankAccount(address + 1); - self.extension - .contracts - .insert(BankAccount(address), contract_meta); - - let mut sub_vm = self.load_subvm(BankAccount(address), funds)?; - cosmwasm_system_run::, _>( - &mut sub_vm, - message, - event_handler, - ) - .map(|data| (BankAccount(address), data)) - } - - fn continue_migrate( - &mut self, - address: Self::Address, - message: &[u8], - event_handler: &mut dyn FnMut(Event), - ) -> Result, Self::Error> { - let mut sub_vm = self.load_subvm(address, vec![])?; - cosmwasm_system_run::, _>( - &mut sub_vm, - message, - event_handler, - ) - } - - fn continue_reply( - &mut self, - message: Reply, - event_handler: &mut dyn FnMut(Event), - ) -> Result, Self::Error> { - let mut sub_vm = self.load_subvm( - self.env.contract.address.clone().into_string().try_into()?, - vec![], - )?; - - cosmwasm_system_run::, OwnedWasmiVM>( - &mut sub_vm, - &serde_json::to_vec(&message).map_err(|_| SimpleVMError::CannotDeserialize)?, - event_handler, - ) - } - - fn query_custom( - &mut self, - _: Self::QueryCustom, - ) -> Result, Self::Error> { - Err(SimpleVMError::NoCustomQuery) - } - - fn message_custom( - &mut self, - _: Self::MessageCustom, - _: &mut dyn FnMut(Event), - ) -> Result, Self::Error> { - Err(SimpleVMError::NoCustomMessage) - } - - fn query_raw( - &mut self, - address: Self::Address, - key: Self::StorageKey, - ) -> Result, Self::Error> { - Ok(self - .extension - .storage - .get(&address) - .unwrap_or(&SimpleWasmiVMStorage::default()) - .data - .get(&key) - .cloned()) - } - - fn transfer_from( - &mut self, - from: &Self::Address, - to: &Self::Address, - funds: &[Coin], - ) -> Result<(), Self::Error> { - log::debug!("Transfer from: {:?} -> {:?}\n{:?}", from, to, funds); - Ok(()) - } - - fn transfer(&mut self, to: &Self::Address, funds: &[Coin]) -> Result<(), Self::Error> { - log::debug!( - "Transfer: {:?} -> {:?}\n{:?}", - self.env.contract.address, - to, - funds - ); - Ok(()) - } - - fn burn(&mut self, funds: &[Coin]) -> Result<(), Self::Error> { - log::debug!("Burn: {:?}\n{:?}", self.env.contract.address, funds); - Ok(()) - } - - fn balance(&mut self, _: &Self::Address, _: String) -> Result { - log::debug!("Query balance."); - Err(SimpleVMError::Unsupported) - } - - fn supply(&mut self, _: String) -> Result { - log::debug!("Supply."); - Err(SimpleVMError::Unsupported) - } - - fn all_balance(&mut self, _: &Self::Address) -> Result, Self::Error> { - log::debug!("Query all balance."); - Ok(vec![]) - } - - fn query_contract_info( - &mut self, - _: Self::Address, - ) -> Result { - Err(SimpleVMError::Unsupported) - } - - fn query_code_info(&mut self, _: CosmwasmCodeId) -> Result { - Err(SimpleVMError::Unsupported) - } - - fn debug(&mut self, message: Vec) -> Result<(), Self::Error> { - log::info!("[contract-debug] {}", String::from_utf8_lossy(&message)); - Ok(()) - } - - #[cfg(feature = "iterator")] - #[tracing::instrument(skip(self, _order))] - fn db_scan( - &mut self, - _start: Option, - _end: Option, - _order: Order, - ) -> Result { - let contract_addr = self.env.contract.address.clone().try_into()?; - let mut empty = SimpleWasmiVMStorage::default(); - let storage = self - .extension - .storage - .get_mut(&contract_addr) - .unwrap_or(&mut empty); - - let data = storage.data.clone().into_iter().collect::>(); - // Exceeding u32 size is fatal - let last_id: u32 = storage - .iterators - .len() - .try_into() - .expect("Found more iterator IDs than supported"); - - let new_id = last_id + 1; - let iter = Iter { data, position: 0 }; - storage.iterators.insert(new_id, iter); - - Ok(new_id) - } - - #[cfg(feature = "iterator")] - #[tracing::instrument(skip(self))] - fn db_next( - &mut self, - iterator_id: u32, - ) -> Result<(Self::StorageKey, Self::StorageValue), Self::Error> { - let contract_addr = self.env.contract.address.clone().try_into()?; - let storage = self - .extension - .storage - .get_mut(&contract_addr) - .ok_or(SimpleVMError::IteratorDoesNotExist)?; - - let iterator = storage - .iterators - .get_mut(&iterator_id) - .ok_or(SimpleVMError::IteratorDoesNotExist)?; - - let position = iterator.position; - if iterator.data.len() > position { - iterator.position += 1; - Ok(iterator.data[position].clone()) - } else { - // Empty data works like `None` in rust iterators - Ok((Vec::default(), Vec::default())) - } - } - - fn secp256k1_verify( - &mut self, - message_hash: &[u8], - signature: &[u8], - public_key: &[u8], - ) -> Result { - cosmwasm_crypto::secp256k1_verify(message_hash, signature, public_key) - .map_err(|_| SimpleVMError::Crypto) - } - - fn secp256k1_recover_pubkey( - &mut self, - message_hash: &[u8], - signature: &[u8], - recovery_param: u8, - ) -> Result, ()>, Self::Error> { - Ok( - cosmwasm_crypto::secp256k1_recover_pubkey(message_hash, signature, recovery_param) - .map_err(|_| ()), - ) - } - - fn ed25519_verify( - &mut self, - message: &[u8], - signature: &[u8], - public_key: &[u8], - ) -> Result { - cosmwasm_crypto::ed25519_verify(message, signature, public_key) - .map_err(|_| SimpleVMError::Crypto) - } - - fn ed25519_batch_verify( - &mut self, - messages: &[&[u8]], - signatures: &[&[u8]], - public_keys: &[&[u8]], - ) -> Result { - cosmwasm_crypto::ed25519_batch_verify(messages, signatures, public_keys) - .map_err(|_| SimpleVMError::Crypto) - } - - fn addr_validate(&mut self, input: &str) -> Result, Self::Error> { - let canonical = match self.addr_canonicalize(input)? { - Ok(canonical) => canonical, - Err(e) => return Ok(Err(e)), - }; - let normalized = match self.addr_humanize(&canonical)? { - Ok(canonical) => canonical, - Err(e) => return Ok(Err(e)), - }; - let account = BankAccount::try_from(input.to_string())?; - Ok(if account == normalized { - Ok(()) - } else { - Err(SimpleVMError::InvalidAddress) - }) - } - - fn addr_canonicalize( - &mut self, - input: &str, - ) -> Result, Self::Error> { - // mimicks formats like hex or bech32 where different casings are valid for one address - let normalized = input.to_lowercase(); - - // Dummy input validation. This is more sophisticated for formats like bech32, where format and checksum are validated. - if normalized.len() < 3 { - return Ok(Err(SimpleVMError::InvalidAddress)); - } - - if normalized.len() > CANONICAL_LENGTH { - return Ok(Err(SimpleVMError::InvalidAddress)); - } - - let mut out = Vec::from(normalized); - // pad to canonical length with NULL bytes - out.resize(CANONICAL_LENGTH, 0x00); - // content-dependent rotate followed by shuffle to destroy - let rotate_by = digit_sum(&out) % CANONICAL_LENGTH; - out.rotate_left(rotate_by); - Ok(Ok(riffle_shuffle_n(out, SHUFFLES_ENCODE).try_into()?)) - } - - fn addr_humanize( - &mut self, - addr: &Self::CanonicalAddress, - ) -> Result, Self::Error> { - if addr.0.len() != CANONICAL_LENGTH { - return Ok(Err(SimpleVMError::InvalidAddress)); - } - - // Shuffle two more times which restored the original value (24 elements are back to original after 20 rounds) - let mut tmp = riffle_shuffle_n(addr.clone().into(), SHUFFLES_DECODE); - // Rotate back - let rotate_by = digit_sum(&tmp) % CANONICAL_LENGTH; - tmp.rotate_right(rotate_by); - // Remove NULL bytes (i.e. the padding) - let trimmed = tmp.into_iter().filter(|&x| x != 0x00).collect(); - // decode UTF-8 bytes into string - let Ok(human) = String::from_utf8(trimmed) else { return Ok(Err(SimpleVMError::InvalidAddress)) }; - Ok( - BankAccount::try_from(Addr::unchecked(human)) - .map_err(|_| SimpleVMError::InvalidAddress), - ) - } - - #[tracing::instrument(skip(self))] - fn db_read( - &mut self, - key: Self::StorageKey, - ) -> Result, Self::Error> { - let contract_addr = self.env.contract.address.clone().try_into()?; - let empty = SimpleWasmiVMStorage::default(); - Ok(self - .extension - .storage - .get(&contract_addr) - .unwrap_or(&empty) - .data - .get(&key) - .cloned()) - } - - #[tracing::instrument(skip(self))] - fn db_write( - &mut self, - key: Self::StorageKey, - value: Self::StorageValue, - ) -> Result<(), Self::Error> { - let contract_addr = self.env.contract.address.clone().try_into()?; - self.extension - .storage - .entry(contract_addr) - .or_insert_with(SimpleWasmiVMStorage::default) - .data - .insert(key, value); - Ok(()) - } - - #[tracing::instrument(skip(self))] - fn db_remove(&mut self, key: Self::StorageKey) -> Result<(), Self::Error> { - let contract_addr = self.env.contract.address.clone().try_into()?; - self.extension - .storage - .get_mut(&contract_addr) - .map(|contract_storage| contract_storage.data.remove(&key)); - Ok(()) - } - - fn abort(&mut self, message: String) -> Result<(), Self::Error> { - log::debug!("Contract aborted: {}", message); - Err(SimpleVMError::from(WasmiVMError::from( - SystemError::ContractExecutionFailure(message), - ))) - } - - fn charge(&mut self, value: VmGas) -> Result<(), Self::Error> { - let gas_to_charge = match value { - VmGas::Instrumentation { metered } => u64::from(metered), - x => { - log::debug!("Charging gas: {:?}", x); - 1u64 - } - }; - self.extension.gas.charge(gas_to_charge)?; - Ok(()) - } - - fn gas_checkpoint_push(&mut self, checkpoint: VmGasCheckpoint) -> Result<(), Self::Error> { - log::debug!("> Gas before: {:?}", self.extension.gas); - self.extension.gas.push(&checkpoint)?; - log::debug!("> Gas after: {:?}", self.extension.gas); - Ok(()) - } - - fn gas_checkpoint_pop(&mut self) -> Result<(), Self::Error> { - log::debug!("> Gas before: {:?}", self.extension.gas); - self.extension.gas.pop(); - log::debug!("> Gas after: {:?}", self.extension.gas); - Ok(()) - } - - fn gas_ensure_available(&mut self) -> Result<(), Self::Error> { - let checkpoint = self - .extension - .gas - .checkpoints - .last() - .expect("invalis gas checkpoint state"); - if *checkpoint > 0 { - Ok(()) - } else { - Err(SimpleVMError::OutOfGas) - } - } - - #[cfg(feature = "stargate")] - fn ibc_transfer( - &mut self, - _channel_id: String, - _to_address: String, - _amount: Coin, - _timeout: IbcTimeout, - ) -> Result<(), Self::Error> { - todo!() - } - - #[cfg(feature = "stargate")] - fn ibc_send_packet( - &mut self, - channel_id: String, - data: Binary, - timeout: IbcTimeout, - ) -> Result<(), Self::Error> { - let contract_addr = self.env.contract.address.clone().try_into()?; - let entry = self.extension.ibc.entry(contract_addr).or_default(); - entry.packets_sent.push(SimpleIBCPacket { - channel_id, - data, - timeout, - }); - Ok(()) - } - - #[cfg(feature = "stargate")] - fn ibc_close_channel(&mut self, _channel_id: String) -> Result<(), Self::Error> { - todo!() - } -} - -#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -struct BankAccount(u128); - -impl TryFrom for BankAccount { - type Error = SimpleVMError; - fn try_from(value: Addr) -> Result { - value.to_string().try_into() - } -} - -impl TryFrom for BankAccount { - type Error = SimpleVMError; - fn try_from(value: String) -> Result { - Ok(BankAccount( - u128::from_str(&value).map_err(|_| SimpleVMError::InvalidAccountFormat)?, - )) - } -} - -impl From for Addr { - fn from(BankAccount(account): BankAccount) -> Self { - Addr::unchecked(account.to_string()) - } -} - -impl<'a> Has for SimpleWasmiVM<'a> { - fn get(&self) -> Env { - self.env.clone() - } -} -impl<'a> Has for SimpleWasmiVM<'a> { - fn get(&self) -> MessageInfo { - self.info.clone() - } -} - -impl<'a> Transactional for SimpleWasmiVM<'a> { - type Error = SimpleVMError; - fn transaction_begin(&mut self) -> Result<(), Self::Error> { - self.extension.transaction_depth += 1; - log::debug!("> Transaction begin: {}", self.extension.transaction_depth); - Ok(()) - } - fn transaction_commit(&mut self) -> Result<(), Self::Error> { - self.extension.transaction_depth -= 1; - log::debug!("< Transaction end: {}", self.extension.transaction_depth); - Ok(()) - } - fn transaction_rollback(&mut self) -> Result<(), Self::Error> { - self.extension.transaction_depth -= 1; - log::debug!("< Transaction abort: {}", self.extension.transaction_depth); - Ok(()) - } -} - -struct ConstantCostRules; -impl Rules for ConstantCostRules { - fn instruction_cost( - &self, - _: &wasm_instrument::parity_wasm::elements::Instruction, - ) -> Option { - Some(42) - } - - fn memory_grow_cost(&self) -> wasm_instrument::gas_metering::MemoryGrowCost { - wasm_instrument::gas_metering::MemoryGrowCost::Linear( - NonZeroU32::new(1024).expect("impossible"), - ) - } - - fn call_per_local_cost(&self) -> u32 { - 0 - } -} - -fn instrument_contract(code: &[u8]) -> Vec { - let module = wasm_instrument::parity_wasm::elements::Module::from_bytes(code).unwrap(); - let backend = wasm_instrument::gas_metering::host_function::Injector::new("env", "gas"); - wasm_instrument::gas_metering::inject(module, backend, &ConstantCostRules) - .unwrap() - .into_bytes() - .unwrap() -} - -pub fn initialize() { - use std::sync::Once; - static INIT: Once = Once::new(); - INIT.call_once(|| { - // setup this one to tune output - let mut builder = env_logger::builder(); - builder.format_timestamp_nanos(); - builder.try_init().unwrap(); - - let collector = tracing_subscriber::fmt::layer() - .with_level(true) - .with_line_number(true); - let tracer = opentelemetry::sdk::export::trace::stdout::new_pipeline().install_simple(); - let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); - let subscriber = tracing_subscriber::Registry::default() - .with(telemetry) - .with(collector); - tracing::subscriber::set_global_default(subscriber).unwrap(); - }); -} - -fn create_vm( - extension: &mut SimpleWasmiVMExtension, - env: Env, - info: MessageInfo, -) -> Result, SimpleVMError> { - initialize(); - let code = extension - .codes - .get( - &extension - .contracts - .get(&env.clone().contract.address.try_into().unwrap()) - .expect("contract should have been uploaded") - .code_id, - ) - .expect("contract should have been uploaded") - .clone(); - let vm = SimpleWasmiVM { - executing_module: None, - env, - info, - extension, - }; - let vm = new_wasmi_vm::(&code, vm)?; - Ok(vm) -} - -fn create_simple_vm( - sender: BankAccount, - contract: BankAccount, - funds: Vec, - extension: &mut SimpleWasmiVMExtension, -) -> Result, SimpleVMError> { - create_vm( - extension, - Env { - block: BlockInfo { - height: 0xDEAD_C0DE, - time: Timestamp::from_seconds(10000), - chain_id: "abstract-test".into(), - }, - transaction: None, - contract: ContractInfo { - address: contract.into(), - }, - }, - MessageInfo { - sender: sender.into(), - funds, - }, - ) -} - -fn main() { - let iter = 100; - let cw20_base_code = instrument_contract(include_bytes!("../../fixtures/cw20_base.wasm")); - let hackatom_code = instrument_contract(include_bytes!("../../fixtures/hackatom.wasm")); - let reflect_code = instrument_contract(include_bytes!("../../fixtures/reflect.wasm")); - - let sender = BankAccount(100); - let cw20_address = BankAccount(10_000); - let hackatom_address = BankAccount(10_002); - let reflect_address = BankAccount(10_003); - let funds = vec![]; - let mut extension = SimpleWasmiVMExtension { - storage: BTreeMap::default(), - codes: BTreeMap::from([ - (0x1337, cw20_base_code), - (0x1338, hackatom_code), - (0x1339, reflect_code), - ]), - contracts: BTreeMap::from([ - ( - cw20_address, - CosmwasmContractMeta { - code_id: 0x1337, - admin: None, - label: String::new(), - }, - ), - ( - hackatom_address, - CosmwasmContractMeta { - code_id: 0x1338, - admin: None, - label: String::new(), - }, - ), - ( - reflect_address, - CosmwasmContractMeta { - code_id: 0x1339, - admin: None, - label: String::new(), - }, - ), - ]), - next_account_id: BankAccount(10_004), - transaction_depth: 0, - gas: Gas::new(u64::MAX), - ..Default::default() - }; - - { - { - let mut vm = - create_simple_vm(sender, cw20_address, funds.clone(), &mut extension).unwrap(); - - assert_matches!( - cosmwasm_call::, OwnedWasmiVM>( - &mut vm, - r#"{ - "name": "Picasso", - "symbol": "PICA", - "decimals": 12, - "initial_balances": [], - "mint": null, - "marketing": null - }"# - .as_bytes(), - ) - .unwrap(), - InstantiateResult(CosmwasmExecutionResult::Ok(_)) - ); - - for _ in 0..iter { - cosmwasm_call::>( - &mut vm, - r#"{ "token_info": {} }"#.as_bytes(), - ) - .unwrap(); - } - } - { - let mut vm = - create_simple_vm(sender, reflect_address, funds.clone(), &mut extension).unwrap(); - let _ = cosmwasm_system_entrypoint::>( - &mut vm, - r#"{}"#.as_bytes(), - ) - .unwrap(); - - for _ in 0..iter { - cosmwasm_system_entrypoint::>( - &mut vm, - r#"{ - "reflect_sub_msg": { - "msgs": [{ - "id": 10, - "msg": { - "wasm": { - "execute": { - "contract_addr": "10001", - "msg": "eyAicmVsZWFzZSI6IHt9IH0=", - "funds": [] - } - } - }, - "gas_limit": null, - "reply_on": "always" - }] - } - }"# - .as_bytes(), - ) - .unwrap(); - } - } - - { - let mut vm = create_simple_vm(sender, hackatom_address, funds, &mut extension).unwrap(); - - cosmwasm_system_entrypoint::( - &mut vm, - r#"{"verifier": "10000", "beneficiary": "10000"}"#.as_bytes(), - ) - .unwrap(); - for _ in 0..iter { - cosmwasm_call::>( - &mut vm, - r#"{ "recurse": { "depth": 10, "work": 10 }}"#.as_bytes(), - ) - .unwrap(); - } - } - } -} - -pub fn digit_sum(input: &[u8]) -> usize { - input.iter().map(|v| *v as usize).sum() -} - -fn riffle_shuffle_n(mut input: Vec, n: usize) -> Vec { - assert!( - input.len() % 2 == 0, - "Method only defined for even number of elements" - ); - let mid = input.len() / 2; - let mut tmp = Vec::with_capacity(input.len()); - for _ in 0..n { - tmp.clear(); - let (left, right) = input.split_at(mid); - for (l, r) in left.iter().zip(right.iter()) { - tmp.push(*r); - tmp.push(*l); - } - core::mem::swap(&mut input, &mut tmp); - } - input -} - -#[test] -fn test_riffle_shuffle() { - let input = [0, 1, 2, 3, 4, 5, 6, 7]; - let out1 = riffle_shuffle_n(input.to_vec(), 1); - let out2 = riffle_shuffle_n(input.to_vec(), 2); - let out3 = riffle_shuffle_n(input.to_vec(), 3); - assert_eq!(&[4, 0, 5, 1, 6, 2, 7, 3], out1.as_slice()); - assert_eq!(&[6, 4, 2, 0, 7, 5, 3, 1], out2.as_slice()); - assert_eq!(&[7, 6, 5, 4, 3, 2, 1, 0], out3.as_slice()); -} - -#[test] -fn test_riffle_shuffle_encode_decode() { - let input: Vec = (0..CANONICAL_LENGTH as u8).collect(); - let output = riffle_shuffle_n(input.clone(), SHUFFLES_ENCODE + SHUFFLES_DECODE); - assert_eq!(input, output); -} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 735830c..e8cc993 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2022-12-07" +channel = "nightly-2023-03-09" components = [ "clippy", "llvm-tools-preview", @@ -7,4 +7,8 @@ components = [ "rustfmt", "rust-src", ] -targets = ["thumbv7em-none-eabi", "wasm32-unknown-unknown"] +targets = [ + "wasm32-unknown-unknown", + # that is the way to test that it checks and builds againt no_std target + "thumbv7em-none-eabi", +]