diff --git a/internal/api/bindings.h b/internal/api/bindings.h index aa1b4e6c..b99f258f 100644 --- a/internal/api/bindings.h +++ b/internal/api/bindings.h @@ -202,7 +202,16 @@ typedef struct UnmanagedVector { * from Go this is done via `C.destroy_unmanaged_vector`. */ typedef struct AnalysisReport { + /** + * `true` if and only if all required ibc exports exist as exported functions. + * This does not guarantee they are functional or even have the correct signatures. + */ bool has_ibc_entry_points; + /** + * A UTF-8 encoded comma separated list of all entrypoints that + * are exported by the contract. + */ + struct UnmanagedVector entrypoints; /** * An UTF-8 encoded comma separated list of reqired capabilities. * This is never None/nil. diff --git a/internal/api/lib_test.go b/internal/api/lib_test.go index 53889c63..d635698e 100644 --- a/internal/api/lib_test.go +++ b/internal/api/lib_test.go @@ -261,7 +261,6 @@ func TestGetMetrics(t *testing.T) { require.Equal(t, uint32(0), metrics.HitsMemoryCache) require.Equal(t, uint32(1), metrics.HitsFsCache) require.Equal(t, uint64(1), metrics.ElementsMemoryCache) - t.Log(t, metrics.SizeMemoryCache) require.InEpsilon(t, 2832576, metrics.SizeMemoryCache, 0.25) // Instantiate 2 @@ -362,7 +361,7 @@ func TestInstantiate(t *testing.T) { res, cost, err := Instantiate(cache, checksum, env, info, msg, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) require.NoError(t, err) requireOkResponse(t, res, 0) - assert.Equal(t, uint64(0x13a78a36c), cost.UsedInternally) + assert.Equal(t, uint64(0x5088ea), cost.UsedInternally) var result types.ContractResult err = json.Unmarshal(res, &result) @@ -393,7 +392,7 @@ func TestExecute(t *testing.T) { diff := time.Since(start) require.NoError(t, err) requireOkResponse(t, res, 0) - assert.Equal(t, uint64(0x13a78a36c), cost.UsedInternally) + assert.Equal(t, uint64(0x5088ea), cost.UsedInternally) t.Logf("Time (%d gas): %s\n", cost.UsedInternally, diff) // execute with the same store @@ -406,7 +405,7 @@ func TestExecute(t *testing.T) { res, cost, err = Execute(cache, checksum, env, info, []byte(`{"release":{}}`), &igasMeter2, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) diff = time.Since(start) require.NoError(t, err) - assert.Equal(t, uint64(0x222892d70), cost.UsedInternally) + assert.Equal(t, uint64(0x8be9c6), cost.UsedInternally) t.Logf("Time (%d gas): %s\n", cost.UsedInternally, diff) // make sure it read the balance properly and we got 250 atoms @@ -514,7 +513,7 @@ func TestExecuteCpuLoop(t *testing.T) { diff := time.Since(start) require.NoError(t, err) requireOkResponse(t, res, 0) - assert.Equal(t, uint64(0xd45091d0), cost.UsedInternally) + assert.Equal(t, uint64(0x365a42), cost.UsedInternally) t.Logf("Time (%d gas): %s\n", cost.UsedInternally, diff) // execute a cpu loop @@ -665,7 +664,7 @@ func TestMultipleInstances(t *testing.T) { require.NoError(t, err) requireOkResponse(t, res, 0) // we now count wasm gas charges and db writes - assert.Equal(t, uint64(0x138559c5c), cost.UsedInternally) + assert.Equal(t, uint64(0x4ffce0), cost.UsedInternally) // instance2 controlled by mary gasMeter2 := NewMockGasMeter(TESTING_GAS_LIMIT) @@ -676,14 +675,14 @@ func TestMultipleInstances(t *testing.T) { res, cost, err = Instantiate(cache, checksum, env, info, msg, &igasMeter2, store2, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) require.NoError(t, err) requireOkResponse(t, res, 0) - assert.Equal(t, uint64(0x1399177bc), cost.UsedInternally) + assert.Equal(t, uint64(0x504dbc), cost.UsedInternally) // fail to execute store1 with mary - resp := exec(t, cache, checksum, "mary", store1, api, querier, 0x1218ff5d0) + resp := exec(t, cache, checksum, "mary", store1, api, querier, 0x4a20c2) require.Equal(t, "Unauthorized", resp.Err) // succeed to execute store1 with fred - resp = exec(t, cache, checksum, "fred", store1, api, querier, 0x22188d470) + resp = exec(t, cache, checksum, "fred", store1, api, querier, 0x8ba826) require.Equal(t, "", resp.Err) require.Equal(t, 1, len(resp.Ok.Messages)) attributes := resp.Ok.Attributes @@ -692,7 +691,7 @@ func TestMultipleInstances(t *testing.T) { require.Equal(t, "bob", attributes[1].Value) // succeed to execute store2 with mary - resp = exec(t, cache, checksum, "mary", store2, api, querier, 0x2220900f0) + resp = exec(t, cache, checksum, "mary", store2, api, querier, 0x8bc8f6) require.Equal(t, "", resp.Err) require.Equal(t, 1, len(resp.Ok.Messages)) attributes = resp.Ok.Attributes diff --git a/lib_test.go b/lib_test.go index eeabb4c3..d54592da 100644 --- a/lib_test.go +++ b/lib_test.go @@ -281,6 +281,7 @@ func TestGetMetrics(t *testing.T) { require.Equal(t, uint32(0), metrics.HitsMemoryCache) require.Equal(t, uint32(1), metrics.HitsFsCache) require.Equal(t, uint64(1), metrics.ElementsMemoryCache) + t.Log(metrics.SizeMemoryCache) require.InEpsilon(t, 2832576, metrics.SizeMemoryCache, 0.25) // Instantiate 2 diff --git a/libwasmvm/Cargo.lock b/libwasmvm/Cargo.lock index 3efa8222..743103b9 100644 --- a/libwasmvm/Cargo.lock +++ b/libwasmvm/Cargo.lock @@ -254,8 +254,8 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.5.0" -source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v1.5.0#89891f0bb2de2c83d00600208695d0d5e1b617ac" +version = "2.0.0-beta.0" +source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.0.0-beta.0#1ce73c6b91466fc46e8b574e381cdaa1fb0b298e" dependencies = [ "digest 0.10.7", "ecdsa", @@ -267,16 +267,16 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.5.0" -source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v1.5.0#89891f0bb2de2c83d00600208695d0d5e1b617ac" +version = "2.0.0-beta.0" +source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.0.0-beta.0#1ce73c6b91466fc46e8b574e381cdaa1fb0b298e" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-std" -version = "1.5.0" -source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v1.5.0#89891f0bb2de2c83d00600208695d0d5e1b617ac" +version = "2.0.0-beta.0" +source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.0.0-beta.0#1ce73c6b91466fc46e8b574e381cdaa1fb0b298e" dependencies = [ "base64", "bech32", @@ -296,9 +296,10 @@ dependencies = [ [[package]] name = "cosmwasm-vm" -version = "1.5.0" -source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v1.5.0#89891f0bb2de2c83d00600208695d0d5e1b617ac" +version = "2.0.0-beta.0" +source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.0.0-beta.0#1ce73c6b91466fc46e8b574e381cdaa1fb0b298e" dependencies = [ + "bech32", "bitflags", "bytecheck", "bytes", @@ -313,6 +314,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.7", + "strum", "thiserror", "wasmer", "wasmer-middlewares", @@ -1391,6 +1393,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.13" @@ -1464,9 +1472,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.5.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +checksum = "83c37d03f3b0f6b5f77c11af1e7c772de1c9af83e50bef7bb6069601900ba67b" dependencies = [ "serde", ] @@ -1605,6 +1613,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.29", +] + [[package]] name = "subtle" version = "2.5.0" diff --git a/libwasmvm/Cargo.toml b/libwasmvm/Cargo.toml index 8301c67e..d0c4a162 100644 --- a/libwasmvm/Cargo.toml +++ b/libwasmvm/Cargo.toml @@ -26,8 +26,8 @@ default = [] backtraces = [] [dependencies] -cosmwasm-std = { git = "https://github.com/CosmWasm/cosmwasm.git", rev = "v1.5.0", features = ["staking", "stargate", "iterator"] } -cosmwasm-vm = { git = "https://github.com/CosmWasm/cosmwasm.git", rev = "v1.5.0", features = ["staking", "stargate", "iterator"] } +cosmwasm-std = { git = "https://github.com/CosmWasm/cosmwasm.git", rev = "v2.0.0-beta.0", features = ["staking", "stargate", "iterator"] } +cosmwasm-vm = { git = "https://github.com/CosmWasm/cosmwasm.git", rev = "v2.0.0-beta.0", features = ["staking", "stargate", "iterator"] } errno = "0.2" serde_json = "1.0.91" thiserror = "1.0.38" diff --git a/libwasmvm/bindings.h b/libwasmvm/bindings.h index aa1b4e6c..b99f258f 100644 --- a/libwasmvm/bindings.h +++ b/libwasmvm/bindings.h @@ -202,7 +202,16 @@ typedef struct UnmanagedVector { * from Go this is done via `C.destroy_unmanaged_vector`. */ typedef struct AnalysisReport { + /** + * `true` if and only if all required ibc exports exist as exported functions. + * This does not guarantee they are functional or even have the correct signatures. + */ bool has_ibc_entry_points; + /** + * A UTF-8 encoded comma separated list of all entrypoints that + * are exported by the contract. + */ + struct UnmanagedVector entrypoints; /** * An UTF-8 encoded comma separated list of reqired capabilities. * This is never None/nil. diff --git a/libwasmvm/src/cache.rs b/libwasmvm/src/cache.rs index 2a5a0c74..d63c4357 100644 --- a/libwasmvm/src/cache.rs +++ b/libwasmvm/src/cache.rs @@ -1,9 +1,10 @@ -use std::collections::HashSet; +use std::collections::BTreeSet; use std::convert::TryInto; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::str::from_utf8; -use cosmwasm_vm::{capabilities_from_csv, Cache, CacheOptions, Checksum, Size}; +use cosmwasm_std::Checksum; +use cosmwasm_vm::{capabilities_from_csv, Cache, CacheOptions, Size}; use crate::api::GoApi; use crate::args::{AVAILABLE_CAPABILITIES_ARG, CACHE_ARG, CHECKSUM_ARG, DATA_DIR_ARG, WASM_ARG}; @@ -72,12 +73,12 @@ fn do_init_cache( .try_into() .expect("Cannot convert u32 to usize. What kind of system is this?"), ); - let options = CacheOptions { - base_dir: dir_str.into(), - available_capabilities: capabilities, + let options = CacheOptions::new( + dir_str, + capabilities, memory_cache_size, instance_memory_limit, - }; + ); let cache = unsafe { Cache::new(options) }?; let out = Box::new(cache); Ok(Box::into_raw(out)) @@ -243,7 +244,12 @@ fn do_unpin( #[repr(C)] #[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] pub struct AnalysisReport { + /// `true` if and only if all required ibc exports exist as exported functions. + /// This does not guarantee they are functional or even have the correct signatures. pub has_ibc_entry_points: bool, + /// A UTF-8 encoded comma separated list of all entrypoints that + /// are exported by the contract. + pub entrypoints: UnmanagedVector, /// An UTF-8 encoded comma separated list of reqired capabilities. /// This is never None/nil. pub required_capabilities: UnmanagedVector, @@ -254,19 +260,26 @@ impl From for AnalysisReport { let cosmwasm_vm::AnalysisReport { has_ibc_entry_points, required_capabilities, + entrypoints, } = report; let required_capabilities_utf8 = set_to_csv(required_capabilities).into_bytes(); + let entrypoints = set_to_csv(entrypoints).into_bytes(); AnalysisReport { has_ibc_entry_points, required_capabilities: UnmanagedVector::new(Some(required_capabilities_utf8)), + entrypoints: UnmanagedVector::new(Some(entrypoints)), } } } -fn set_to_csv(set: HashSet) -> String { - let mut list: Vec = set.into_iter().collect(); - list.sort_unstable(); +/// Takes a set of string-like elements and returns a comma-separated list. +/// Since no escaping or quoting is applied to the elements, the caller needs to ensure +/// only simple alphanumeric values are used. +/// +/// The order of the output elements is determined by the iteration order of the provided set. +fn set_to_csv(set: BTreeSet>) -> String { + let list: Vec<&str> = set.iter().map(|e| e.as_ref()).collect(); list.join(",") } @@ -390,7 +403,7 @@ mod tests { use crate::assert_approx_eq; use super::*; - use std::iter::FromIterator; + use std::{cmp::Ordering, iter::FromIterator}; use tempfile::TempDir; static HACKATOM: &[u8] = include_bytes!("../../testdata/hackatom.wasm"); @@ -738,13 +751,13 @@ mod tests { #[test] fn set_to_csv_works() { - assert_eq!(set_to_csv(HashSet::new()), ""); + assert_eq!(set_to_csv(BTreeSet::::new()), ""); assert_eq!( - set_to_csv(HashSet::from_iter(vec!["foo".to_string()])), + set_to_csv(BTreeSet::from_iter(vec!["foo".to_string()])), "foo", ); assert_eq!( - set_to_csv(HashSet::from_iter(vec![ + set_to_csv(BTreeSet::from_iter(vec![ "foo".to_string(), "bar".to_string(), "baz".to_string(), @@ -752,7 +765,7 @@ mod tests { "bar,baz,foo", ); assert_eq!( - set_to_csv(HashSet::from_iter(vec![ + set_to_csv(BTreeSet::from_iter(vec![ "a".to_string(), "aa".to_string(), "b".to_string(), @@ -764,6 +777,98 @@ mod tests { ])), "A,AA,B,C,a,aa,b,c", ); + + // str + assert_eq!( + set_to_csv(BTreeSet::from_iter(vec![ + "a", "aa", "b", "c", "A", "AA", "B", "C", + ])), + "A,AA,B,C,a,aa,b,c", + ); + + // custom type with numeric ordering + #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] + enum Number { + One = 1, + Two = 2, + Three = 3, + Eleven = 11, + Twelve = 12, + Zero = 0, + MinusOne = -1, + } + + impl AsRef for Number { + fn as_ref(&self) -> &str { + use Number::*; + match self { + One => "1", + Two => "2", + Three => "3", + Eleven => "11", + Twelve => "12", + Zero => "0", + MinusOne => "-1", + } + } + } + + assert_eq!( + set_to_csv(BTreeSet::from_iter([ + Number::One, + Number::Two, + Number::Three, + Number::Eleven, + Number::Twelve, + Number::Zero, + Number::MinusOne, + ])), + "-1,0,1,2,3,11,12", + ); + + // custom type with lexicographical ordering + #[derive(PartialEq, Eq)] + enum Color { + Red, + Green, + Blue, + Yellow, + } + + impl AsRef for Color { + fn as_ref(&self) -> &str { + use Color::*; + match self { + Red => "red", + Green => "green", + Blue => "blue", + Yellow => "yellow", + } + } + } + + impl Ord for Color { + fn cmp(&self, other: &Self) -> Ordering { + // sort by name + self.as_ref().cmp(other.as_ref()) + } + } + + impl PartialOrd for Color { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + assert_eq!( + set_to_csv(BTreeSet::from_iter([ + Color::Red, + Color::Green, + Color::Blue, + Color::Yellow, + ])), + "blue,green,red,yellow", + ); } #[test] diff --git a/libwasmvm/src/calls.rs b/libwasmvm/src/calls.rs index 5c3b0e99..44d57b36 100644 --- a/libwasmvm/src/calls.rs +++ b/libwasmvm/src/calls.rs @@ -5,11 +5,12 @@ use std::panic::{catch_unwind, AssertUnwindSafe}; use std::time::SystemTime; use time::{format_description::well_known::Rfc3339, OffsetDateTime}; +use cosmwasm_std::Checksum; use cosmwasm_vm::{ call_execute_raw, call_ibc_channel_close_raw, call_ibc_channel_connect_raw, call_ibc_channel_open_raw, call_ibc_packet_ack_raw, call_ibc_packet_receive_raw, call_ibc_packet_timeout_raw, call_instantiate_raw, call_migrate_raw, call_query_raw, - call_reply_raw, call_sudo_raw, Backend, Cache, Checksum, Instance, InstanceOptions, VmResult, + call_reply_raw, call_sudo_raw, Backend, Cache, Instance, InstanceOptions, VmResult, }; use crate::api::GoApi; @@ -466,11 +467,9 @@ fn do_call_2_args( let arg2 = arg2.read().ok_or_else(|| Error::unset_arg(ARG2))?; let backend = into_backend(db, api, querier); - let options = InstanceOptions { - gas_limit, - print_debug, - }; - let mut instance = cache.get_instance(&checksum, backend, options)?; + let options = InstanceOptions { gas_limit }; + let mut instance: Instance = + cache.get_instance(&checksum, backend, options)?; // If print_debug = false, use default debug handler from cosmwasm-vm, which discards messages if print_debug { @@ -563,10 +562,7 @@ fn do_call_3_args( let arg3 = arg3.read().ok_or_else(|| Error::unset_arg(ARG3))?; let backend = into_backend(db, api, querier); - let options = InstanceOptions { - gas_limit, - print_debug, - }; + let options = InstanceOptions { gas_limit }; let mut instance = cache.get_instance(&checksum, backend, options)?; // If print_debug = false, use default debug handler from cosmwasm-vm, which discards messages diff --git a/libwasmvm/src/error/rust.rs b/libwasmvm/src/error/rust.rs index 6baa3f91..84545279 100644 --- a/libwasmvm/src/error/rust.rs +++ b/libwasmvm/src/error/rust.rs @@ -1,3 +1,4 @@ +use cosmwasm_std::ChecksumError; use cosmwasm_vm::VmError; use errno::{set_errno, Errno}; #[cfg(feature = "backtraces")] @@ -37,6 +38,11 @@ pub enum RustError { #[cfg(feature = "backtraces")] backtrace: Backtrace, }, + #[error("Checksum not of length 32")] + ChecksumError { + #[cfg(feature = "backtraces")] + backtrace: Backtrace, + }, #[error("Error calling the VM: {}", msg)] VmErr { msg: String, @@ -91,6 +97,13 @@ impl RustError { backtrace: Backtrace::capture(), } } + + pub fn checksum_err() -> Self { + RustError::ChecksumError { + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), + } + } } impl From for RustError { @@ -102,6 +115,12 @@ impl From for RustError { } } +impl From for RustError { + fn from(_: ChecksumError) -> Self { + RustError::checksum_err() + } +} + impl From for RustError { fn from(source: std::str::Utf8Error) -> Self { RustError::invalid_utf8(source) @@ -211,7 +230,8 @@ where #[cfg(test)] mod tests { use super::*; - use cosmwasm_vm::{BackendError, Checksum}; + use cosmwasm_std::Checksum; + use cosmwasm_vm::BackendError; use errno::errno; use std::str; diff --git a/libwasmvm/src/tests.rs b/libwasmvm/src/tests.rs index 9cd7f63a..ee576633 100644 --- a/libwasmvm/src/tests.rs +++ b/libwasmvm/src/tests.rs @@ -9,7 +9,6 @@ use cosmwasm_vm::{ }; static CYBERPUNK: &[u8] = include_bytes!("../../testdata/cyberpunk.wasm"); -const PRINT_DEBUG: bool = false; const MEMORY_CACHE_SIZE: Size = Size::mebi(200); const MEMORY_LIMIT: Size = Size::mebi(32); const GAS_LIMIT: u64 = 200_000_000_000; // ~0.2ms @@ -17,17 +16,16 @@ const GAS_LIMIT: u64 = 200_000_000_000; // ~0.2ms #[test] fn handle_cpu_loop_with_cache() { let backend = mock_backend(&[]); - let options = CacheOptions { - base_dir: TempDir::new().unwrap().path().to_path_buf(), - available_capabilities: capabilities_from_csv("staking"), - memory_cache_size: MEMORY_CACHE_SIZE, - instance_memory_limit: MEMORY_LIMIT, - }; + let options = CacheOptions::new( + TempDir::new().unwrap().path().to_path_buf(), + capabilities_from_csv("staking"), + MEMORY_CACHE_SIZE, + MEMORY_LIMIT, + ); let cache = unsafe { Cache::new(options) }.unwrap(); let options = InstanceOptions { gas_limit: GAS_LIMIT, - print_debug: PRINT_DEBUG, }; // store code