diff --git a/Cargo.lock b/Cargo.lock index 7a0882c97a..b8d7d9dc9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,14 +1211,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "bitseed_generator" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "serde 1.0.210", -] - [[package]] name = "bitvec" version = "0.20.4" @@ -1888,6 +1880,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "clru" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59" + [[package]] name = "codespan" version = "0.11.1" @@ -2142,14 +2140,12 @@ dependencies = [ [[package]] name = "cosmwasm-core" version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d905990ef3afb5753bb709dc7de88e9e370aa32bcc2f31731d4b533b63e82490" +source = "git+https://github.com/rooch-network/cosmwasm?rev=597d3e8437d8c4d1afce07e5a676c29c751a8a81#597d3e8437d8c4d1afce07e5a676c29c751a8a81" [[package]] name = "cosmwasm-crypto" version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b2a7bd9c1dd9a377a4dc0f4ad97d24b03c33798cd5a6d7ceb8869b41c5d2f2d" +source = "git+https://github.com/rooch-network/cosmwasm?rev=597d3e8437d8c4d1afce07e5a676c29c751a8a81#597d3e8437d8c4d1afce07e5a676c29c751a8a81" dependencies = [ "ark-bls12-381", "ark-ec", @@ -2171,8 +2167,7 @@ dependencies = [ [[package]] name = "cosmwasm-derive" version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029910b409398fdf81955d7301b906caf81f2c42b013ea074fbd89720229c424" +source = "git+https://github.com/rooch-network/cosmwasm?rev=597d3e8437d8c4d1afce07e5a676c29c751a8a81#597d3e8437d8c4d1afce07e5a676c29c751a8a81" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", @@ -2182,8 +2177,7 @@ dependencies = [ [[package]] name = "cosmwasm-std" version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51dec99a2e478715c0a4277f0dbeadbb8466500eb7dec873d0924edd086e77f1" +source = "git+https://github.com/rooch-network/cosmwasm?rev=597d3e8437d8c4d1afce07e5a676c29c751a8a81#597d3e8437d8c4d1afce07e5a676c29c751a8a81" dependencies = [ "base64 0.22.1", "bech32 0.11.0", @@ -2202,6 +2196,32 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-vm" +version = "2.1.3" +source = "git+https://github.com/rooch-network/cosmwasm?rev=597d3e8437d8c4d1afce07e5a676c29c751a8a81#597d3e8437d8c4d1afce07e5a676c29c751a8a81" +dependencies = [ + "bech32 0.11.0", + "bytes", + "clru", + "cosmwasm-core", + "cosmwasm-crypto", + "cosmwasm-std", + "crc32fast", + "derivative", + "hex", + "rand_core 0.6.4", + "schemars", + "serde 1.0.210", + "serde_json", + "sha2 0.10.8", + "strum", + "thiserror", + "tracing", + "wasmer", + "wasmer-middlewares", +] + [[package]] name = "cpp_demangle" version = "0.4.3" @@ -7056,6 +7076,7 @@ name = "moveos-stdlib" version = "0.7.2" dependencies = [ "anyhow", + "base64 0.22.1", "bech32 0.11.0", "better_any", "bs58 0.5.1", @@ -9544,6 +9565,28 @@ dependencies = [ "serde_yaml 0.9.34+deprecated", ] +[[package]] +name = "rooch-cosmwasm-vm" +version = "0.7.2" +dependencies = [ + "accumulator", + "anyhow", + "cosmwasm-std", + "cosmwasm-vm", + "move-binary-format", + "move-core-types", + "move-resource-viewer", + "move-vm-types", + "moveos-config", + "moveos-object-runtime", + "moveos-types", + "once_cell", + "prometheus", + "raw-store", + "rooch-types", + "tokio", +] + [[package]] name = "rooch-da" version = "0.7.2" @@ -9826,6 +9869,8 @@ dependencies = [ "bcs", "bitcoin 0.32.2", "ciborium", + "cosmwasm-std", + "cosmwasm-vm", "fastcrypto 0.1.8 (git+https://github.com/MystenLabs/fastcrypto?rev=56f6223b84ada922b6cb2c672c69db2ea3dc6a13)", "hex", "libc", @@ -9836,9 +9881,12 @@ dependencies = [ "move-vm-runtime", "move-vm-types", "moveos", + "moveos-object-runtime", "moveos-stdlib", "moveos-types", "moveos-wasm", + "once_cell", + "rooch-cosmwasm-vm", "rooch-framework", "rooch-types", "serde_json", @@ -13198,9 +13246,9 @@ dependencies = [ [[package]] name = "wasmer" -version = "4.3.6" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be5fa49d7d97f83e095f090dcc178d923f2970f588443283cd7a94974ab8cbe" +checksum = "3de82c1520fb75ade394d6c89cac2b05d1b7b4205a45099efcd078b0bc828375" dependencies = [ "bytes", "cfg-if", @@ -13218,18 +13266,19 @@ dependencies = [ "wasm-bindgen", "wasmer-compiler", "wasmer-compiler-cranelift", + "wasmer-compiler-singlepass", "wasmer-derive", "wasmer-types", "wasmer-vm", "wat", - "windows-sys 0.59.0", + "winapi", ] [[package]] name = "wasmer-compiler" -version = "4.3.6" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9696a040f935903db440078cd287c0288ab152394122de442fdd21b3eaa8cd2c" +checksum = "d6c18588e4b92adb2ea88a2897c93615538c7e40cfec8dc6efc3fcee63299a81" dependencies = [ "backtrace", "bytes", @@ -13250,15 +13299,15 @@ dependencies = [ "wasmer-types", "wasmer-vm", "wasmparser", - "windows-sys 0.59.0", + "winapi", "xxhash-rust", ] [[package]] name = "wasmer-compiler-cranelift" -version = "4.3.6" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5959da148d41a5870d1b18a880e19353add47c0ca95e510061275ea467b6b44" +checksum = "cf497986b83f1ae8589a22f046b803d6d1d77bbeda93e75baa89ab6501838f7c" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -13275,9 +13324,9 @@ dependencies = [ [[package]] name = "wasmer-compiler-singlepass" -version = "4.3.6" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fcaa0fa11a5680268d85b17dc032ec90b63138caa66c8484b8f8f6923d7619f" +checksum = "a1093545b718fdba6dc0b9d5cfac843fa859fbc10d1dfaa72a21fac19c6a5d67" dependencies = [ "byteorder", "dynasm", @@ -13316,9 +13365,9 @@ dependencies = [ [[package]] name = "wasmer-derive" -version = "4.3.6" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f448efbe12d656ba96d997c9e338f15cd80934c81f2286c2730cb9224d4e41d" +checksum = "45ab99baa393da623dbca6390c17bd9cd7666e8c48f6b42b4f8c635106a09ca8" dependencies = [ "proc-macro-error", "proc-macro2 1.0.86", @@ -13326,11 +13375,22 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "wasmer-middlewares" +version = "4.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5f8fee717085401399c343b5c0daa37179c9f31c64889e9ae183a4bad15e37" +dependencies = [ + "wasmer", + "wasmer-types", + "wasmer-vm", +] + [[package]] name = "wasmer-types" -version = "4.3.6" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8b383ef63005176be3bc2056d3b4078ae1497b324f573d79acbf81036f1c9ec" +checksum = "0aebf59870ee6e2ef0264d87e6a83f767f5cfe83e21470c0cc32e06231335e23" dependencies = [ "bytecheck", "enum-iterator", @@ -13349,16 +13409,16 @@ dependencies = [ [[package]] name = "wasmer-vm" -version = "4.3.6" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c371597ec33248e775de641c7a475173fb60f2b5ea085c74d34cee9fad06b83" +checksum = "0df3b84ac2c450ad04931b558392cadd03d606d94ab15496e13ce56ee8320634" dependencies = [ "backtrace", "cc", "cfg-if", "corosensei", "crossbeam-queue", - "dashmap 6.0.1", + "dashmap 5.5.3", "derivative", "enum-iterator", "fnv", @@ -13372,7 +13432,7 @@ dependencies = [ "scopeguard", "thiserror", "wasmer-types", - "windows-sys 0.59.0", + "winapi", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index aab0ffcfc6..165eb66970 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ members = [ "crates/rooch-test-transaction-builder", "crates/rooch-types", "crates/rooch-event", + "crates/rooch-cosmwasm-vm", "crates/testsuite", "crates/rooch-ord", "frameworks/bitcoin-move", @@ -59,7 +60,6 @@ members = [ "frameworks/moveos-stdlib", "frameworks/rooch-framework", "frameworks/rooch-nursery", - "examples/bitseed_generator", ] default-members = [ @@ -139,6 +139,7 @@ data-verify = { path = "crates/data_verify" } rooch-db = { path = "crates/rooch-db" } rooch-event = { path = "crates/rooch-event" } rooch-ord = { path = "crates/rooch-ord" } +rooch-cosmwasm-vm = { path = "crates/rooch-cosmwasm-vm" } # frameworks framework-types = { path = "frameworks/framework-types" } @@ -322,7 +323,9 @@ base64 = "0.22.1" #criterion-cpu-time = "0.1.0" wasmer = "4.2.5" wasmer-types = "4.2.5" -wasmer-compiler-singlepass = "4.3.6" +wasmer-compiler-singlepass = "4.2.2" +cosmwasm-vm = { git = "https://github.com/rooch-network/cosmwasm", rev = "597d3e8437d8c4d1afce07e5a676c29c751a8a81" } +cosmwasm-std = { git = "https://github.com/rooch-network/cosmwasm", rev = "597d3e8437d8c4d1afce07e5a676c29c751a8a81" } ciborium = "0.2.1" pprof = { version = "0.13.0", features = ["flamegraph", "criterion", "cpp", "frame-pointer", "protobuf-codec"] } celestia-rpc = { git = "https://github.com/eigerco/celestia-node-rs.git", rev = "129272e8d926b4c7badf27a26dea915323dd6489" } diff --git a/crates/rooch-cosmwasm-vm/Cargo.toml b/crates/rooch-cosmwasm-vm/Cargo.toml new file mode 100644 index 0000000000..5bab38e80b --- /dev/null +++ b/crates/rooch-cosmwasm-vm/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "rooch-cosmwasm-vm" + +# Workspace inherited keys +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +publish = { workspace = true } +repository = { workspace = true } +rust-version = { workspace = true } + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = { workspace = true } +once_cell = { workspace = true } +prometheus = { workspace = true } +tokio = { workspace = true } +cosmwasm-vm = { workspace = true } +cosmwasm-std = { workspace = true } + +move-core-types = { workspace = true } +move-vm-types = { workspace = true } +move-resource-viewer = { workspace = true } +move-binary-format = { workspace = true } + +raw-store = { workspace = true } +moveos-config = { workspace = true } +moveos-types = { workspace = true } +moveos-object-runtime = { workspace = true } +accumulator = { workspace = true } + +rooch-types = { workspace = true } \ No newline at end of file diff --git a/crates/rooch-cosmwasm-vm/src/backend.rs b/crates/rooch-cosmwasm-vm/src/backend.rs new file mode 100644 index 0000000000..6d74d2d8b4 --- /dev/null +++ b/crates/rooch-cosmwasm-vm/src/backend.rs @@ -0,0 +1,379 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::ops::Bound; + +use cosmwasm_std::{Binary, ContractResult, Order, Record, SystemResult}; +use cosmwasm_vm::{Backend, BackendApi, BackendError, BackendResult, GasInfo, Querier, Storage}; + +use move_core_types::value::MoveTypeLayout; +use move_core_types::vm_status::StatusCode; +use move_vm_types::loaded_data::runtime_types::Type; +use move_vm_types::values::Value; + +use moveos_object_runtime::runtime_object::RuntimeObject; +use moveos_object_runtime::TypeLayoutLoader; +use moveos_types::h256; +use moveos_types::state::FieldKey; +use moveos_types::state::MoveState; +use moveos_types::state_resolver::StatelessResolver; + +type IteratorItem = (FieldKey, Vec); +type IteratorState = (Vec, usize); + +pub struct MoveStorage<'a> { + object: &'a mut RuntimeObject, + layout_loader: &'a dyn TypeLayoutLoader, + resolver: &'a dyn StatelessResolver, + iterator_id_counter: u32, + iterators: HashMap, +} + +impl<'a> MoveStorage<'a> { + pub fn new( + object: &'a mut RuntimeObject, + layout_loader: &'a dyn TypeLayoutLoader, + resolver: &'a dyn StatelessResolver, + ) -> Self { + MoveStorage { + object, + layout_loader, + resolver, + iterator_id_counter: 0, + iterators: HashMap::new(), + } + } + + fn serialize_value( + &self, + layout: &MoveTypeLayout, + value: &Value, + ) -> Result, BackendError> { + let bytes = match value.simple_serialize(layout) { + Some(bytes) => bytes, + None => return Err(BackendError::BadArgument {}), + }; + Ok(bytes) + } + + fn deserialize_value( + &self, + layout: &MoveTypeLayout, + bytes: &[u8], + ) -> Result { + let value = match Value::simple_deserialize(bytes, layout) { + Some(value) => value, + None => return Err(BackendError::BadArgument {}), + }; + Ok(value) + } +} + +impl Default for MockStorage { + fn default() -> Self { + Self::new() + } +} + +impl<'a> Storage for MoveStorage<'a> { + fn get(&self, key: &[u8]) -> BackendResult>> { + let hash = h256::sha3_256_of(key); + let field_key = FieldKey::new(hash.into()); + let move_layout = Vec::::type_layout(); + let move_type = Type::Vector(Box::new(Type::U8)); + + match self + .object + .get_field(self.layout_loader, self.resolver, field_key, &move_type) + { + Ok(value) => match self.serialize_value(&move_layout, &value.0) { + Ok(bytes) => (Ok(Some(bytes)), GasInfo::new(1, 0)), + Err(e) => (Err(e), GasInfo::new(1, 0)), + }, + Err(e) => { + if e.major_status() == StatusCode::RESOURCE_DOES_NOT_EXIST { + (Ok(None), GasInfo::new(1, 0)) + } else { + ( + Err(BackendError::Unknown { msg: e.to_string() }), + GasInfo::new(1, 0), + ) + } + } + } + } + + fn set(&mut self, key: &[u8], value: &[u8]) -> BackendResult<()> { + let hash = h256::sha3_256_of(key); + let field_key = FieldKey::new(hash.into()); + let move_layout = Vec::::type_layout(); + let move_type = Type::Vector(Box::new(Type::U8)); + + let deserialized_value = match self.deserialize_value(&move_layout, value) { + Ok(v) => v, + Err(e) => return (Err(e), GasInfo::new(1, 0)), + }; + + match self.object.add_field( + self.layout_loader, + self.resolver, + field_key, + &move_type, + deserialized_value, + ) { + Ok(_) => (Ok(()), GasInfo::new(1, 0)), + Err(e) => ( + Err(BackendError::Unknown { msg: e.to_string() }), + GasInfo::new(1, 0), + ), + } + } + + fn remove(&mut self, key: &[u8]) -> BackendResult<()> { + let hash = h256::sha3_256_of(key); + let field_key = FieldKey::new(hash.into()); + let move_type = Type::Vector(Box::new(Type::U8)); + + match self + .object + .remove_field(self.layout_loader, self.resolver, field_key, &move_type) + { + Ok(_) => (Ok(()), GasInfo::new(1, 0)), + Err(e) => { + if e.major_status() == StatusCode::RESOURCE_DOES_NOT_EXIST { + (Ok(()), GasInfo::new(1, 0)) + } else { + ( + Err(BackendError::Unknown { msg: e.to_string() }), + GasInfo::new(1, 0), + ) + } + } + } + } + + fn scan( + &mut self, + start: Option<&[u8]>, + end: Option<&[u8]>, + order: Order, + ) -> BackendResult { + let cursor = start.map(|s| FieldKey::new(h256::sha3_256_of(s).into())); + + match self.object.scan_fields( + self.layout_loader, + self.resolver, + cursor, + usize::MAX, + &Type::Vector(Box::new(Type::U8)), // Assuming values are Vec + ) { + Ok((values, bytes_len_opt)) => { + let move_layout = Vec::::type_layout(); + + let mut records: Vec<(FieldKey, Vec)> = values + .into_iter() + .filter_map(|(key, value)| { + self.serialize_value(&move_layout, &value) + .ok() + .map(|bytes| (key, bytes)) + }) + .collect(); + + if order == Order::Descending { + records.reverse(); + } + + // Apply end filter + if let Some(end_bytes) = end { + let end_key = FieldKey::new(h256::sha3_256_of(end_bytes).into()); + records.retain(|(key, _)| match order { + Order::Ascending => *key < end_key, + Order::Descending => *key > end_key, + }); + } + + let id = self.iterator_id_counter; + self.iterator_id_counter += 1; + self.iterators.insert(id, (records, 0)); + + let gas_used = if let Some(bytes_len) = bytes_len_opt { + bytes_len.into() + } else { + 0 + }; + (Ok(id), GasInfo::new(gas_used, 0)) + } + Err(e) => ( + Err(BackendError::Unknown { msg: e.to_string() }), + GasInfo::new(1, 0), + ), + } + } + + fn next(&mut self, iterator_id: u32) -> BackendResult> { + let (records, index) = match self.iterators.get_mut(&iterator_id) { + Some(it) => it, + None => { + return ( + Err(BackendError::IteratorDoesNotExist { id: iterator_id }), + GasInfo::new(1, 0), + ) + } + }; + + if *index >= records.len() { + return (Ok(None), GasInfo::new(1, 0)); + } + + let (key, value) = &records[*index]; + *index += 1; + + let key_bytes: [u8; 32] = key.0; + + ( + Ok(Some((key_bytes.to_vec(), value.clone()))), + GasInfo::new(1, 0), + ) + } +} + +// Implement BackendApi +#[derive(Clone)] +pub struct MoveBackendApi; + +impl BackendApi for MoveBackendApi { + fn addr_validate(&self, _human: &str) -> BackendResult<()> { + // Implement address validation logic + (Ok(()), GasInfo::new(1, 0)) + } + + fn addr_canonicalize(&self, human: &str) -> BackendResult> { + // Implement address canonicalization logic + (Ok(human.as_bytes().to_vec()), GasInfo::new(1, 0)) + } + + fn addr_humanize(&self, canonical: &[u8]) -> BackendResult { + // Implement address humanization logic + match String::from_utf8(canonical.to_vec()) { + Ok(human) => (Ok(human), GasInfo::new(1, 0)), + Err(_) => (Err(BackendError::InvalidUtf8 {}), GasInfo::new(1, 0)), + } + } +} + +// Implement Querier +#[derive(Clone)] +pub struct MoveBackendQuerier; + +impl Querier for MoveBackendQuerier { + fn query_raw( + &self, + _request: &[u8], + gas_limit: u64, + ) -> BackendResult>> { + // Implement query logic + ( + Ok(SystemResult::Ok(ContractResult::Ok(Binary::from(vec![])))), + GasInfo::with_externally_used(gas_limit), + ) + } +} + +pub fn build_move_backend<'a, 'b: 'a>( + object: &'a mut RuntimeObject, + layout_loader: &'b dyn TypeLayoutLoader, + resolver: &'b dyn StatelessResolver, +) -> Backend, MoveBackendQuerier> { + Backend { + api: MoveBackendApi, + storage: MoveStorage::new(object, layout_loader, resolver), + querier: MoveBackendQuerier, + } +} + +pub struct MockStorage { + data: BTreeMap, Vec>, + iterators: Vec, Vec)>>, +} + +impl MockStorage { + pub fn new() -> Self { + MockStorage { + data: BTreeMap::new(), + iterators: Vec::new(), + } + } +} + +impl Storage for MockStorage { + fn get(&self, key: &[u8]) -> BackendResult>> { + let result = Ok(self.data.get(key).cloned()); + let gas_info = GasInfo::free(); + (result, gas_info) + } + + fn set(&mut self, key: &[u8], value: &[u8]) -> BackendResult<()> { + self.data.insert(key.to_vec(), value.to_vec()); + let result = Ok(()); + let gas_info = GasInfo::free(); + (result, gas_info) + } + + fn remove(&mut self, key: &[u8]) -> BackendResult<()> { + self.data.remove(key); + let result = Ok(()); + let gas_info = GasInfo::free(); + (result, gas_info) + } + + fn scan( + &mut self, + start: Option<&[u8]>, + end: Option<&[u8]>, + order: Order, + ) -> BackendResult { + let start_bound = start.map_or(Bound::Unbounded, |s| Bound::Included(s.to_vec())); + let end_bound = end.map_or(Bound::Unbounded, |e| Bound::Excluded(e.to_vec())); + + let mut items: Vec<_> = self + .data + .range::, (Bound>, Bound>)>((start_bound, end_bound)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + + if order == Order::Descending { + items.reverse(); + } + + self.iterators.push(items); + let result = Ok((self.iterators.len() - 1) as u32); + let gas_info = GasInfo::free(); + (result, gas_info) + } + + fn next(&mut self, iterator_id: u32) -> BackendResult> { + let iterator = match self.iterators.get_mut(iterator_id as usize) { + Some(iter) => iter, + None => { + return ( + Err(BackendError::IteratorDoesNotExist { id: iterator_id }), + GasInfo::free(), + ) + } + }; + + let result = Ok(iterator.pop()); + let gas_info = GasInfo::free(); + (result, gas_info) + } +} + +pub fn build_mock_backend() -> Backend { + Backend { + api: MoveBackendApi, + storage: MockStorage::new(), + querier: MoveBackendQuerier, + } +} diff --git a/crates/rooch-cosmwasm-vm/src/lib.rs b/crates/rooch-cosmwasm-vm/src/lib.rs new file mode 100644 index 0000000000..750bcdcea7 --- /dev/null +++ b/crates/rooch-cosmwasm-vm/src/lib.rs @@ -0,0 +1,4 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +pub mod backend; diff --git a/crates/testsuite/data/cosmwasm_vm_execution_opt.wasm b/crates/testsuite/data/cosmwasm_vm_execution_opt.wasm new file mode 100644 index 0000000000..b633e9da5b Binary files /dev/null and b/crates/testsuite/data/cosmwasm_vm_execution_opt.wasm differ diff --git a/crates/testsuite/data/generator_cosmwasm_opt.wasm b/crates/testsuite/data/generator_cosmwasm_opt.wasm new file mode 100755 index 0000000000..c686e0bf4c Binary files /dev/null and b/crates/testsuite/data/generator_cosmwasm_opt.wasm differ diff --git a/crates/testsuite/features/cmd.feature b/crates/testsuite/features/cmd.feature index 882c55ef22..b55d508b59 100644 --- a/crates/testsuite/features/cmd.feature +++ b/crates/testsuite/features/cmd.feature @@ -436,6 +436,22 @@ Feature: Rooch CLI integration tests # release servers Then stop the server + @serial + Scenario: cosmwasm-vm test + # prepare servers + Given a server for wasm_test + + # publish wasm execution + Then cmd: "move publish -p ../../examples/cosmwasm_vm_execution --named-addresses rooch_examples=default --json" + Then assert: "{{$.move[-1].execution_info.status.type}} == executed" + + # run cosmwasm execute example + Then cmd: "move run --function default::cosmwasm_vm_execution::run_cosmwasm_example --sender default --args 'file:./data/cosmwasm_vm_execution_opt.wasm' --json" + Then assert: "{{$.move[-1].execution_info.status.type}} == executed" + + # release servers + Then stop the server + @serial Scenario: view_function_loop example Given a server for view_function_loop diff --git a/examples/cosmwasm_vm_execution/.gitignore b/examples/cosmwasm_vm_execution/.gitignore new file mode 100644 index 0000000000..d6b374c175 --- /dev/null +++ b/examples/cosmwasm_vm_execution/.gitignore @@ -0,0 +1,2 @@ +/target +/artifacts diff --git a/examples/cosmwasm_vm_execution/Cargo.toml b/examples/cosmwasm_vm_execution/Cargo.toml new file mode 100644 index 0000000000..6efa630d6b --- /dev/null +++ b/examples/cosmwasm_vm_execution/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cosmwasm_vm_execution" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +cosmwasm-std = { version = "2.1.3", features = ["staking"] } +serde = { version = "1.0.103", default-features = false, features = ["derive"] } + diff --git a/examples/cosmwasm_vm_execution/Makefile b/examples/cosmwasm_vm_execution/Makefile new file mode 100644 index 0000000000..7e625b1b9d --- /dev/null +++ b/examples/cosmwasm_vm_execution/Makefile @@ -0,0 +1,17 @@ +.PHONY: all clean build test optimize + +all: build + +build: + cargo wasm --target-dir="./target" + +test: + cargo test + +optimize: build + @mkdir -p ./artifacts + @wasm-opt -Os --signext-lowering "./target/wasm32-unknown-unknown/release/cosmwasm_vm_execution.wasm" -o "./artifacts/cosmwasm_vm_execution_opt.wasm" + @wasm-snip --snip-rust-fmt-code --snip-rust-panicking-code --skip-producers-section ./artifacts/cosmwasm_vm_execution_opt.wasm -o ./artifacts/cosmwasm_vm_execution_snip.wasm + +clean: + rm -rf ./artifacts && rm -rf ./target diff --git a/examples/cosmwasm_vm_execution/Move.toml b/examples/cosmwasm_vm_execution/Move.toml new file mode 100644 index 0000000000..be395725ac --- /dev/null +++ b/examples/cosmwasm_vm_execution/Move.toml @@ -0,0 +1,18 @@ +[package] +name = "cosmwasm_vm_execution" +version = "0.0.1" + +[dependencies] +MoveosStdlib = { local = "../../frameworks/moveos-stdlib" } +BitcoinMove = { local = "../../frameworks/bitcoin-move" } +RoochNursery = { local = "../../frameworks/rooch-nursery" } + +[addresses] +rooch_examples = "_" +moveos_std = "0x2" +rooch_framework = "0x3" +bitcoin_move = "0x4" +rooch_nursery = "0xa" + +[dev-addresses] +rooch_examples = "0x42" diff --git a/examples/cosmwasm_vm_execution/readme.md b/examples/cosmwasm_vm_execution/readme.md new file mode 100644 index 0000000000..3a4aaa9617 --- /dev/null +++ b/examples/cosmwasm_vm_execution/readme.md @@ -0,0 +1,15 @@ +**A full workflow example:** + +1. add fixtures/config.yml to the ROOCH_CONFIG environment variable +2. Start a local server: +``` + rooch server start +``` +3. Open another terminal, publish the `cosmwasm_vm_execution` module: +``` + rooch move publish -p ../../examples/cosmwasm_vm_execution/ --sender-account 0x123 +``` +4. Run `0x123::cosmwasm_vm_execution::run` to execute a wasm function +``` + rooch move run --function 0x123::cosmwasm_vm_execution::run --sender-account 0x123 +``` diff --git a/examples/cosmwasm_vm_execution/sources/wasm.move b/examples/cosmwasm_vm_execution/sources/wasm.move new file mode 100644 index 0000000000..7cd44dc7ec --- /dev/null +++ b/examples/cosmwasm_vm_execution/sources/wasm.move @@ -0,0 +1,136 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +module rooch_examples::cosmwasm_vm_execution { + use std::debug; + use std::string; + use std::vector; + use std::option; + + use moveos_std::result; + use rooch_nursery::cosmwasm_std; + use rooch_nursery::cosmwasm_vm; + + #[data_struct] + struct InstantiateMsg has store, copy, drop { + initial_value: u64 + } + + #[data_struct] + struct Add has store, copy, drop { + value: u64 + } + + #[data_struct] + struct GetValue has store, copy, drop {} + + #[data_struct] + struct ExecuteMsg has store, copy, drop { + add: Add + } + + #[data_struct] + struct QueryMsg has store, copy, drop { + get_value: GetValue + } + + #[data_struct] + struct MigreateMsg has store, copy, drop { + new_value: u64 + } + + #[data_struct] + struct UpdateValue has store, copy, drop { + value: u64 + } + + #[data_struct] + struct SudoMsg has store, copy, drop { + update_value: UpdateValue + } + + entry public fun run_cosmwasm_example(_account: &signer, wasm_bytes: vector) { + debug::print(&string::utf8(b"run_cosmwasm_example start")); + + // 1. create wasm VM instance (required step) + let instance_result = cosmwasm_vm::from_code(wasm_bytes); + debug::print(&string::utf8(b"instance_result:")); + debug::print(&instance_result); + + // Verify that the result is successful + let instance = result::assert_ok(instance_result, 1); // Use assert_ok here + + // 2. Verify some properties of the instance + // Note: The specific verification method may need to be adjusted based on the actual definition of the Instance structure + assert!(vector::length(&cosmwasm_vm::code_checksum(&instance)) > 0, 2); + + let env = cosmwasm_std::current_env(); + let info = cosmwasm_std::current_message_info(); + + // 3. call instantiate of the instance + let msg = InstantiateMsg{ + initial_value: 1 + }; + let instantiate_result = cosmwasm_vm::call_instantiate(&mut instance, &env, &info, &msg); + let instantiate_resp = result::assert_ok(instantiate_result, 3); // Use assert_ok here + debug::print(&string::utf8(b"instantiate_resp:")); + debug::print(&instantiate_resp); + + // 4. call execute of the instance + let msg = ExecuteMsg{ + add: Add { + value: 1 + } + }; + + let execute_result = cosmwasm_vm::call_execute(&mut instance, &env, &info, &msg); + let execute_resp = result::assert_ok(execute_result, 4); // Use assert_ok here + debug::print(&string::utf8(b"execute_resp:")); + debug::print(&execute_resp); + + // 5. call query of the instance + let msg = QueryMsg{ + get_value: GetValue {} + }; + + let query_result = cosmwasm_vm::call_query(&instance, &env, &msg); + let query_resp = result::assert_ok(query_result, 5); // Use assert_ok here + debug::print(&string::utf8(b"query_resp:")); + debug::print(&query_resp); + + // 6. call migrate of the instance + let msg = MigreateMsg{ + new_value: 2 + }; + + let migrate_result = cosmwasm_vm::call_migrate(&mut instance, &env, &msg); + let migrate_resp = result::assert_ok(migrate_result, 6); // Use assert_ok here + debug::print(&string::utf8(b"migrate_resp:")); + debug::print(&migrate_resp); + + // 7. call reply of the instance + let resp = cosmwasm_std::new_sub_msg_response(); + let msg = cosmwasm_std::new_reply(1, cosmwasm_std::new_binary(b"hello"), 10, resp); + + let reply_result = cosmwasm_vm::call_reply(&mut instance, &env, &msg); + let reply_resp = result::assert_ok(reply_result, 7); // Use assert_ok here + debug::print(&string::utf8(b"reply_resp:")); + debug::print(&reply_resp); + + // 8. call sudo of the instance + let msg = SudoMsg{ + update_value: UpdateValue { + value: 2 + } + }; + + let sudo_result = cosmwasm_vm::call_sudo(&mut instance, &env, &msg); + let sudo_resp = result::assert_ok(sudo_result, 8); // Use assert_ok here + debug::print(&string::utf8(b"sudo_resp:")); + debug::print(&sudo_resp); + + // 9. Destroy the instance + let destroy_result = cosmwasm_vm::destroy_instance(instance); + assert!(option::is_none(&destroy_result), 9); + } +} diff --git a/examples/cosmwasm_vm_execution/src/lib.rs b/examples/cosmwasm_vm_execution/src/lib.rs new file mode 100644 index 0000000000..c3830354f1 --- /dev/null +++ b/examples/cosmwasm_vm_execution/src/lib.rs @@ -0,0 +1,247 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_std::{ + entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, + StdResult, SubMsgResponse, SubMsgResult, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct InstantiateMsg { + initial_value: u64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + Add { value: u64 }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + GetValue {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct ValueResponse { + value: u64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct MigrateMsg { + new_value: u64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum SudoMsg { + UpdateValue { value: u64 }, +} + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + deps.storage.set(b"value", &msg.initial_value.to_be_bytes()); + Ok(Response::default()) +} + +#[entry_point] +pub fn execute( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + match msg { + ExecuteMsg::Add { value } => { + let current_value = deps + .storage + .get(b"value") + .map(|bytes| u64::from_be_bytes(bytes.try_into().unwrap_or([0; 8]))) + .unwrap_or(0); + let new_value = current_value + value; + deps.storage.set(b"value", &new_value.to_be_bytes()); + Ok(Response::default()) + } + } +} + +#[entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetValue {} => { + let value = deps + .storage + .get(b"value") + .map(|bytes| u64::from_be_bytes(bytes.try_into().unwrap_or([0; 8]))) + .unwrap_or(0); + to_json_binary(&value) + } + } +} + +#[entry_point] +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult { + deps.storage.set(b"value", &msg.new_value.to_be_bytes()); + Ok(Response::new() + .add_attribute("action", "migrate") + .add_attribute("new_value", msg.new_value.to_string())) +} + +#[entry_point] +pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> StdResult { + Ok(Response::new() + .add_attribute("action", "reply") + .add_attribute("id", msg.id.to_string())) +} + +#[entry_point] +pub fn sudo(deps: DepsMut, _env: Env, msg: SudoMsg) -> StdResult { + match msg { + SudoMsg::UpdateValue { value } => { + deps.storage.set(b"value", &value.to_be_bytes()); + Ok(Response::new() + .add_attribute("action", "sudo_update_value") + .add_attribute("new_value", value.to_string())) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::from_json; + use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env}; + + #[test] + fn test_instantiate() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + let creator = deps.api.addr_make("creator"); + let info = message_info(&creator, &[]); + let msg = InstantiateMsg { initial_value: 100 }; + + let res = instantiate(deps.as_mut(), env, info, msg).unwrap(); + assert_eq!(res.messages.len(), 0); + + let value: u64 = + from_json(query(deps.as_ref(), mock_env(), QueryMsg::GetValue {}).unwrap()).unwrap(); + assert_eq!(value, 100); + } + + #[test] + fn test_execute_add() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let creator = deps.api.addr_make("creator"); + let info = message_info(&creator, &[]); + let init_msg = InstantiateMsg { initial_value: 100 }; + instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap(); + + let exec_msg = ExecuteMsg::Add { value: 50 }; + let res = execute(deps.as_mut(), env.clone(), info.clone(), exec_msg).unwrap(); + assert_eq!(res.messages.len(), 0); + + let value: u64 = + from_json(query(deps.as_ref(), mock_env(), QueryMsg::GetValue {}).unwrap()).unwrap(); + assert_eq!(value, 150); + } + + #[test] + fn test_query_value() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let creator = deps.api.addr_make("creator"); + let info = message_info(&creator, &[]); + let init_msg = InstantiateMsg { initial_value: 100 }; + instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap(); + + let value: u64 = + from_json(query(deps.as_ref(), mock_env(), QueryMsg::GetValue {}).unwrap()).unwrap(); + assert_eq!(value, 100); + } + + #[test] + fn test_migrate() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let creator = deps.api.addr_make("creator"); + let info = message_info(&creator, &[]); + let init_msg = InstantiateMsg { initial_value: 100 }; + instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap(); + + let migrate_msg = MigrateMsg { new_value: 200 }; + let res = migrate(deps.as_mut(), env.clone(), migrate_msg).unwrap(); + assert_eq!(res.attributes.len(), 2); + + let value: u64 = + from_json(query(deps.as_ref(), mock_env(), QueryMsg::GetValue {}).unwrap()).unwrap(); + assert_eq!(value, 200); + } + + #[test] + #[allow(deprecated)] + fn test_reply() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + let result = SubMsgResult::Ok(SubMsgResponse { + data: None, + msg_responses: vec![], + events: vec![], + }); + + let reply_msg = Reply { + id: 1, + payload: Binary::new(vec![1, 2, 3]), + gas_used: 0, + result, + }; + let res = reply(deps.as_mut(), env, reply_msg).unwrap(); + + assert_eq!(res.attributes.len(), 2); + assert_eq!(res.attributes[0].key, "action"); + assert_eq!(res.attributes[0].value, "reply"); + assert_eq!(res.attributes[1].key, "id"); + assert_eq!(res.attributes[1].value, "1"); + } + + #[test] + fn test_sudo() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let creator = deps.api.addr_make("creator"); + let info = message_info(&creator, &[]); + let init_msg = InstantiateMsg { initial_value: 100 }; + instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap(); + + let sudo_msg = SudoMsg::UpdateValue { value: 300 }; + let res = sudo(deps.as_mut(), env.clone(), sudo_msg).unwrap(); + assert_eq!(res.attributes.len(), 2); + + let value: u64 = + from_json(query(deps.as_ref(), mock_env(), QueryMsg::GetValue {}).unwrap()).unwrap(); + assert_eq!(value, 300); + } + + #[test] + fn test_parse_env_json() { + let env_json = "{\"block\":{\"height\":12345,\"time\":\"1571797419879305533\",\"chain_id\":\"cosmos-testnet-14002\"},\"contract\":{\"address\":\"cosmos2contract\"},\"transaction\":{\"index\":3}}"; + let env: Env = from_json(env_json).unwrap(); + assert_eq!(env.block.height, 12345); + } + + #[test] + fn test_parse_reply_json() { + let reply_json = "{\"id\":1,\"payload\":\"aGVsbG8=\",\"gas_used\":10,\"result\":{\"ok\":{\"events\":[],\"msg_responses\":[]}}}"; + let reply: Reply = from_json(reply_json).unwrap(); + assert_eq!(reply.id, 1); + } +} diff --git a/examples/cosmwasm_vm_execution/web/README.md b/examples/cosmwasm_vm_execution/web/README.md new file mode 100644 index 0000000000..316b7094a7 --- /dev/null +++ b/examples/cosmwasm_vm_execution/web/README.md @@ -0,0 +1,5 @@ +# Counter Web Example + +Please visit the following path to view the Counter Web Example project: + +- `sdk/typescript/examples/counter` diff --git a/frameworks/moveos-stdlib/Cargo.toml b/frameworks/moveos-stdlib/Cargo.toml index e7841815f5..7e0070bedf 100644 --- a/frameworks/moveos-stdlib/Cargo.toml +++ b/frameworks/moveos-stdlib/Cargo.toml @@ -27,6 +27,7 @@ rlp = { workspace = true } primitive-types = { workspace = true } bech32 = { workspace = true } bs58 = { workspace = true, features = ["check"] } +base64 = { workspace = true } revm-precompile = { workspace = true } revm-primitives = { workspace = true } ripemd = { workspace = true } diff --git a/frameworks/moveos-stdlib/doc/README.md b/frameworks/moveos-stdlib/doc/README.md index 777e26f37c..4644fb5193 100644 --- a/frameworks/moveos-stdlib/doc/README.md +++ b/frameworks/moveos-stdlib/doc/README.md @@ -17,6 +17,7 @@ This is the reference documentation of the MoveOS standard library. - [`0x2::any`](any.md#0x2_any) - [`0x2::bag`](bag.md#0x2_bag) - [`0x2::base58`](base58.md#0x2_base58) +- [`0x2::base64`](base64.md#0x2_base64) - [`0x2::bcs`](bcs.md#0x2_bcs) - [`0x2::bech32`](bech32.md#0x2_bech32) - [`0x2::big_vector`](big_vector.md#0x2_big_vector) diff --git a/frameworks/moveos-stdlib/doc/base64.md b/frameworks/moveos-stdlib/doc/base64.md new file mode 100644 index 0000000000..22fe70bcb7 --- /dev/null +++ b/frameworks/moveos-stdlib/doc/base64.md @@ -0,0 +1,54 @@ + + + +# Module `0x2::base64` + +Module which defines base64 functions. + + +- [Constants](#@Constants_0) +- [Function `encode`](#0x2_base64_encode) +- [Function `decode`](#0x2_base64_decode) + + +
+ + + + + +## Constants + + + + + + +
const E_DECODE_FAILED: u64 = 1;
+
+ + + + + +## Function `encode` + +@param input: bytes to be encoded +Encode the input bytes with Base64 algorithm and returns an encoded base64 string + + +
public fun encode(input: &vector<u8>): vector<u8>
+
+ + + + + +## Function `decode` + +@param encoded_input: encoded base64 string +Decode the base64 string and returns the original bytes + + +
public fun decode(encoded_input: &vector<u8>): vector<u8>
+
diff --git a/frameworks/moveos-stdlib/sources/base64.move b/frameworks/moveos-stdlib/sources/base64.move new file mode 100644 index 0000000000..60cd4137ae --- /dev/null +++ b/frameworks/moveos-stdlib/sources/base64.move @@ -0,0 +1,36 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + + +/// Module which defines base64 functions. +module moveos_std::base64 { + + // Decode failed error + const E_DECODE_FAILED: u64 = 1; + + /// @param input: bytes to be encoded + /// Encode the input bytes with Base64 algorithm and returns an encoded base64 string + native public fun encode(input: &vector): vector; + + /// @param encoded_input: encoded base64 string + /// Decode the base64 string and returns the original bytes + native public fun decode(encoded_input: &vector): vector; + + #[test] + fun test_encode() { + let input = b"Hello, World!"; + let encoded = encode(&input); + let expected = b"SGVsbG8sIFdvcmxkIQ=="; + + assert!(encoded == expected, 1000); + } + + #[test] + fun test_decode() { + let encoded_input = b"SGVsbG8sIFdvcmxkIQ=="; + let decoded = decode(&encoded_input); + let expected = b"Hello, World!"; + + assert!(decoded == expected, E_DECODE_FAILED); + } +} \ No newline at end of file diff --git a/frameworks/moveos-stdlib/src/natives/mod.rs b/frameworks/moveos-stdlib/src/natives/mod.rs index 89253364d0..5202e01b9f 100644 --- a/frameworks/moveos-stdlib/src/natives/mod.rs +++ b/frameworks/moveos-stdlib/src/natives/mod.rs @@ -25,6 +25,7 @@ pub struct GasParameters { pub cbor: moveos_stdlib::cbor::GasParameters, pub tx_context: moveos_stdlib::tx_context::GasParameters, pub base58: moveos_stdlib::base58::GasParameters, + pub base64: moveos_stdlib::base64::GasParameters, pub bech32: moveos_stdlib::bech32::GasParameters, pub hash: moveos_stdlib::hash::GasParameters, pub bls12381: moveos_stdlib::bls12381::GasParameters, @@ -50,6 +51,7 @@ impl GasParameters { cbor: moveos_stdlib::cbor::GasParameters::zeros(), tx_context: moveos_stdlib::tx_context::GasParameters::zeros(), base58: moveos_stdlib::base58::GasParameters::zeros(), + base64: moveos_stdlib::base64::GasParameters::zeros(), bech32: moveos_stdlib::bech32::GasParameters::zeros(), hash: moveos_stdlib::hash::GasParameters::zeros(), bls12381: moveos_stdlib::bls12381::GasParameters::zeros(), @@ -121,6 +123,7 @@ pub fn all_natives(gas_params: GasParameters) -> NativeFunctionTable { moveos_stdlib::tx_context::make_all(gas_params.tx_context) ); add_natives!("base58", moveos_stdlib::base58::make_all(gas_params.base58)); + add_natives!("base64", moveos_stdlib::base64::make_all(gas_params.base64)); add_natives!("bech32", moveos_stdlib::bech32::make_all(gas_params.bech32)); add_natives!("hash", moveos_stdlib::hash::make_all(gas_params.hash)); add_natives!( diff --git a/frameworks/moveos-stdlib/src/natives/moveos_stdlib/base64.rs b/frameworks/moveos-stdlib/src/natives/moveos_stdlib/base64.rs new file mode 100644 index 0000000000..a9b4904451 --- /dev/null +++ b/frameworks/moveos-stdlib/src/natives/moveos_stdlib/base64.rs @@ -0,0 +1,120 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use crate::natives::helpers::{make_module_natives, make_native}; +use base64::{engine::general_purpose, Engine as _}; +use move_binary_format::errors::PartialVMResult; +use move_core_types::gas_algebra::{InternalGas, InternalGasPerByte, NumBytes}; +use move_vm_runtime::native_functions::{NativeContext, NativeFunction}; +use move_vm_types::{ + loaded_data::runtime_types::Type, + natives::function::NativeResult, + pop_arg, + values::{Value, VectorRef}, +}; +use smallvec::smallvec; +use std::collections::VecDeque; + +pub const E_DECODE_FAILED: u64 = 1; + +/*************************************************************************************************** + * native fun encode + **************************************************************************************************/ +pub fn native_encode( + gas_params: &EncodeDecodeGasParametersOption, + _context: &mut NativeContext, + ty_args: Vec, + mut args: VecDeque, +) -> PartialVMResult { + debug_assert!(ty_args.is_empty()); + debug_assert!(args.len() == 1); + + let input = pop_arg!(args, VectorRef); + + let cost = gas_params.base.unwrap_or_else(InternalGas::zero) + + (gas_params.per_byte.unwrap_or_else(InternalGasPerByte::zero) + * NumBytes::new(input.as_bytes_ref().len() as u64)); + + let encoded = general_purpose::STANDARD.encode(input.as_bytes_ref().to_vec()); + + Ok(NativeResult::ok( + cost, + smallvec![Value::vector_u8(encoded.into_bytes())], + )) +} + +/*************************************************************************************************** + * native fun decode + **************************************************************************************************/ +pub fn native_decode( + gas_params: &EncodeDecodeGasParametersOption, + _context: &mut NativeContext, + ty_args: Vec, + mut args: VecDeque, +) -> PartialVMResult { + debug_assert!(ty_args.is_empty()); + debug_assert!(args.len() == 1); + + let encoded_input = pop_arg!(args, VectorRef); + + let cost = gas_params.base.unwrap_or_else(InternalGas::zero) + + (gas_params.per_byte.unwrap_or_else(InternalGasPerByte::zero) + * NumBytes::new(encoded_input.as_bytes_ref().len() as u64)); + + let decoded = match general_purpose::STANDARD.decode(encoded_input.as_bytes_ref().to_vec()) { + Ok(bytes) => bytes, + Err(_) => return Ok(NativeResult::err(cost, E_DECODE_FAILED)), + }; + + Ok(NativeResult::ok(cost, smallvec![Value::vector_u8(decoded)])) +} + +/*************************************************************************************************** + * gas parameter structs + **************************************************************************************************/ +#[derive(Debug, Clone)] +pub struct EncodeDecodeGasParametersOption { + pub base: Option, + pub per_byte: Option, +} + +#[derive(Debug, Clone)] +pub struct GasParameters { + pub encode: EncodeDecodeGasParametersOption, + pub decode: EncodeDecodeGasParametersOption, +} + +impl GasParameters { + pub fn zeros() -> Self { + Self { + encode: EncodeDecodeGasParametersOption { + base: Some(0.into()), + per_byte: Some(0.into()), + }, + decode: EncodeDecodeGasParametersOption { + base: Some(0.into()), + per_byte: Some(0.into()), + }, + } + } +} + +pub fn make_all(gas_params: GasParameters) -> impl Iterator { + let mut natives = Vec::new(); + + if gas_params.encode.base.is_some() || gas_params.encode.per_byte.is_some() { + natives.push(( + "encode".to_string(), + make_native(gas_params.encode, native_encode), + )); + } + + if gas_params.decode.base.is_some() || gas_params.decode.per_byte.is_some() { + natives.push(( + "decode".to_string(), + make_native(gas_params.decode, native_decode), + )); + } + + make_module_natives(natives) +} diff --git a/frameworks/moveos-stdlib/src/natives/moveos_stdlib/json.rs b/frameworks/moveos-stdlib/src/natives/moveos_stdlib/json.rs index 4aa33548c0..b3dcc82476 100644 --- a/frameworks/moveos-stdlib/src/natives/moveos_stdlib/json.rs +++ b/frameworks/moveos-stdlib/src/natives/moveos_stdlib/json.rs @@ -81,6 +81,27 @@ fn parse_struct_value_from_json( Ok(Struct::pack(vec![Value::vector_u8( str_value.as_bytes().to_vec(), )])) + } else if is_std_option(struct_type, &MOVE_STD_ADDRESS) { + let vec_layout = fields + .first() + .ok_or_else(|| anyhow::anyhow!("Invalid std option layout"))?; + let type_tag: TypeTag = (&vec_layout.layout).try_into()?; + let ty = context.load_type(&type_tag)?; + + if json_value.is_null() { + let value = Vector::pack(&ty, vec![])?; + return Ok(Struct::pack(vec![value])); + } + + if let MoveTypeLayout::Vector(vec_layout) = vec_layout.layout.clone() { + let struct_layout = vec_layout.as_ref(); + let move_struct_value = + parse_move_value_from_json(struct_layout, json_value, context)?; + let value = Vector::pack(&ty, vec![move_struct_value])?; + return Ok(Struct::pack(vec![value])); + } + + Err(anyhow::anyhow!("Invalid std option layout")) } else if struct_type == &SimpleMap::::struct_tag() { let key_value_pairs = json_obj_to_key_value_pairs(json_value)?; let mut key_values = Vec::new(); @@ -101,9 +122,22 @@ fn parse_struct_value_from_json( .iter() .map(|field| -> Result { let name = field.name.as_str(); - let json_field = json_value.get(name).ok_or_else(|| { - anyhow::anyhow!("type: {}, Missing field {}", struct_type, name) - })?; + let json_field = match json_value.get(name) { + Some(value) => value, + None => { + if let MoveTypeLayout::Struct(_) | MoveTypeLayout::Vector(_) = + field.layout + { + &JsonValue::Null + } else { + return Err(anyhow::anyhow!( + "type: {}, Missing field {}", + struct_type, + name + )); + } + } + }; parse_move_value_from_json(&field.layout, json_field, context) }) .collect::>>()?; @@ -156,6 +190,12 @@ fn parse_move_value_from_json( Ok(Value::address(addr)) } MoveTypeLayout::Vector(item_layout) => { + if json_value.is_null() { + let type_tag: TypeTag = (&**item_layout).try_into()?; + let ty = context.load_type(&type_tag)?; + return Ok(Vector::pack(&ty, vec![])?); + } + let vec_value = json_value .as_array() .ok_or_else(|| anyhow::anyhow!("Invalid vector value"))? @@ -545,7 +585,10 @@ fn serialize_move_fields_to_json( for (field_layout, (name, value)) in layout_fields.iter().zip(value_fields) { let json_value = serialize_move_value_to_json(&field_layout.layout, value)?; - fields.insert(name.clone().into_string(), json_value); + + if !json_value.is_null() { + fields.insert(name.clone().into_string(), json_value); + } } Ok(JsonValue::Object(fields)) diff --git a/frameworks/moveos-stdlib/src/natives/moveos_stdlib/mod.rs b/frameworks/moveos-stdlib/src/natives/moveos_stdlib/mod.rs index c7cda3776d..393f405ebc 100644 --- a/frameworks/moveos-stdlib/src/natives/moveos_stdlib/mod.rs +++ b/frameworks/moveos-stdlib/src/natives/moveos_stdlib/mod.rs @@ -3,6 +3,7 @@ pub mod account; pub mod base58; +pub mod base64; pub mod bcs; pub mod bech32; pub mod bls12381; diff --git a/frameworks/rooch-framework/src/natives/gas_parameter/base64.rs b/frameworks/rooch-framework/src/natives/gas_parameter/base64.rs new file mode 100644 index 0000000000..8480c58817 --- /dev/null +++ b/frameworks/rooch-framework/src/natives/gas_parameter/base64.rs @@ -0,0 +1,12 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use crate::natives::gas_parameter::native::MUL; +use moveos_stdlib::natives::moveos_stdlib::base64::GasParameters; + +crate::natives::gas_parameter::native::define_gas_parameters_for_natives!(GasParameters, "base64", [ + [.encode.base, optional "encode.base", 1000 * MUL], + [.encode.per_byte, optional "encode.per_byte", 30 * MUL], + [.decode.base, optional "decode.base", 1000 * MUL], + [.decode.per_byte, optional "decode.per_byte", 30 * MUL], +]); diff --git a/frameworks/rooch-framework/src/natives/gas_parameter/mod.rs b/frameworks/rooch-framework/src/natives/gas_parameter/mod.rs index e27392a463..7b1f2e76bd 100644 --- a/frameworks/rooch-framework/src/natives/gas_parameter/mod.rs +++ b/frameworks/rooch-framework/src/natives/gas_parameter/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 mod account; mod base58; +mod base64; mod bcs; mod bech32; mod bitcoin_address; diff --git a/frameworks/rooch-framework/src/natives/mod.rs b/frameworks/rooch-framework/src/natives/mod.rs index f63f20938c..bec122006d 100644 --- a/frameworks/rooch-framework/src/natives/mod.rs +++ b/frameworks/rooch-framework/src/natives/mod.rs @@ -83,6 +83,7 @@ impl FromOnChainGasSchedule for MoveOSStdlibGasParameters { cbor: FromOnChainGasSchedule::from_on_chain_gas_schedule(gas_schedule).unwrap(), tx_context: FromOnChainGasSchedule::from_on_chain_gas_schedule(gas_schedule).unwrap(), base58: FromOnChainGasSchedule::from_on_chain_gas_schedule(gas_schedule).unwrap(), + base64: FromOnChainGasSchedule::from_on_chain_gas_schedule(gas_schedule).unwrap(), bech32: FromOnChainGasSchedule::from_on_chain_gas_schedule(gas_schedule).unwrap(), hash: FromOnChainGasSchedule::from_on_chain_gas_schedule(gas_schedule).unwrap(), bls12381: FromOnChainGasSchedule::from_on_chain_gas_schedule(gas_schedule).unwrap(), @@ -109,6 +110,7 @@ impl ToOnChainGasSchedule for MoveOSStdlibGasParameters { entires.extend(self.cbor.to_on_chain_gas_schedule()); entires.extend(self.tx_context.to_on_chain_gas_schedule()); entires.extend(self.base58.to_on_chain_gas_schedule()); + entires.extend(self.base64.to_on_chain_gas_schedule()); entires.extend(self.bech32.to_on_chain_gas_schedule()); entires.extend(self.hash.to_on_chain_gas_schedule()); entires.extend(self.bls12381.to_on_chain_gas_schedule()); @@ -136,6 +138,7 @@ impl InitialGasSchedule for MoveOSStdlibGasParameters { cbor: InitialGasSchedule::initial(), tx_context: InitialGasSchedule::initial(), base58: InitialGasSchedule::initial(), + base64: InitialGasSchedule::initial(), bech32: InitialGasSchedule::initial(), hash: InitialGasSchedule::initial(), bls12381: InitialGasSchedule::initial(), diff --git a/frameworks/rooch-nursery/Cargo.toml b/frameworks/rooch-nursery/Cargo.toml index 27b3af2951..edae54fb1b 100644 --- a/frameworks/rooch-nursery/Cargo.toml +++ b/frameworks/rooch-nursery/Cargo.toml @@ -25,6 +25,9 @@ serde_json = { workspace = true } ciborium = { workspace = true } wasmer = { workspace = true } libc = { workspace = true } +cosmwasm-vm = { workspace = true } +cosmwasm-std = { workspace = true } +once_cell = { workspace = true } move-binary-format = { workspace = true } move-core-types = { workspace = true } @@ -37,6 +40,8 @@ moveos-types = { workspace = true } moveos-stdlib = { workspace = true } moveos = { workspace = true } moveos-wasm = { workspace = true } +moveos-object-runtime = { workspace = true } +rooch-cosmwasm-vm = { workspace = true } rooch-types = { workspace = true } rooch-framework = { workspace = true } \ No newline at end of file diff --git a/frameworks/rooch-nursery/doc/README.md b/frameworks/rooch-nursery/doc/README.md index 135533926b..4a16720a6d 100644 --- a/frameworks/rooch-nursery/doc/README.md +++ b/frameworks/rooch-nursery/doc/README.md @@ -14,6 +14,8 @@ This is the reference documentation of the Rooch Nursery Framework. - [`0xa::bitseed`](bitseed.md#0xa_bitseed) - [`0xa::brc20`](brc20.md#0xa_brc20) +- [`0xa::cosmwasm_std`](cosmwasm_std.md#0xa_cosmwasm_std) +- [`0xa::cosmwasm_vm`](cosmwasm_vm.md#0xa_cosmwasm_vm) - [`0xa::ethereum`](ethereum.md#0xa_ethereum) - [`0xa::ethereum_validator`](ethereum_validator.md#0xa_ethereum_validator) - [`0xa::genesis`](genesis.md#0xa_genesis) diff --git a/frameworks/rooch-nursery/doc/cosmwasm_std.md b/frameworks/rooch-nursery/doc/cosmwasm_std.md new file mode 100644 index 0000000000..9b619a3c99 --- /dev/null +++ b/frameworks/rooch-nursery/doc/cosmwasm_std.md @@ -0,0 +1,522 @@ + + + +# Module `0xa::cosmwasm_std` + + + +- [Struct `Coin`](#0xa_cosmwasm_std_Coin) +- [Struct `BlockInfo`](#0xa_cosmwasm_std_BlockInfo) +- [Struct `TransactionInfo`](#0xa_cosmwasm_std_TransactionInfo) +- [Struct `ContractInfo`](#0xa_cosmwasm_std_ContractInfo) +- [Struct `Env`](#0xa_cosmwasm_std_Env) +- [Struct `MessageInfo`](#0xa_cosmwasm_std_MessageInfo) +- [Struct `Attribute`](#0xa_cosmwasm_std_Attribute) +- [Struct `Event`](#0xa_cosmwasm_std_Event) +- [Struct `Response`](#0xa_cosmwasm_std_Response) +- [Struct `SubMsg`](#0xa_cosmwasm_std_SubMsg) +- [Struct `Error`](#0xa_cosmwasm_std_Error) +- [Struct `MsgResponse`](#0xa_cosmwasm_std_MsgResponse) +- [Struct `SubMsgResponse`](#0xa_cosmwasm_std_SubMsgResponse) +- [Struct `SubMsgResult`](#0xa_cosmwasm_std_SubMsgResult) +- [Struct `Reply`](#0xa_cosmwasm_std_Reply) +- [Struct `ReplyOn`](#0xa_cosmwasm_std_ReplyOn) +- [Struct `StdResult`](#0xa_cosmwasm_std_StdResult) +- [Constants](#@Constants_0) +- [Function `new_response`](#0xa_cosmwasm_std_new_response) +- [Function `new_sub_msg_response`](#0xa_cosmwasm_std_new_sub_msg_response) +- [Function `new_sub_msg_error`](#0xa_cosmwasm_std_new_sub_msg_error) +- [Function `add_attribute`](#0xa_cosmwasm_std_add_attribute) +- [Function `add_event`](#0xa_cosmwasm_std_add_event) +- [Function `set_data`](#0xa_cosmwasm_std_set_data) +- [Function `add_message`](#0xa_cosmwasm_std_add_message) +- [Function `new_coin`](#0xa_cosmwasm_std_new_coin) +- [Function `new_sub_msg`](#0xa_cosmwasm_std_new_sub_msg) +- [Function `new_error`](#0xa_cosmwasm_std_new_error) +- [Function `new_error_result`](#0xa_cosmwasm_std_new_error_result) +- [Function `new_reply`](#0xa_cosmwasm_std_new_reply) +- [Function `serialize_env`](#0xa_cosmwasm_std_serialize_env) +- [Function `serialize_message_info`](#0xa_cosmwasm_std_serialize_message_info) +- [Function `serialize_message`](#0xa_cosmwasm_std_serialize_message) +- [Function `deserialize_stdresult`](#0xa_cosmwasm_std_deserialize_stdresult) +- [Function `new_binary`](#0xa_cosmwasm_std_new_binary) +- [Function `current_chain`](#0xa_cosmwasm_std_current_chain) +- [Function `current_env`](#0xa_cosmwasm_std_current_env) +- [Function `current_message_info`](#0xa_cosmwasm_std_current_message_info) + + +
use 0x1::option;
+use 0x1::string;
+use 0x2::base64;
+use 0x2::json;
+use 0x2::result;
+use 0x2::timestamp;
+use 0x2::tx_context;
+use 0x3::chain_id;
+
+ + + + + +## Struct `Coin` + + + +
#[data_struct]
+struct Coin has copy, drop, store
+
+ + + + + +## Struct `BlockInfo` + + + +
#[data_struct]
+struct BlockInfo has copy, drop, store
+
+ + + + + +## Struct `TransactionInfo` + + + +
#[data_struct]
+struct TransactionInfo has copy, drop, store
+
+ + + + + +## Struct `ContractInfo` + + + +
#[data_struct]
+struct ContractInfo has copy, drop, store
+
+ + + + + +## Struct `Env` + + + +
#[data_struct]
+struct Env has copy, drop, store
+
+ + + + + +## Struct `MessageInfo` + + + +
#[data_struct]
+struct MessageInfo has copy, drop, store
+
+ + + + + +## Struct `Attribute` + + + +
#[data_struct]
+struct Attribute has copy, drop, store
+
+ + + + + +## Struct `Event` + + + +
#[data_struct]
+struct Event has copy, drop, store
+
+ + + + + +## Struct `Response` + + + +
#[data_struct]
+struct Response has copy, drop, store
+
+ + + + + +## Struct `SubMsg` + + + +
#[data_struct]
+struct SubMsg has copy, drop, store
+
+ + + + + +## Struct `Error` + + + +
#[data_struct]
+struct Error has copy, drop, store
+
+ + + + + +## Struct `MsgResponse` + + + +
#[data_struct]
+struct MsgResponse has copy, drop, store
+
+ + + + + +## Struct `SubMsgResponse` + + + +
#[data_struct]
+struct SubMsgResponse has copy, drop, store
+
+ + + + + +## Struct `SubMsgResult` + + + +
#[data_struct]
+struct SubMsgResult has copy, drop, store
+
+ + + + + +## Struct `Reply` + + + +
#[data_struct]
+struct Reply has copy, drop, store
+
+ + + + + +## Struct `ReplyOn` + + + +
#[data_struct]
+struct ReplyOn has copy, drop, store
+
+ + + + + +## Struct `StdResult` + + + +
#[data_struct]
+struct StdResult has copy, drop
+
+ + + + + +## Constants + + + + +This error code is returned when a deserialization error occurs. + + +
const ErrorDeserialize: u32 = 1;
+
+ + + + + + + +
const REPLY_ALWAYS: u8 = 3;
+
+ + + + + + + +
const REPLY_ON_ERROR: u8 = 2;
+
+ + + + + + + +
const REPLY_ON_SUCCESS: u8 = 1;
+
+ + + + + +## Function `new_response` + + + +
public fun new_response(): cosmwasm_std::Response
+
+ + + + + +## Function `new_sub_msg_response` + + + +
public fun new_sub_msg_response(): cosmwasm_std::SubMsgResult
+
+ + + + + +## Function `new_sub_msg_error` + + + +
public fun new_sub_msg_error(err: string::String): cosmwasm_std::SubMsgResult
+
+ + + + + +## Function `add_attribute` + + + +
public fun add_attribute(response: &mut cosmwasm_std::Response, key: string::String, value: string::String)
+
+ + + + + +## Function `add_event` + + + +
public fun add_event(response: &mut cosmwasm_std::Response, event: cosmwasm_std::Event)
+
+ + + + + +## Function `set_data` + + + +
public fun set_data(response: &mut cosmwasm_std::Response, data: vector<u8>)
+
+ + + + + +## Function `add_message` + + + +
public fun add_message(response: &mut cosmwasm_std::Response, msg: cosmwasm_std::SubMsg)
+
+ + + + + +## Function `new_coin` + + + +
public fun new_coin(denom: string::String, amount: u128): cosmwasm_std::Coin
+
+ + + + + +## Function `new_sub_msg` + + + +
public fun new_sub_msg(id: u64, msg: vector<u8>, gas_limit: option::Option<u64>, reply_on: u8): cosmwasm_std::SubMsg
+
+ + + + + +## Function `new_error` + + + +
public fun new_error(code: u32, message: string::String): cosmwasm_std::Error
+
+ + + + + +## Function `new_error_result` + + + +
public fun new_error_result<T>(code: u32, message: string::String): result::Result<T, cosmwasm_std::Error>
+
+ + + + + +## Function `new_reply` + + + +
public fun new_reply(id: u64, payload: string::String, gas_used: u64, result: cosmwasm_std::SubMsgResult): cosmwasm_std::Reply
+
+ + + + + +## Function `serialize_env` + + + +
public fun serialize_env(env: &cosmwasm_std::Env): vector<u8>
+
+ + + + + +## Function `serialize_message_info` + + + +
public fun serialize_message_info(info: &cosmwasm_std::MessageInfo): vector<u8>
+
+ + + + + +## Function `serialize_message` + + + +
public fun serialize_message<T: drop>(msg: &T): vector<u8>
+
+ + + + + +## Function `deserialize_stdresult` + + + +
public fun deserialize_stdresult(raw: vector<u8>): result::Result<cosmwasm_std::Response, cosmwasm_std::Error>
+
+ + + + + +## Function `new_binary` + + + +
public fun new_binary(data: vector<u8>): string::String
+
+ + + + + +## Function `current_chain` + + + +
public fun current_chain(): vector<u8>
+
+ + + + + +## Function `current_env` + + + +
public fun current_env(): cosmwasm_std::Env
+
+ + + + + +## Function `current_message_info` + + + +
public fun current_message_info(): cosmwasm_std::MessageInfo
+
diff --git a/frameworks/rooch-nursery/doc/cosmwasm_vm.md b/frameworks/rooch-nursery/doc/cosmwasm_vm.md new file mode 100644 index 0000000000..078eddf12d --- /dev/null +++ b/frameworks/rooch-nursery/doc/cosmwasm_vm.md @@ -0,0 +1,155 @@ + + + +# Module `0xa::cosmwasm_vm` + + + +- [Resource `Instance`](#0xa_cosmwasm_vm_Instance) +- [Function `code_checksum`](#0xa_cosmwasm_vm_code_checksum) +- [Function `store`](#0xa_cosmwasm_vm_store) +- [Function `from_code`](#0xa_cosmwasm_vm_from_code) +- [Function `call_instantiate`](#0xa_cosmwasm_vm_call_instantiate) +- [Function `call_execute`](#0xa_cosmwasm_vm_call_execute) +- [Function `call_query`](#0xa_cosmwasm_vm_call_query) +- [Function `call_migrate`](#0xa_cosmwasm_vm_call_migrate) +- [Function `call_reply`](#0xa_cosmwasm_vm_call_reply) +- [Function `call_sudo`](#0xa_cosmwasm_vm_call_sudo) +- [Function `destroy_instance`](#0xa_cosmwasm_vm_destroy_instance) + + +
use 0x1::option;
+use 0x1::string;
+use 0x2::features;
+use 0x2::object;
+use 0x2::result;
+use 0x2::table;
+use 0xa::cosmwasm_std;
+
+ + + + + +## Resource `Instance` + + + +
struct Instance has store, key
+
+ + + + + +## Function `code_checksum` + + + +
public fun code_checksum(instance: &cosmwasm_vm::Instance): vector<u8>
+
+ + + + + +## Function `store` + + + +
public fun store(instance: &cosmwasm_vm::Instance): &table::Table<string::String, vector<u8>>
+
+ + + + + +## Function `from_code` + + + +
public fun from_code(code: vector<u8>): result::Result<cosmwasm_vm::Instance, cosmwasm_std::Error>
+
+ + + + + +## Function `call_instantiate` + + + +
#[data_struct(#[T])]
+public fun call_instantiate<T: drop>(instance: &mut cosmwasm_vm::Instance, env: &cosmwasm_std::Env, info: &cosmwasm_std::MessageInfo, msg: &T): result::Result<cosmwasm_std::Response, cosmwasm_std::Error>
+
+ + + + + +## Function `call_execute` + + + +
#[data_struct(#[T])]
+public fun call_execute<T: drop>(instance: &mut cosmwasm_vm::Instance, env: &cosmwasm_std::Env, info: &cosmwasm_std::MessageInfo, msg: &T): result::Result<cosmwasm_std::Response, cosmwasm_std::Error>
+
+ + + + + +## Function `call_query` + + + +
#[data_struct(#[T])]
+public fun call_query<T: drop>(instance: &cosmwasm_vm::Instance, env: &cosmwasm_std::Env, msg: &T): result::Result<cosmwasm_std::Response, cosmwasm_std::Error>
+
+ + + + + +## Function `call_migrate` + + + +
#[data_struct(#[T])]
+public fun call_migrate<T: drop>(instance: &mut cosmwasm_vm::Instance, env: &cosmwasm_std::Env, msg: &T): result::Result<cosmwasm_std::Response, cosmwasm_std::Error>
+
+ + + + + +## Function `call_reply` + + + +
public fun call_reply(instance: &mut cosmwasm_vm::Instance, env: &cosmwasm_std::Env, reply: &cosmwasm_std::Reply): result::Result<cosmwasm_std::Response, cosmwasm_std::Error>
+
+ + + + + +## Function `call_sudo` + + + +
#[data_struct(#[T])]
+public fun call_sudo<T: drop>(instance: &mut cosmwasm_vm::Instance, env: &cosmwasm_std::Env, msg: &T): result::Result<cosmwasm_std::Response, cosmwasm_std::Error>
+
+ + + + + +## Function `destroy_instance` + +Destroys an Instance and releases associated resources. + + +
public fun destroy_instance(instance: cosmwasm_vm::Instance): option::Option<cosmwasm_std::Error>
+
diff --git a/frameworks/rooch-nursery/sources/cosmwasm_std.move b/frameworks/rooch-nursery/sources/cosmwasm_std.move new file mode 100644 index 0000000000..0cb0fd36c9 --- /dev/null +++ b/frameworks/rooch-nursery/sources/cosmwasm_std.move @@ -0,0 +1,284 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +module rooch_nursery::cosmwasm_std { + use std::vector; + use std::string::{Self, String}; + use std::option::{Self, Option}; + + use moveos_std::base64; + use moveos_std::json; + use moveos_std::timestamp; + use moveos_std::tx_context; + use moveos_std::result::{Result, err, ok}; + + use rooch_framework::chain_id; + + // Error codes + + /// This error code is returned when a deserialization error occurs. + const ErrorDeserialize: u32 = 1; + + // Basic types + #[data_struct] + struct Coin has store, copy, drop { + denom: String, + amount: u128, + } + + // Environment information + #[data_struct] + struct BlockInfo has store, copy, drop { + height: u64, + time: u128, + chain_id: String, + } + + #[data_struct] + struct TransactionInfo has store, copy, drop { + index: u64, + } + + #[data_struct] + struct ContractInfo has store, copy, drop { + address: address, + } + + #[data_struct] + struct Env has store, copy, drop { + block: BlockInfo, + contract: ContractInfo, + transaction: Option, + } + + #[data_struct] + struct MessageInfo has store, copy, drop { + sender: address, + funds: vector, + } + + // Response types + #[data_struct] + struct Attribute has store, copy, drop { + key: String, + value: String, + } + + #[data_struct] + struct Event has store, copy, drop { + ty: String, + attributes: vector + } + + #[data_struct] + struct Response has store, copy, drop { + messages: vector, + attributes: vector, + events: vector, + data: vector + } + + #[data_struct] + struct SubMsg has store, copy, drop { + id: u64, + msg: vector, + gas_limit: Option, + reply_on: ReplyOn, + } + + #[data_struct] + struct Error has store, copy, drop { + code: u32, + message: String, + } + + #[data_struct] + struct MsgResponse has store, copy, drop { + type_url: String, + value: vector, + } + + #[data_struct] + struct SubMsgResponse has store, copy, drop { + events: vector, + msg_responses: vector, + } + + #[data_struct] + struct SubMsgResult has store, copy, drop { + ok: Option, + err: Option, + } + + #[data_struct] + struct Reply has store, copy, drop { + id: u64, + payload: String, + gas_used: u64, + result: SubMsgResult, + } + + // Enums + #[data_struct] + struct ReplyOn has store, copy, drop { + value: u8, + } + + #[data_struct] + struct StdResult has copy, drop { + ok: Option, + error: Option, + } + + // Constants for ReplyOn + const REPLY_ON_SUCCESS: u8 = 1; + const REPLY_ON_ERROR: u8 = 2; + const REPLY_ALWAYS: u8 = 3; + + // Functions + public fun new_response(): Response { + Response { + messages: vector::empty(), + attributes: vector::empty(), + events: vector::empty(), + data: vector::empty(), + } + } + + public fun new_sub_msg_response(): SubMsgResult { + SubMsgResult{ + ok: option::some( + SubMsgResponse { + events: vector::empty(), + msg_responses: vector::empty(), + } + ), + err: option::none(), + } + } + + public fun new_sub_msg_error(err: String): SubMsgResult { + SubMsgResult{ + ok: option::none(), + err: option::some(err), + } + } + + public fun add_attribute(response: &mut Response, key: String, value: String) { + vector::push_back(&mut response.attributes, Attribute { key, value }); + } + + public fun add_event(response: &mut Response, event: Event) { + vector::push_back(&mut response.events, event); + } + + public fun set_data(response: &mut Response, data: vector) { + response.data = data; + } + + public fun add_message(response: &mut Response, msg: SubMsg) { + vector::push_back(&mut response.messages, msg); + } + + public fun new_coin(denom: String, amount: u128): Coin { + Coin { denom, amount } + } + + public fun new_sub_msg(id: u64, msg: vector, gas_limit: Option, reply_on: u8): SubMsg { + SubMsg { + id, + msg, + gas_limit, + reply_on: ReplyOn { value: reply_on }, + } + } + + public fun new_error(code: u32, message: String): Error { + Error { code, message } + } + + public fun new_error_result(code: u32, message: String): Result { + err(new_error(code, message)) + } + + public fun new_reply(id: u64, payload: String, gas_used: u64, result: SubMsgResult): Reply { + Reply { + id: id, + payload: payload, + gas_used: gas_used, + result: result, + } + } + + // Helper functions + public fun serialize_env(env: &Env): vector { + json::to_json(env) + } + + public fun serialize_message_info(info: &MessageInfo): vector { + json::to_json(info) + } + + public fun serialize_message(msg: &T): vector { + json::to_json(msg) + } + + public fun deserialize_stdresult(raw: vector): Result { + let result_option = json::from_json_option(raw); + if (option::is_none(&result_option)) { + return new_error_result(ErrorDeserialize, string::utf8(b"deserialize_response_error")) + }; + + let std_result = option::extract(&mut result_option); + if (option::is_some(&std_result.ok)) { + ok(option::extract(&mut std_result.ok)) + } else { + err(new_error(1, option::extract(&mut std_result.error))) + } + } + + public fun new_binary(data: vector): String { + let encode_bytes = base64::encode(&data); + string::utf8(encode_bytes) + } + + public fun current_chain(): vector { + if (chain_id::is_main()) { + b"rooch_main" + } else if (chain_id::is_test()) { + b"rooch_test" + } else if (chain_id::is_dev()) { + b"rooch_dev" + } else { + b"rooch_local" + } + } + + public fun current_env(): Env { + let sender = tx_context::sender(); + let sequence_number = tx_context::sequence_number(); + + Env { + block: BlockInfo { + height: sequence_number, + time: (timestamp::now_milliseconds() as u128) * 1000000u128, // nanos + chain_id: std::string::utf8(current_chain()), + }, + contract: ContractInfo { + address: sender, + }, + transaction: std::option::some(TransactionInfo { + index: 0, + }), + } + } + + public fun current_message_info(): MessageInfo { + let sender = tx_context::sender(); + + MessageInfo { + sender: sender, + funds: vector::empty(), + } + } +} \ No newline at end of file diff --git a/frameworks/rooch-nursery/sources/cosmwasm_vm.move b/frameworks/rooch-nursery/sources/cosmwasm_vm.move new file mode 100644 index 0000000000..f4f4609b3f --- /dev/null +++ b/frameworks/rooch-nursery/sources/cosmwasm_vm.move @@ -0,0 +1,154 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +module rooch_nursery::cosmwasm_vm { + use std::string::{Self, String}; + use std::option::{Self, Option}; + + use moveos_std::features; + use moveos_std::table; + use moveos_std::object::{ObjectID}; + use moveos_std::result::{Result, ok}; + + use rooch_nursery::cosmwasm_std::{Response, Error, Env, MessageInfo, Reply, + new_error, new_error_result, serialize_env, serialize_message_info, serialize_message, deserialize_stdresult}; + + struct Instance has key, store { + code_checksum: vector, + store: table::Table> + } + + public fun code_checksum(instance: &Instance): vector { + instance.code_checksum + } + + public fun store(instance: &Instance): &table::Table> { + &instance.store + } + + public fun from_code(code: vector): Result { + features::ensure_wasm_enabled(); + + let store = table::new>(); + let store_handle = table::handle(&store); + + let (checksum, error_code) = native_create_instance(code, store_handle); + if (error_code == 0) { + ok(Instance { + code_checksum: checksum, + store: store, + }) + } else { + table::drop(store); + new_error_result(error_code, string::utf8(b"native_create_instance_error")) + } + } + + #[data_struct(T)] + public fun call_instantiate(instance: &mut Instance, env: &Env, info: &MessageInfo, msg: &T): Result { + let store_handle = table::handle(&mut instance.store); + let env_bytes = serialize_env(env); + let info_bytes = serialize_message_info(info); + let msg_bytes = serialize_message(msg); + + let (std_result, error_code) = native_call_instantiate_raw(instance.code_checksum, store_handle, env_bytes, info_bytes, msg_bytes); + if (error_code == 0) { + deserialize_stdresult(std_result) + } else { + new_error_result(error_code, string::utf8(b"native_call_instantiate_raw_error")) + } + } + + #[data_struct(T)] + public fun call_execute(instance: &mut Instance, env: &Env, info: &MessageInfo, msg: &T): Result { + let store_handle = table::handle(&mut instance.store); + let env_bytes = serialize_env(env); + let info_bytes = serialize_message_info(info); + let msg_bytes = serialize_message(msg); + + let (std_result, error_code) = native_call_execute_raw(instance.code_checksum, store_handle, env_bytes, info_bytes, msg_bytes); + if (error_code == 0) { + deserialize_stdresult(std_result) + } else { + new_error_result(error_code, string::utf8(b"native_call_execute_raw_error")) + } + } + + #[data_struct(T)] + public fun call_query(instance: &Instance, env: &Env, msg: &T): Result { + let store_handle = table::handle(&instance.store); + let env_bytes = serialize_env(env); + let msg_bytes = serialize_message(msg); + + let (std_result, error_code) = native_call_query_raw(instance.code_checksum, store_handle, env_bytes, msg_bytes); + if (error_code == 0) { + deserialize_stdresult(std_result) + } else { + new_error_result(error_code, string::utf8(b"native_call_query_raw_error")) + } + } + + #[data_struct(T)] + public fun call_migrate(instance: &mut Instance, env: &Env, msg: &T): Result { + let store_handle = table::handle(&instance.store); + let env_bytes = serialize_env(env); + let msg_bytes = serialize_message(msg); + + let (std_result, error_code) = native_call_migrate_raw(instance.code_checksum, store_handle, env_bytes, msg_bytes); + if (error_code == 0) { + deserialize_stdresult(std_result) + } else { + new_error_result(error_code, string::utf8(b"native_call_migrate_raw_error")) + } + } + + public fun call_reply(instance: &mut Instance, env: &Env, reply: &Reply): Result { + let store_handle = table::handle(&mut instance.store); + let env_bytes = serialize_env(env); + let msg_bytes = serialize_message(reply); + + let (std_result, error_code) = native_call_reply_raw(instance.code_checksum, store_handle, env_bytes, msg_bytes); + if (error_code == 0) { + deserialize_stdresult(std_result) + } else { + new_error_result(error_code, string::utf8(b"native_call_reply_raw_error")) + } + } + + #[data_struct(T)] + public fun call_sudo(instance: &mut Instance, env: &Env, msg: &T): Result { + let store_handle = table::handle(&mut instance.store); + let env_bytes = serialize_env(env); + let msg_bytes = serialize_message(msg); + + let (std_result, error_code) = native_call_sudo_raw(instance.code_checksum, store_handle, env_bytes, msg_bytes); + if (error_code == 0) { + deserialize_stdresult(std_result) + } else { + new_error_result(error_code, string::utf8(b"native_call_sudo_raw_error")) + } + } + + /// Destroys an Instance and releases associated resources. + public fun destroy_instance(instance: Instance): Option { + let Instance { code_checksum, store } = instance; + table::drop(store); + + let error_code = native_destroy_instance(code_checksum); + if (error_code == 0) { + option::none() + } else { + option::some(new_error(error_code, string::utf8(b"native_destroy_instance_error"))) + } + } + + // Native function declarations + native fun native_create_instance(code: vector, store_handle: ObjectID): (vector, u32); + native fun native_destroy_instance(code_checksum: vector): u32; + native fun native_call_instantiate_raw(code_checksum: vector, store_handle: ObjectID, env: vector, info: vector, msg: vector): (vector, u32); + native fun native_call_execute_raw(code_checksum: vector, store_handle: ObjectID, env: vector, info: vector, msg: vector): (vector, u32); + native fun native_call_query_raw(code_checksum: vector, store_handle: ObjectID, env: vector, msg: vector): (vector, u32); + native fun native_call_migrate_raw(code_checksum: vector, store_handle: ObjectID, env: vector, msg: vector): (vector, u32); + native fun native_call_reply_raw(code_checksum: vector, store_handle: ObjectID, env: vector, msg: vector): (vector, u32); + native fun native_call_sudo_raw(code_checksum: vector, store_handle: ObjectID, env: vector, msg: vector):(vector, u32); +} \ No newline at end of file diff --git a/frameworks/rooch-nursery/src/natives/cosmwasm_vm.rs b/frameworks/rooch-nursery/src/natives/cosmwasm_vm.rs new file mode 100644 index 0000000000..fe6174f9ba --- /dev/null +++ b/frameworks/rooch-nursery/src/natives/cosmwasm_vm.rs @@ -0,0 +1,568 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use log::error; +use std::collections::VecDeque; +use std::sync::Arc; +use std::vec; + +use cosmwasm_std::Checksum; +use cosmwasm_vm::{ + call_execute_raw, call_instantiate_raw, call_migrate_raw, call_query_raw, call_reply_raw, + call_sudo_raw, capabilities_from_csv, Cache, CacheOptions, Instance, InstanceOptions, Size, + VmResult, +}; +use once_cell::sync::Lazy; +use rooch_cosmwasm_vm::backend::{ + build_mock_backend, MockStorage, MoveBackendApi, MoveBackendQuerier, +}; +use smallvec::smallvec; + +use move_binary_format::errors::{PartialVMError, PartialVMResult}; +use move_core_types::gas_algebra::{InternalGas, InternalGasPerByte, NumBytes}; +use move_core_types::vm_status::StatusCode; +use move_vm_runtime::native_functions::{NativeContext, NativeFunction}; +use move_vm_types::loaded_data::runtime_types::Type; +use move_vm_types::natives::function::NativeResult; +use move_vm_types::pop_arg; +use move_vm_types::values::Value; + +use moveos_object_runtime::{ + runtime::ObjectRuntimeContext, runtime_object::RuntimeObject, TypeLayoutLoader, +}; +use moveos_types::{moveos_std::object::ObjectID, state_resolver::StatelessResolver}; + +use moveos_stdlib::natives::helpers::{make_module_natives, make_native}; + +use crate::natives::helper::{pop_object_id, CommonGasParametersOption}; + +const DEFAULT_GAS_LIMIT: u64 = 10000000; + +static WASM_CACHE: Lazy>> = + Lazy::new(|| { + let options = CacheOptions::new( + std::env::temp_dir(), + capabilities_from_csv("iterator,staking"), + Size::mebi(200), + Size::mebi(64), + ); + Arc::new(unsafe { Cache::new(options).unwrap() }) + }); + +#[derive(Debug, Clone)] +pub struct CosmWasmCreateInstanceGasParametersOption { + pub base: Option, + pub per_byte_wasm: Option, +} + +impl CosmWasmCreateInstanceGasParametersOption { + pub fn zeros() -> Self { + Self { + base: Some(0.into()), + per_byte_wasm: Some(InternalGasPerByte::zero()), + } + } +} + +fn vm_error(err: impl std::fmt::Display) -> PartialVMError { + PartialVMError::new(StatusCode::VM_EXTENSION_ERROR).with_message(format!("{}", err)) +} + +/*************************************************************************************************** + * native fun native_create_instance + **************************************************************************************************/ +#[inline] +pub fn native_create_instance( + gas_parameters: &GasParameters, + context: &mut NativeContext, + ty_args: Vec, + mut args: VecDeque, +) -> PartialVMResult { + debug_assert!( + ty_args.is_empty(), + "native_create_instance expects no type arguments" + ); + debug_assert_eq!(args.len(), 2, "native_create_instance expects 2 arguments"); + + let common_gas_parameter = gas_parameters.common.clone(); + let create_instance_gas_parameter = gas_parameters.native_create_instance.clone(); + + let store_obj_id = pop_object_id(&mut args)?; + let wasm_code = pop_arg!(args, Vec); + + wasm_instance_fn_dispatch( + &common_gas_parameter, + create_instance_gas_parameter + .base + .unwrap_or_else(InternalGas::zero), + create_instance_gas_parameter + .per_byte_wasm + .unwrap_or_else(InternalGasPerByte::zero), + context, + store_obj_id, + wasm_code, + move |_layout_loader, + _resolver, + _rt_obj, + wasm_bytes| + -> PartialVMResult<(Value, Option>)> { + // wat2 wasm bytes + let bytecode = wasmer::wat2wasm(wasm_bytes.as_slice()).map_err(|e| { + PartialVMError::new(StatusCode::STORAGE_ERROR) + .with_message(format!("Failed to cast wat to WASM: {}", e)) + })?; + + // Save WASM bytecode and get checksum + let checksum = WASM_CACHE.save_wasm(&bytecode).map_err(|e| { + PartialVMError::new(StatusCode::STORAGE_ERROR) + .with_message(format!("Failed to save WASM: {}", e)) + })?; + + //TODO Create real Backend + let backend = build_mock_backend(); + + // Create WASM instance + let instance_options = InstanceOptions { + gas_limit: DEFAULT_GAS_LIMIT, + }; + + let (module, store) = WASM_CACHE.get_module(&checksum).map_err(|e| { + PartialVMError::new(StatusCode::STORAGE_ERROR) + .with_message(format!("Failed to get WASM module: {}", e)) + })?; + + let _ = Instance::from_module( + store, + &module, + backend, + instance_options.gas_limit, + None, + None, + ) + .map_err(|e| { + PartialVMError::new(StatusCode::STORAGE_ERROR) + .with_message(format!("Failed to get WASM instance: {}", e)) + })?; + + Ok(( + Value::vector_u8(checksum.as_slice().to_vec()), + Some(Some(NumBytes::new(wasm_bytes.len() as u64))), + )) + }, + ) +} + +fn wasm_instance_fn_dispatch( + common_gas_params: &CommonGasParametersOption, + base: InternalGas, + per_byte_wasm: InternalGasPerByte, + context: &mut NativeContext, + store_obj_id: ObjectID, + wasm_bytes: Vec, + f: impl FnOnce( + &dyn TypeLayoutLoader, + &dyn StatelessResolver, + &mut RuntimeObject, + Vec, + ) -> PartialVMResult<(Value, Option>)>, +) -> PartialVMResult { + let object_context = context.extensions().get::(); + let binding = object_context.object_runtime(); + let mut object_runtime = binding.write(); + let resolver = object_runtime.resolver(); + let (rt_obj, object_load_gas) = object_runtime.load_object(context, &store_obj_id)?; + let wasm_bytes_len = wasm_bytes.len() as u64; + let gas_cost = base + + per_byte_wasm * NumBytes::new(wasm_bytes_len) + + common_gas_params.calculate_load_cost(object_load_gas); + + let result = f(context, resolver, rt_obj, wasm_bytes); + match result { + Ok((value, wasm_load_gas)) => Ok(NativeResult::ok( + gas_cost + common_gas_params.calculate_load_cost(wasm_load_gas), + smallvec![value, Value::u32(0)], + )), + Err(err) => { + error!("wasm_instance_fn_dispatch error: {:?}", err); + let abort_code = error_to_abort_code(err); + Ok(NativeResult::ok( + gas_cost, + smallvec![Value::vector_u8(vec![]), Value::u32(abort_code as u32)], + )) + } + } +} + +// Helper function: convert PartialVMError to abort code +fn error_to_abort_code(err: PartialVMError) -> u64 { + err.major_status().into() +} + +#[derive(Debug, Clone)] +pub struct CosmWasmDestroyInstanceGasParametersOption { + pub base: Option, +} + +impl CosmWasmDestroyInstanceGasParametersOption { + pub fn zeros() -> Self { + Self { + base: Some(0.into()), + } + } +} + +/*************************************************************************************************** + * native fun native_destroy_instance + **************************************************************************************************/ +#[inline] +fn native_destroy_instance( + gas_params: &GasParameters, + _context: &mut NativeContext, + ty_args: Vec, + arguments: VecDeque, +) -> PartialVMResult { + assert!(ty_args.is_empty(), "Wrong number of type arguments"); + assert!(arguments.len() == 1, "Wrong number of arguments"); + + Ok(NativeResult::ok( + gas_params + .common + .load_base + .unwrap_or_else(InternalGas::zero), + smallvec![Value::u32(0)], + )) +} + +/*************************************************************************************************** + * native_call_instantiate_raw + **************************************************************************************************/ + +fn native_contract_call( + gas_params: &GasParameters, + context: &mut NativeContext, + ty_args: Vec, + mut arguments: VecDeque, + expected_args: usize, + operation_name: &str, + contract_operation: F, +) -> PartialVMResult +where + F: FnOnce( + &mut Instance, + &[u8], + Option<&[u8]>, + &[u8], + ) -> VmResult>, +{ + debug_assert!( + ty_args.is_empty(), + "{} expects no type arguments", + operation_name + ); + debug_assert_eq!( + arguments.len(), + expected_args, + "{} expects {} arguments", + operation_name, + expected_args + ); + + let msg = pop_arg!(arguments, Vec); + let info = if expected_args == 5 { + Some(pop_arg!(arguments, Vec)) + } else { + None + }; + let env = pop_arg!(arguments, Vec); + let store_obj_id = pop_object_id(&mut arguments)?; + let code_checksum = pop_arg!(arguments, Vec); + + let object_context = context.extensions().get::(); + let binding = object_context.object_runtime(); + let mut object_runtime = binding.write(); + let _resolver = object_runtime.resolver(); + let (_rt_obj, object_load_gas) = object_runtime.load_object(context, &store_obj_id)?; + + let gas_cost = gas_params + .common + .load_base + .unwrap_or_else(InternalGas::zero) + + gas_params.common.calculate_load_cost(object_load_gas) + + gas_params + .common + .calculate_load_cost(Some(Some(NumBytes::new(code_checksum.len() as u64)))); + + let checksum = Checksum::try_from(code_checksum.as_slice()).map_err(vm_error)?; + let (module, store) = WASM_CACHE.get_module(&checksum).map_err(vm_error)?; + + let backend = build_mock_backend(); + let instance_options = InstanceOptions { + gas_limit: DEFAULT_GAS_LIMIT, + }; + let mut instance = Instance::from_module( + store, + &module, + backend, + instance_options.gas_limit, + None, + None, + ) + .map_err(|e| vm_error(format!("Failed to get WASM instance: {}", e)))?; + + let result = contract_operation( + &mut instance, + env.as_slice(), + info.as_ref().map(AsRef::as_ref), + msg.as_slice(), + ); + + match result { + Ok(response) => { + let gas_used = instance.get_gas_left(); + let total_gas = gas_cost + InternalGas::new(gas_used); + Ok(NativeResult::ok( + total_gas, + smallvec![ + Value::vector_u8(response), + Value::u32(0) // success + ], + )) + } + Err(err) => { + error!("{} error: {:?}", operation_name, err); + + let error_code = StatusCode::VM_EXTENSION_ERROR; + Ok(NativeResult::ok( + gas_cost, + smallvec![Value::vector_u8(vec![]), Value::u32(error_code as u32)], + )) + } + } +} + +/*************************************************************************************************** + * call_instantiate_raw + **************************************************************************************************/ + +#[inline] +fn native_call_instantiate_raw( + gas_params: &GasParameters, + context: &mut NativeContext, + ty_args: Vec, + arguments: VecDeque, +) -> PartialVMResult { + native_contract_call( + gas_params, + context, + ty_args, + arguments, + 5, // code_checksum, store_obj_id, env, info, msg + "call_instantiate_raw", + move |instance: &mut Instance, + env: &[u8], + info: Option<&[u8]>, + msg: &[u8]| + -> VmResult> { + call_instantiate_raw(instance, env, info.unwrap(), msg) + }, + ) +} + +/*************************************************************************************************** + * native_call_execute_raw + **************************************************************************************************/ + +#[inline] +fn native_call_execute_raw( + gas_params: &GasParameters, + context: &mut NativeContext, + ty_args: Vec, + arguments: VecDeque, +) -> PartialVMResult { + native_contract_call( + gas_params, + context, + ty_args, + arguments, + 5, // code_checksum, store_obj_id, env, info, msg + "call_execute_raw", + move |instance: &mut Instance, + env: &[u8], + info: Option<&[u8]>, + msg: &[u8]| + -> VmResult> { call_execute_raw(instance, env, info.unwrap(), msg) }, + ) +} + +/*************************************************************************************************** + * native_call_query_raw + **************************************************************************************************/ + +#[inline] +fn native_call_query_raw( + gas_params: &GasParameters, + context: &mut NativeContext, + ty_args: Vec, + arguments: VecDeque, +) -> PartialVMResult { + native_contract_call( + gas_params, + context, + ty_args, + arguments, + 4, // code_checksum, store_obj_id, env, msg + "call_query_raw", + move |instance: &mut Instance, + env: &[u8], + _info: Option<&[u8]>, + msg: &[u8]| + -> VmResult> { call_query_raw(instance, env, msg) }, + ) +} + +/*************************************************************************************************** + * native_call_migrate_raw + **************************************************************************************************/ + +#[inline] +fn native_call_migrate_raw( + gas_params: &GasParameters, + context: &mut NativeContext, + ty_args: Vec, + arguments: VecDeque, +) -> PartialVMResult { + native_contract_call( + gas_params, + context, + ty_args, + arguments, + 4, // code_checksum, store_obj_id, env, msg + "call_migrate_raw", + move |instance: &mut Instance, + env: &[u8], + _info: Option<&[u8]>, + msg: &[u8]| + -> VmResult> { call_migrate_raw(instance, env, msg) }, + ) +} + +/*************************************************************************************************** + * native_call_reply_raw + **************************************************************************************************/ + +#[inline] +fn native_call_reply_raw( + gas_params: &GasParameters, + context: &mut NativeContext, + ty_args: Vec, + arguments: VecDeque, +) -> PartialVMResult { + native_contract_call( + gas_params, + context, + ty_args, + arguments, + 4, // code_checksum, store_obj_id, env, msg + "call_reply_raw", + move |instance: &mut Instance, + env: &[u8], + _info: Option<&[u8]>, + msg: &[u8]| + -> VmResult> { call_reply_raw(instance, env, msg) }, + ) +} + +/*************************************************************************************************** + * native_call_sudo_raw + **************************************************************************************************/ + +#[inline] +fn native_call_sudo_raw( + gas_params: &GasParameters, + context: &mut NativeContext, + ty_args: Vec, + arguments: VecDeque, +) -> PartialVMResult { + native_contract_call( + gas_params, + context, + ty_args, + arguments, + 4, // code_checksum, store_obj_id, env, msg + "call_sudo_raw", + move |instance: &mut Instance, + env: &[u8], + _info: Option<&[u8]>, + msg: &[u8]| + -> VmResult> { call_sudo_raw(instance, env, msg) }, + ) +} + +/*************************************************************************************************** + * module + **************************************************************************************************/ + +#[derive(Debug, Clone)] +pub struct GasParameters { + pub common: CommonGasParametersOption, + pub native_create_instance: CosmWasmCreateInstanceGasParametersOption, + pub native_destroy_instance: CosmWasmDestroyInstanceGasParametersOption, +} + +impl GasParameters { + pub fn zeros() -> Self { + Self { + common: CommonGasParametersOption::zeros(), + native_create_instance: CosmWasmCreateInstanceGasParametersOption::zeros(), + native_destroy_instance: CosmWasmDestroyInstanceGasParametersOption::zeros(), + } + } +} + +pub fn make_all(gas_params: GasParameters) -> impl Iterator { + let mut natives = Vec::new(); + + if gas_params.common.load_base.is_some() || gas_params.common.load_per_byte.is_some() { + natives.push(( + "native_create_instance", + make_native(gas_params.clone(), native_create_instance), + )); + + natives.push(( + "native_destroy_instance", + make_native(gas_params.clone(), native_destroy_instance), + )); + + natives.push(( + "native_call_instantiate_raw", + make_native(gas_params.clone(), native_call_instantiate_raw), + )); + + natives.push(( + "native_call_execute_raw", + make_native(gas_params.clone(), native_call_execute_raw), + )); + + natives.push(( + "native_call_query_raw", + make_native(gas_params.clone(), native_call_query_raw), + )); + + natives.push(( + "native_call_migrate_raw", + make_native(gas_params.clone(), native_call_migrate_raw), + )); + + natives.push(( + "native_call_reply_raw", + make_native(gas_params.clone(), native_call_reply_raw), + )); + + natives.push(( + "native_call_sudo_raw", + make_native(gas_params.clone(), native_call_sudo_raw), + )); + } + + make_module_natives(natives) +} diff --git a/frameworks/rooch-nursery/src/natives/gas_parameter/cosmwasm_vm.rs b/frameworks/rooch-nursery/src/natives/gas_parameter/cosmwasm_vm.rs new file mode 100644 index 0000000000..3d0565f877 --- /dev/null +++ b/frameworks/rooch-nursery/src/natives/gas_parameter/cosmwasm_vm.rs @@ -0,0 +1,14 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use crate::natives::cosmwasm_vm::GasParameters; +use rooch_framework::natives::gas_parameter::native::MUL; + +rooch_framework::natives::gas_parameter::native::define_gas_parameters_for_natives!(GasParameters, "cosmwasm_vm", [ + [.common.load_base, optional "common.load_base", 1000 * MUL], + [.common.load_per_byte, optional "common.load_per_byte", 30 * MUL], + [.common.load_failure, optional "common.load_failure", 300 * MUL], + [.native_create_instance.base, optional "native_create_instance.base", 1000 * MUL], + [.native_create_instance.per_byte_wasm, optional "native_create_instance.per_byte_wasm", 30 * MUL], + [.native_destroy_instance.base, optional "native_destroy_instance.base", 1000 * MUL], +]); diff --git a/frameworks/rooch-nursery/src/natives/gas_parameter/mod.rs b/frameworks/rooch-nursery/src/natives/gas_parameter/mod.rs index 828224d05f..f8d23e6399 100644 --- a/frameworks/rooch-nursery/src/natives/gas_parameter/mod.rs +++ b/frameworks/rooch-nursery/src/natives/gas_parameter/mod.rs @@ -1,4 +1,5 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 +pub mod cosmwasm_vm; pub mod wasm; diff --git a/frameworks/rooch-nursery/src/natives/helper.rs b/frameworks/rooch-nursery/src/natives/helper.rs new file mode 100644 index 0000000000..aca869b1ee --- /dev/null +++ b/frameworks/rooch-nursery/src/natives/helper.rs @@ -0,0 +1,54 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use move_binary_format::errors::{PartialVMError, PartialVMResult}; +use move_core_types::{ + gas_algebra::{InternalGas, InternalGasPerByte, NumBytes}, + vm_status::StatusCode, +}; +use move_vm_types::values::Value; +use moveos_types::{moveos_std::object::ObjectID, state::MoveState}; +use std::collections::VecDeque; + +#[derive(Debug, Clone)] +pub struct CommonGasParametersOption { + pub load_base: Option, + pub load_per_byte: Option, + pub load_failure: Option, +} + +impl CommonGasParametersOption { + pub fn zeros() -> Self { + Self { + load_base: Some(InternalGas::zero()), + load_per_byte: Some(InternalGasPerByte::zero()), + load_failure: Some(InternalGas::zero()), + } + } + + pub fn calculate_load_cost(&self, loaded: Option>) -> InternalGas { + let load_base = self.load_base.unwrap_or_else(InternalGas::zero); + let load_per_byte = self.load_per_byte.unwrap_or_else(InternalGasPerByte::zero); + let load_failure = self.load_failure.unwrap_or_else(InternalGas::zero); + + load_base + + match loaded { + Some(Some(num_bytes)) => load_per_byte * num_bytes, + Some(None) => load_failure, + None => 0.into(), + } + } +} + +// ========================================================================================= +// Helpers + +pub(crate) fn pop_object_id(args: &mut VecDeque) -> PartialVMResult { + let handle = args.pop_back().unwrap(); + ObjectID::from_runtime_value(handle).map_err(|e| { + if log::log_enabled!(log::Level::Debug) { + log::warn!("[ObjectRuntime] get_object_id: {:?}", e); + } + PartialVMError::new(StatusCode::TYPE_RESOLUTION_FAILURE).with_message(e.to_string()) + }) +} diff --git a/frameworks/rooch-nursery/src/natives/mod.rs b/frameworks/rooch-nursery/src/natives/mod.rs index d3301a65cf..d955fc6f72 100644 --- a/frameworks/rooch-nursery/src/natives/mod.rs +++ b/frameworks/rooch-nursery/src/natives/mod.rs @@ -8,31 +8,42 @@ use rooch_framework::natives::gas_parameter::gas_member::{ use rooch_types::addresses::ROOCH_NURSERY_ADDRESS; use std::collections::BTreeMap; +pub mod cosmwasm_vm; pub mod gas_parameter; +pub mod helper; pub mod wasm; #[derive(Debug, Clone)] pub struct GasParameters { wasm: crate::natives::wasm::GasParameters, + cosmwasm_vm: crate::natives::cosmwasm_vm::GasParameters, // Add this field } impl GasParameters { pub fn zeros() -> Self { Self { wasm: crate::natives::wasm::GasParameters::zeros(), + cosmwasm_vm: crate::natives::cosmwasm_vm::GasParameters::zeros(), } } } impl FromOnChainGasSchedule for GasParameters { fn from_on_chain_gas_schedule(gas_schedule: &BTreeMap) -> Option { - FromOnChainGasSchedule::from_on_chain_gas_schedule(gas_schedule).map(|v| Self { wasm: v }) + Some(Self { + wasm: FromOnChainGasSchedule::from_on_chain_gas_schedule(gas_schedule) + .unwrap_or_else(crate::natives::wasm::GasParameters::zeros), + cosmwasm_vm: FromOnChainGasSchedule::from_on_chain_gas_schedule(gas_schedule) + .unwrap_or_else(crate::natives::cosmwasm_vm::GasParameters::zeros), + }) } } impl ToOnChainGasSchedule for GasParameters { fn to_on_chain_gas_schedule(&self) -> Vec<(String, u64)> { - self.wasm.to_on_chain_gas_schedule() + let mut gas_schedule = self.wasm.to_on_chain_gas_schedule(); + gas_schedule.extend(self.cosmwasm_vm.to_on_chain_gas_schedule()); + gas_schedule } } @@ -40,6 +51,7 @@ impl InitialGasSchedule for GasParameters { fn initial() -> Self { Self { wasm: InitialGasSchedule::initial(), + cosmwasm_vm: InitialGasSchedule::initial(), } } } @@ -55,6 +67,7 @@ pub fn all_natives(gas_params: GasParameters) -> NativeFunctionTable { }; } add_natives!("wasm", wasm::make_all(gas_params.wasm)); + add_natives!("cosmwasm_vm", cosmwasm_vm::make_all(gas_params.cosmwasm_vm)); let rooch_nursery_native_fun_table = make_table_from_iter(ROOCH_NURSERY_ADDRESS, natives); diff --git a/moveos/moveos-object-runtime/src/runtime_object.rs b/moveos/moveos-object-runtime/src/runtime_object.rs index b912c21f11..f7c6d12b44 100644 --- a/moveos/moveos-object-runtime/src/runtime_object.rs +++ b/moveos/moveos-object-runtime/src/runtime_object.rs @@ -32,6 +32,9 @@ use moveos_types::{ }; use std::collections::{btree_map::Entry, BTreeMap}; +type ScanFieldList = Vec<(FieldKey, Value)>; +type FieldList = Vec<(FieldKey, RuntimeObject, Option>)>; + /// A structure representing a single runtime object. pub struct RuntimeObject { pub(crate) rt_meta: RuntimeObjectMeta, @@ -239,6 +242,72 @@ impl RuntimeObject { }) } + /// Retrieves a field of the object from the state store. + fn retrieve_field_object_from_db( + &self, + layout_loader: &dyn TypeLayoutLoader, + resolver: &dyn StatelessResolver, + field_key: FieldKey, + ) -> PartialVMResult<(RuntimeObject, Option>)> { + let state_root = self.state_root()?; + let field_obj_id = self.id().child_id(field_key); + + match resolver + .get_field_at(state_root, &field_key) + .map_err(|err| { + partial_extension_error(format!("remote object resolver failure: {}", err)) + })? { + Some(obj_state) => { + debug_assert!( + obj_state.metadata.id == field_obj_id, + "The loaded object id should be equal to the expected field object id" + ); + let value_layout = layout_loader.get_type_layout(obj_state.object_type())?; + let state_bytes_len = obj_state.value.len() as u64; + + Ok(( + RuntimeObject::load(value_layout, obj_state)?, + Some(Some(NumBytes::new(state_bytes_len))), + )) + } + None => Ok((RuntimeObject::none(field_obj_id), Some(None))), + } + } + + /// List fields of the object from the state store. + fn list_field_objects_from_db( + &self, + layout_loader: &dyn TypeLayoutLoader, + resolver: &dyn StatelessResolver, + cursor: Option, + limit: usize, + ) -> PartialVMResult { + let state_root = self.state_root()?; + let state_kvs = resolver + .list_fields_at(state_root, cursor, limit) + .map_err(|err| { + partial_extension_error(format!("remote object resolver failure: {}", err)) + })?; + + let mut fields = Vec::new(); + for (key, obj_state) in state_kvs { + let field_obj_id = self.id().child_id(key); + debug_assert!( + obj_state.metadata.id == field_obj_id, + "The loaded object id should be equal to the expected field object id" + ); + let value_layout = layout_loader.get_type_layout(obj_state.object_type())?; + let state_bytes_len = obj_state.value.len() as u64; + fields.push(( + key, + RuntimeObject::load(value_layout, obj_state)?, + Some(Some(NumBytes::new(state_bytes_len))), + )); + } + + Ok(fields) + } + pub fn get_loaded_field(&self, field_key: &FieldKey) -> Option<&RuntimeObject> { self.fields .get(field_key) @@ -514,6 +583,64 @@ impl RuntimeObject { let value = tv.borrow_value(Some(&expect_value_type))?; Ok((value, field_load_gas)) } + + pub fn get_field( + &self, + layout_loader: &dyn TypeLayoutLoader, + resolver: &dyn StatelessResolver, + field_key: FieldKey, + rt_type: &Type, + ) -> PartialVMResult<(Value, Option>)> { + let expect_value_type = layout_loader.type_to_type_tag(rt_type)?; + + if let Some(cached_field) = self.get_loaded_field(&field_key) { + let value = cached_field.borrow_value(Some(&expect_value_type))?; + return Ok((value, Some(Some(NumBytes::zero())))); + } + + let (tv, field_load_gas) = + self.retrieve_field_object_from_db(layout_loader, resolver, field_key)?; + let value = tv.borrow_value(Some(&expect_value_type))?; + Ok((value, field_load_gas)) + } + + /// Scan fields of the object from the state store. + pub fn scan_fields( + &self, + layout_loader: &dyn TypeLayoutLoader, + resolver: &dyn StatelessResolver, + cursor: Option, + limit: usize, + field_type: &Type, + ) -> PartialVMResult<(ScanFieldList, Option)> { + let expect_value_type = layout_loader.type_to_type_tag(field_type)?; + let fields_with_objects = + self.list_field_objects_from_db(layout_loader, resolver, cursor, limit)?; + + let mut fields = Vec::with_capacity(fields_with_objects.len()); + let mut total_bytes_len = NumBytes::zero(); + + for (key, db_obj, bytes_len_opt) in fields_with_objects { + let (value, bytes_len) = if let Some(cached_field) = self.get_loaded_field(&key) { + ( + cached_field.borrow_value(Some(&expect_value_type))?, + Some(NumBytes::zero()), + ) + } else { + ( + db_obj.borrow_value(Some(&expect_value_type))?, + bytes_len_opt.flatten(), + ) + }; + + fields.push((key, value)); + if let Some(bytes_len) = bytes_len { + total_bytes_len += bytes_len; + } + } + + Ok((fields, Some(total_bytes_len))) + } } /// Internal functions diff --git a/moveos/moveos-wasm/src/middlewares/gas_metering.rs b/moveos/moveos-wasm/src/middlewares/gas_metering.rs index 061e8ec9b5..1df0585da7 100644 --- a/moveos/moveos-wasm/src/middlewares/gas_metering.rs +++ b/moveos/moveos-wasm/src/middlewares/gas_metering.rs @@ -65,10 +65,7 @@ impl ModuleMiddleware for GasMiddleware { }) } - fn transform_module_info( - &self, - module_info: &mut wasmer_types::ModuleInfo, - ) -> Result<(), MiddlewareError> { + fn transform_module_info(&self, module_info: &mut wasmer_types::ModuleInfo) { // Insert the signature for the charge function let charge_signature = FunctionType::new(vec![Type::I64], vec![]); let charge_signature_index = module_info.signatures.push(charge_signature); @@ -143,7 +140,6 @@ impl ModuleMiddleware for GasMiddleware { let mut charge_function_index_lock = self.charge_function_index.lock().unwrap(); *charge_function_index_lock = Some(charge_function_index); - Ok(()) } } diff --git a/scripts/bitcoin/test.sh b/scripts/bitcoin/test.sh index 46d5820024..93cd93ec0f 100644 --- a/scripts/bitcoin/test.sh +++ b/scripts/bitcoin/test.sh @@ -57,7 +57,7 @@ if [ ! -z "$UNIT_TEST" ]; then fi if [ ! -z "$WASM_INT_TEST" ]; then - cargo test -p testsuite --test integration -- --name "wasm test" + cargo test -p testsuite --test integration -- --name "cosmwasm-vm test" fi if [ ! -z "$BITCOIN_INT_TEST" ]; then