From ef1c454aaf036b9511d33e84bb3d4798d446517b Mon Sep 17 00:00:00 2001 From: loloicci Date: Wed, 28 Sep 2022 17:01:18 +0900 Subject: [PATCH 1/7] feat: add get_wasmer_module to backend api --- packages/vm/src/backend.rs | 2 ++ packages/vm/src/cache.rs | 39 ++++++++++++++++++--------------- packages/vm/src/testing/mock.rs | 16 ++++++++++++++ 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/packages/vm/src/backend.rs b/packages/vm/src/backend.rs index 256bb286b..297954fb3 100644 --- a/packages/vm/src/backend.rs +++ b/packages/vm/src/backend.rs @@ -6,6 +6,7 @@ use thiserror::Error; use cosmwasm_std::{Binary, ContractResult, SystemResult}; #[cfg(feature = "iterator")] use cosmwasm_std::{Order, Pair}; +use wasmer::Module; use crate::environment::Environment; use crate::{FunctionMetadata, WasmerVal}; @@ -141,6 +142,7 @@ pub trait BackendApi: Copy + Clone + Send { A: BackendApi + 'static, S: Storage + 'static, Q: Querier + 'static; + fn get_wasmer_module(&self, contract_addr: &str) -> BackendResult; } pub trait Querier { diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 279d18c91..dc47f81fb 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -15,6 +15,8 @@ use crate::size::Size; use crate::static_analysis::{deserialize_wasm, has_ibc_entry_points}; use crate::wasm_backend::{compile, make_runtime_store}; +use wasmer::Module; + const WASM_DIR: &str = "wasm"; const MODULES_DIR: &str = "modules"; @@ -229,35 +231,36 @@ where backend: Backend, options: InstanceOptions, ) -> VmResult> { + let module = self.get_module(checksum)?; + let instance = + Instance::from_module(&module, backend, options.gas_limit, options.print_debug)?; + Ok(instance) + } + + /// Returns a Module tied to a previously saved Wasm. + /// Depending on availability, this is either generated from a cached instance, a cached module or Wasm code. + pub fn get_module(&self, checksum: &Checksum) -> VmResult { let mut cache = self.inner.lock().unwrap(); // Try to get module from the pinned memory cache if let Some(module) = cache.pinned_memory_cache.load(checksum)? { cache.stats.hits_pinned_memory_cache += 1; - let instance = - Instance::from_module(&module, backend, options.gas_limit, options.print_debug)?; - return Ok(instance); + return Ok(module); } // Get module from memory cache if let Some(module) = cache.memory_cache.load(checksum)? { cache.stats.hits_memory_cache += 1; - let instance = Instance::from_module( - &module.module, - backend, - options.gas_limit, - options.print_debug, - )?; - return Ok(instance); + return Ok(module.module); } // Get module from file system cache let store = make_runtime_store(Some(cache.instance_memory_limit)); if let Some((module, module_size)) = cache.fs_cache.load(checksum, &store)? { cache.stats.hits_fs_cache += 1; - let instance = - Instance::from_module(&module, backend, options.gas_limit, options.print_debug)?; - cache.memory_cache.store(checksum, module, module_size)?; - return Ok(instance); + cache + .memory_cache + .store(checksum, module.clone(), module_size)?; + return Ok(module); } // Re-compile module from wasm @@ -268,11 +271,11 @@ where let wasm = self.load_wasm_with_path(&cache.wasm_path, checksum)?; cache.stats.misses += 1; let module = compile(&wasm, Some(cache.instance_memory_limit))?; - let instance = - Instance::from_module(&module, backend, options.gas_limit, options.print_debug)?; let module_size = cache.fs_cache.store(checksum, &module)?; - cache.memory_cache.store(checksum, module, module_size)?; - Ok(instance) + cache + .memory_cache + .store(checksum, module.clone(), module_size)?; + Ok(module) } } diff --git a/packages/vm/src/testing/mock.rs b/packages/vm/src/testing/mock.rs index d124c06dd..a67ad0fbb 100644 --- a/packages/vm/src/testing/mock.rs +++ b/packages/vm/src/testing/mock.rs @@ -4,6 +4,7 @@ use std::cell::RefCell; use std::collections::HashMap; use std::sync::RwLock; use std::thread_local; +use wasmer::Module; use super::querier::MockQuerier; use super::storage::MockStorage; @@ -46,6 +47,7 @@ thread_local! { // INSTANCE_CACHE is intended to replace wasmvm's cache layer in the mock. // Unlike wasmvm, you have to initialize it yourself in the place where you test the dynamic call. pub static INSTANCE_CACHE: RwLock>> = RwLock::new(HashMap::new()); + pub static MODULE_CACHE: RwLock>> = RwLock::new(HashMap::new()); } /// Zero-pads all human addresses to make them fit the canonical_length and @@ -212,6 +214,20 @@ impl BackendApi for MockApi { } }) } + + fn get_wasmer_module(&self, contract_addr: &str) -> BackendResult { + let gas_info = GasInfo::new(0, 0); + MODULE_CACHE.with(|lock| { + let cache = lock.read().unwrap(); + match cache.get(contract_addr) { + Some(module) => (Ok(module.borrow().clone()), gas_info), + None => ( + Err(BackendError::dynamic_link_err("cannot found checksum")), + gas_info, + ), + } + }) + } } /// Returns a default enviroment with height, time, chain_id, and contract address From 5b62b943de89e456e29e7312bcc7d8b21c6648ed Mon Sep 17 00:00:00 2001 From: loloicci Date: Wed, 28 Sep 2022 17:01:34 +0900 Subject: [PATCH 2/7] feat: add validate interface to api --- Cargo.lock | 1 + packages/std/Cargo.toml | 1 + packages/std/src/imports.rs | 26 ++++++++++++- packages/std/src/mock.rs | 10 +++++ packages/std/src/traits.rs | 7 ++++ packages/vm/Cargo.toml | 2 +- packages/vm/src/compatibility.rs | 2 + packages/vm/src/dynamic_link.rs | 65 ++++++++++++++++++++++++++++---- packages/vm/src/environment.rs | 1 + packages/vm/src/imports.rs | 1 + packages/vm/src/instance.rs | 16 +++++++- 11 files changed, 121 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b3e06d2d..9bf16e008 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,6 +246,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] diff --git a/packages/std/Cargo.toml b/packages/std/Cargo.toml index fc0d8cb7b..df1075bc0 100644 --- a/packages/std/Cargo.toml +++ b/packages/std/Cargo.toml @@ -35,6 +35,7 @@ schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] } thiserror = "1.0" uuid = { version = "1.0.0-alpha.1", features = ["v5", "serde"] } +wasmer-types = { version = "1.0.2", features = ["enable-serde"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] cosmwasm-crypto = { path = "../crypto", version = "0.14.0-0.4.0" } diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index 69866d271..23cc6379e 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -1,5 +1,6 @@ use std::convert::TryInto; use std::vec::Vec; +use wasmer_types::{ExportType, FunctionType}; use crate::addresses::{Addr, CanonicalAddr}; use crate::binary::Binary; @@ -7,12 +8,12 @@ use crate::errors::{ HashCalculationError, RecoverPubkeyError, StdError, StdResult, SystemError, VerificationError, }; use crate::import_helpers::{from_high_half, from_low_half}; -use crate::memory::{alloc, build_region, consume_region, Region}; +use crate::memory::{alloc, build_region, consume_region, release_buffer, Region}; use crate::results::SystemResult; #[cfg(feature = "iterator")] use crate::sections::decode_sections2; use crate::sections::encode_sections; -use crate::serde::from_slice; +use crate::serde::{from_slice, to_vec}; use crate::traits::{Api, Querier, QuerierResult, Storage}; #[cfg(feature = "iterator")] use crate::{ @@ -52,6 +53,7 @@ extern "C" { fn ed25519_verify(message_ptr: u32, signature_ptr: u32, public_key_ptr: u32) -> u32; fn ed25519_batch_verify(messages_ptr: u32, signatures_ptr: u32, public_keys_ptr: u32) -> u32; fn sha1_calculate(inputs_ptr: u32) -> u64; + fn validate_dynamic_link_interface(contract_ptr: u32, interface_ptr: u32) -> u32; fn debug(source_ptr: u32); @@ -346,6 +348,26 @@ impl Api for ExternalApi { } } + fn validate_dynamic_link_interface( + &self, + contract: &Addr, + interface: &[ExportType], + ) -> StdResult<()> { + let contract_region = release_buffer(to_vec(contract)?); + let contract_ptr = contract_region as u32; + let interface_region = release_buffer(to_vec(interface)?); + let interface_ptr = interface_region as u32; + let result = unsafe { validate_dynamic_link_interface(contract_ptr, interface_ptr) }; + if result != 0 { + let error = unsafe { consume_string_region_written_by_vm(result as *mut Region) }; + return Err(StdError::generic_err(format!( + "dynamic_link_interface_validate errored: {}", + error + ))); + }; + Ok(()) + } + fn debug(&self, message: &str) { // keep the boxes in scope, so we free it at the end (don't cast to pointers same line as build_region) let region = build_region(message.as_bytes()); diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index a22e08704..4fcdf5c03 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -2,6 +2,7 @@ use serde::de::DeserializeOwned; #[cfg(feature = "stargate")] use serde::Serialize; use std::collections::HashMap; +use wasmer_types::{ExportType, FunctionType}; use crate::addresses::{Addr, CanonicalAddr}; use crate::binary::Binary; @@ -185,6 +186,15 @@ impl Api for MockApi { fn debug(&self, message: &str) { println!("{}", message); } + + // always true in mock + fn validate_dynamic_link_interface( + &self, + _contract: &Addr, + _interface: &[ExportType], + ) -> StdResult<()> { + Ok(()) + } } /// Returns a default enviroment with height, time, chain_id, and contract address diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index ce32c6a99..79e5dfab6 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -1,5 +1,6 @@ use serde::{de::DeserializeOwned, Serialize}; use std::ops::Deref; +use wasmer_types::{ExportType, FunctionType}; use crate::addresses::{Addr, CanonicalAddr}; use crate::binary::Binary; @@ -119,6 +120,12 @@ pub trait Api { fn sha1_calculate(&self, inputs: &[&[u8]]) -> Result<[u8; 20], HashCalculationError>; + fn validate_dynamic_link_interface( + &self, + contract: &Addr, + interface: &[ExportType], + ) -> StdResult<()>; + /// Emits a debugging message that is handled depending on the environment (typically printed to console or ignored). /// Those messages are not persisted to chain. fn debug(&self, message: &str); diff --git a/packages/vm/Cargo.toml b/packages/vm/Cargo.toml index 4130b843f..45b582021 100644 --- a/packages/vm/Cargo.toml +++ b/packages/vm/Cargo.toml @@ -49,7 +49,7 @@ sha2 = "0.9.1" thiserror = "1.0" wasmer = { version = "1.0.2", default-features = false, features = ["jit", "singlepass"] } wasmer-middlewares = { version = "1.0.2" } -wasmer-types = { version = "1.0.2"} +wasmer-types = { version = "1.0.2", features = ["enable-serde"] } # Wasmer git/local (used for quick local debugging or patching) # wasmer = { git = "https://github.com/wasmerio/wasmer", rev = "1.0.2", default-features = false, features = ["jit", "singlepass"] } diff --git a/packages/vm/src/compatibility.rs b/packages/vm/src/compatibility.rs index ead1f8b16..b0db4bfad 100644 --- a/packages/vm/src/compatibility.rs +++ b/packages/vm/src/compatibility.rs @@ -21,6 +21,7 @@ const SUPPORTED_IMPORTS: &[&str] = &[ "env.ed25519_verify", "env.ed25519_batch_verify", "env.sha1_calculate", + "env.validate_dynamic_link_interface", "env.debug", "env.query_chain", #[cfg(feature = "iterator")] @@ -332,6 +333,7 @@ mod tests { (import "env" "ed25519_verify" (func (param i32 i32 i32) (result i32))) (import "env" "ed25519_batch_verify" (func (param i32 i32 i32) (result i32))) (import "env" "sha1_calculate" (func (param i32) (result i64))) + (import "env" "validate_dynamic_link_interface" (func (param i32 i32) (result i32))) )"#, ) .unwrap(); diff --git a/packages/vm/src/dynamic_link.rs b/packages/vm/src/dynamic_link.rs index 272aafe29..da43c6ea3 100644 --- a/packages/vm/src/dynamic_link.rs +++ b/packages/vm/src/dynamic_link.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; use std::fmt; -use std::str; use crate::backend::{BackendApi, Querier, Storage}; use crate::conversion::ref_to_u32; @@ -8,9 +7,13 @@ use crate::environment::{process_gas_info, Environment}; use crate::errors::{CommunicationError, VmError, VmResult}; use crate::imports::write_to_contract; use crate::memory::read_region; -use wasmer::{Exports, Function, FunctionType, ImportObject, Module, RuntimeError, Val}; +use wasmer::{ + ExportType, Exports, Function, FunctionType, ImportObject, Module, RuntimeError, Val, +}; use wasmer_types::ImportIndex; +use cosmwasm_std::{from_slice, Addr}; + const MAX_REGIONS_LENGTH: usize = 100_000; pub type WasmerVal = Val; @@ -85,10 +88,9 @@ where )); }; let address_region_ptr = ref_to_u32(&args[0])?; - let raw_contract_addr = read_region(&env.memory(), address_region_ptr, 64)?; - let contract_addr = str::from_utf8(&raw_contract_addr) - .map_err(|_| RuntimeError::new("Invalid stored callee contract address"))? - .trim_matches('"'); + let contract_addr_binary = read_region(&env.memory(), address_region_ptr, 64)?; + let contract_addr: Addr = from_slice(&contract_addr_binary) + .map_err(|_| RuntimeError::new("Invalid callee contract address"))?; let func_args = &args[1..]; with_trace_dynamic_call(env, || { let func_info = env @@ -98,7 +100,7 @@ where .unwrap(); let (call_result, gas_info) = env.api - .contract_call(env, contract_addr, &func_info, func_args); + .contract_call(env, contract_addr.as_str(), &func_info, func_args); process_gas_info::(env, gas_info)?; match call_result { Ok(ret) => Ok(ret.to_vec()), @@ -234,6 +236,55 @@ where Ok(write_to_contract(env, value)?.into()) } +pub fn native_validate_dynamic_link_interface( + env: &Environment, + address: u32, + interface: u32, +) -> Result +where + A: BackendApi + 'static, + S: Storage + 'static, + Q: Querier + 'static, +{ + let contract_addr_raw = read_region(&env.memory(), address, 64)?; + let contract_addr: Addr = from_slice(&contract_addr_raw) + .map_err(|_| RuntimeError::new("Invalid contract address to detect interface"))?; + let expected_interface_binary = read_region(&env.memory(), interface, MAX_REGIONS_LENGTH)?; + let expected_interface: Vec> = from_slice(&expected_interface_binary) + .map_err(|_| RuntimeError::new("Invalid expected interface"))?; + let (module_result, gas_info) = env.api.get_wasmer_module(contract_addr.as_str()); + process_gas_info::(env, gas_info)?; + let module = module_result.map_err(|_| RuntimeError::new("Cannot get module"))?; + let mut exported_fns: HashMap = HashMap::new(); + for f in module.exports().functions() { + exported_fns.insert(f.name().to_string(), f.ty().clone()); + } + + let mut err_msg = "The following functions are not implemented: ".to_string(); + let mut is_err = false; + for expected_fn in expected_interface.iter() { + // if not expected + if !exported_fns + .get(expected_fn.name()) + .map_or(false, |t| t == expected_fn.ty()) + { + if is_err { + err_msg.push_str(", "); + }; + err_msg.push_str(&format!("{}: {}", expected_fn.name(), expected_fn.ty())); + is_err = true; + } + } + + if is_err { + // not expected + Ok(write_to_contract::(env, err_msg.as_bytes())?) + } else { + // as expected + Ok(0) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs index 8cd209862..12a1a66a8 100644 --- a/packages/vm/src/environment.rs +++ b/packages/vm/src/environment.rs @@ -551,6 +551,7 @@ mod tests { "ed25519_batch_verify" => Function::new_native(&store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "sha1_calculate" => Function::new_native(&store, |_a: u32| -> u64 { 0 }), "debug" => Function::new_native(&store, |_a: u32| {}), + "validate_dynamic_link_interface" => Function::new_native(&store, |_a: u32, _b: u32| { 0 }), }, }; let instance = Box::from(WasmerInstance::new(&module, &import_obj).unwrap()); diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index bf1796882..61794ffe5 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -655,6 +655,7 @@ mod tests { "ed25519_verify" => Function::new_native(&store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "ed25519_batch_verify" => Function::new_native(&store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "sha1_calculate" => Function::new_native(&store, |_a: u32| -> u64 { 0 }), + "validate_dynamic_link_interface" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), "debug" => Function::new_native(&store, |_a: u32| {}), }, }; diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index b9913df2c..de65cfcb0 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -7,7 +7,7 @@ use wasmer::{ use crate::backend::{Backend, BackendApi, Querier, Storage}; use crate::conversion::{ref_to_u32, to_u32}; -use crate::dynamic_link::dynamic_link; +use crate::dynamic_link::{dynamic_link, native_validate_dynamic_link_interface}; use crate::environment::Environment; use crate::errors::{CommunicationError, VmError, VmResult}; use crate::features::required_features_from_wasmer_instance; @@ -212,6 +212,20 @@ where Function::new_native_with_env(store, env.clone(), native_db_next), ); + // Validate specified contract have dynamic link functions + // with specified interfaces. + // Returns 0 if the interface is satisfied. + // Returns pointer of error message if the interface is not satisfied. + // The first arg is the contract address and the second arg is expected interfaces. + env_imports.insert( + "validate_dynamic_link_interface", + Function::new_native_with_env( + store, + env.clone(), + native_validate_dynamic_link_interface, + ), + ); + import_obj.register("env", env_imports); dynamic_link(module, &env, &mut import_obj); From 920b1f7ca16cfd8a3a06d7864a4b5aedcdeeadf0 Mon Sep 17 00:00:00 2001 From: loloicci Date: Wed, 28 Sep 2022 16:53:14 +0900 Subject: [PATCH 3/7] feat: make dynamic_link macro which adds validate interface automatically --- packages/derive/Cargo.toml | 2 +- packages/derive/src/dynamic_link.rs | 52 ++++++++++++++++++++++++++--- packages/derive/src/lib.rs | 12 ++++++- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/packages/derive/Cargo.toml b/packages/derive/Cargo.toml index 78271e52a..4b5a3fe27 100644 --- a/packages/derive/Cargo.toml +++ b/packages/derive/Cargo.toml @@ -15,7 +15,7 @@ proc-macro = true default = [] [dependencies] -syn = { version = "1.0", features = ["full"] } +syn = { version = "1.0", features = ["full", "parsing"] } quote = "1.0" proc-macro2 = "1.0" proc-macro-error = { version = "1", default-features = false } diff --git a/packages/derive/src/dynamic_link.rs b/packages/derive/src/dynamic_link.rs index dc751166d..745bfa919 100644 --- a/packages/derive/src/dynamic_link.rs +++ b/packages/derive/src/dynamic_link.rs @@ -1,7 +1,8 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{ - AttributeArgs, Ident, ItemTrait, Lit, Meta, NestedMeta, Signature, TraitItem, TypeParamBound, + parse_quote, AttributeArgs, Ident, ItemTrait, Lit, Meta, NestedMeta, ReturnType, Signature, + TraitItem, TypeParamBound, }; use crate::utils::{abort_by, collect_available_arg_types, has_return_value, make_typed_return}; @@ -86,9 +87,16 @@ pub fn generate_import_contract_declaration( contract_struct_id, &signatures, ); + + let mut new_trait_def = trait_def.clone(); + let method_validate_interface: TraitItem = parse_quote! { + fn validate_interface(&self, deps: cosmwasm_std::Deps) -> cosmwasm_std::StdResult<()>; + }; + new_trait_def.items.push(method_validate_interface); + if does_use_user_defined_mock { quote! { - #trait_def + #new_trait_def #[cfg(target_arch = "wasm32")] #extern_block @@ -98,7 +106,7 @@ pub fn generate_import_contract_declaration( } } else { quote! { - #trait_def + #new_trait_def #extern_block @@ -145,6 +153,30 @@ fn generate_extern_block(module_name: &str, methods: &[&Signature]) -> TokenStre } } +fn generate_validate_interface_method(methods: &[&Signature]) -> TokenStream { + let interfaces = methods.iter().map(|sig| { + let name = sig.ident.to_string(); + // -1 for &self and +1 for arg `env`, so equals to len() + let input_len = sig.inputs.len(); + let result_len = match sig.output { + ReturnType::Default => 0_usize, + ReturnType::Type(..) => 1_usize, + }; + quote! { + wasmer_types::ExportType::new(#name, ([wasmer_types::Type::I32; #input_len], [wasmer_types::Type::I32; #result_len]).into()) + } + }); + quote! { + fn validate_interface(&self, deps: cosmwasm_std::Deps) -> cosmwasm_std::StdResult<()> { + let address = self.get_address(); + let expected_interface: Vec> = vec![ + #(#interfaces,)* + ]; + deps.api.validate_dynamic_link_interface(&address, &expected_interface) + } + } +} + fn generate_implements( module_name: &str, trait_id: &Ident, @@ -154,9 +186,11 @@ fn generate_implements( let impl_funcs = methods .iter() .map(|sig| generate_serialization_func(module_name, sig)); + let impl_validate_interface = generate_validate_interface_method(methods); quote! { impl #trait_id for #struct_id { #(#impl_funcs)* + #impl_validate_interface } } } @@ -223,7 +257,7 @@ fn make_call_function_and_return( #[cfg(test)] mod tests { use super::*; - use syn::{parse_quote, ItemTrait, Signature}; + use syn::{ItemTrait, Signature}; #[test] fn make_call_function_and_return_works() { @@ -474,6 +508,16 @@ mod tests { cosmwasm_std::from_slice(&vec_result).unwrap() } } + + fn validate_interface(&self, deps: cosmwasm_std::Deps) -> cosmwasm_std::StdResult<()> { + let address = self.get_address(); + let expected_interface: Vec> = vec![ + wasmer_types::ExportType::new("foo", ([wasmer_types::Type::I32; 3usize], [wasmer_types::Type::I32; 1usize]).into()), + wasmer_types::ExportType::new("bar", ([wasmer_types::Type::I32; 1usize], [wasmer_types::Type::I32; 0usize]).into()), + wasmer_types::ExportType::new("foobar", ([wasmer_types::Type::I32; 3usize], [wasmer_types::Type::I32; 1usize]).into()), + ]; + deps.api.validate_dynamic_link_interface(&address, &expected_interface) + } } }; assert_eq!(expected.to_string(), result_code); diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index ddd58f2bf..a9eedeb2c 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -125,6 +125,10 @@ pub fn callable_point(_attr: TokenStream, item: TokenStream) -> TokenStream { /// This macro implements functions to call dynamic linked function for attributed trait. /// +/// To use this macro, the contract must declare the import +/// `wasmer-types = { version = "1.0.2", features = ["enable-serde"] }` +/// in Cargo.toml +/// /// This macro must take an attribute specifying a struct to implement the traits for. /// The trait must have `cosmwasm_std::Contract` as a supertrait and each /// methods of the trait must have `&self` receiver as its first argument. @@ -138,7 +142,7 @@ pub fn callable_point(_attr: TokenStream, item: TokenStream) -> TokenStream { /// example usage: /// /// ``` -/// use cosmwasm_std::{Addr, Contract, dynamic_link}; +/// use cosmwasm_std::{Addr, Contract, Deps, StdResult, dynamic_link}; /// /// #[derive(Contract)] /// struct ContractStruct { @@ -157,6 +161,12 @@ pub fn callable_point(_attr: TokenStream, item: TokenStream) -> TokenStream { /// fn callable_point_on_another_contract(&self, x: i32) -> i32 { /// 42 /// } +/// +/// // validate_interface is auto generated function from `dynamic_link` macro. +/// // this function must be defined in the mock. +/// fn validate_interface(&self, dep: Deps) -> StdResult<()> { +/// Ok(()) +/// } /// } /// ``` #[proc_macro_error] From dea61a1ddf9f1fd53fb77f69d8e3bb50db05303f Mon Sep 17 00:00:00 2001 From: loloicci Date: Wed, 28 Sep 2022 16:52:47 +0900 Subject: [PATCH 4/7] feat: update dynamic caller contract to use interface detection and update contracts depending cosmwasm --- contracts/burner/Cargo.lock | 1 + contracts/call-number/Cargo.lock | 1 + contracts/call-number/Cargo.toml | 1 + contracts/crypto-verify/Cargo.lock | 1 + contracts/dynamic-callee-contract/Cargo.lock | 1 + contracts/dynamic-callee-contract/Cargo.toml | 1 + contracts/dynamic-caller-contract/Cargo.lock | 1 + contracts/dynamic-caller-contract/Cargo.toml | 1 + .../schema/execute_msg.json | 24 ++++++++++++++++ .../dynamic-caller-contract/src/contract.rs | 28 ++++++++++++++++++- contracts/dynamic-caller-contract/src/msg.rs | 2 ++ contracts/hackatom/Cargo.lock | 1 + contracts/ibc-reflect-send/Cargo.lock | 1 + contracts/ibc-reflect/Cargo.lock | 1 + contracts/number/Cargo.lock | 1 + contracts/queue/Cargo.lock | 1 + contracts/reflect/Cargo.lock | 1 + contracts/staking/Cargo.lock | 1 + contracts/voting-with-uuid/Cargo.lock | 1 + 19 files changed, 69 insertions(+), 1 deletion(-) diff --git a/contracts/burner/Cargo.lock b/contracts/burner/Cargo.lock index 24d502695..488779b7e 100644 --- a/contracts/burner/Cargo.lock +++ b/contracts/burner/Cargo.lock @@ -168,6 +168,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] diff --git a/contracts/call-number/Cargo.lock b/contracts/call-number/Cargo.lock index 66421aeca..a4ebcd189 100644 --- a/contracts/call-number/Cargo.lock +++ b/contracts/call-number/Cargo.lock @@ -171,6 +171,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] diff --git a/contracts/call-number/Cargo.toml b/contracts/call-number/Cargo.toml index e2a4541c6..9d29fc59c 100644 --- a/contracts/call-number/Cargo.toml +++ b/contracts/call-number/Cargo.toml @@ -30,6 +30,7 @@ cosmwasm-storage = { path = "../../packages/storage", features = ["iterator"] } schemars = "0.8.1" serde = { version = "1.0.125", default-features = false, features = ["derive"] } thiserror = { version = "1.0.24" } +wasmer-types = { version = "1.0.2", features = ["enable-serde"] } [dev-dependencies] cosmwasm-schema = { path = "../../packages/schema" } diff --git a/contracts/crypto-verify/Cargo.lock b/contracts/crypto-verify/Cargo.lock index fb4476150..4ef08469b 100644 --- a/contracts/crypto-verify/Cargo.lock +++ b/contracts/crypto-verify/Cargo.lock @@ -170,6 +170,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] diff --git a/contracts/dynamic-callee-contract/Cargo.lock b/contracts/dynamic-callee-contract/Cargo.lock index c73fecac6..68eeba5c1 100644 --- a/contracts/dynamic-callee-contract/Cargo.lock +++ b/contracts/dynamic-callee-contract/Cargo.lock @@ -157,6 +157,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] diff --git a/contracts/dynamic-callee-contract/Cargo.toml b/contracts/dynamic-callee-contract/Cargo.toml index 7cac1938a..c9595ee30 100644 --- a/contracts/dynamic-callee-contract/Cargo.toml +++ b/contracts/dynamic-callee-contract/Cargo.toml @@ -30,6 +30,7 @@ cosmwasm-storage = { path = "../../packages/storage", features = ["iterator"] } schemars = "0.8.1" serde = { version = "1.0.125", default-features = false, features = ["derive"] } thiserror = { version = "1.0.24" } +wasmer-types = { version = "1.0.2", features = ["enable-serde"] } [dev-dependencies] cosmwasm-schema = { path = "../../packages/schema" } diff --git a/contracts/dynamic-caller-contract/Cargo.lock b/contracts/dynamic-caller-contract/Cargo.lock index e76146bc6..8c5ebd3e2 100644 --- a/contracts/dynamic-caller-contract/Cargo.lock +++ b/contracts/dynamic-caller-contract/Cargo.lock @@ -157,6 +157,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] diff --git a/contracts/dynamic-caller-contract/Cargo.toml b/contracts/dynamic-caller-contract/Cargo.toml index 06ad7eee6..dd670af7a 100644 --- a/contracts/dynamic-caller-contract/Cargo.toml +++ b/contracts/dynamic-caller-contract/Cargo.toml @@ -30,6 +30,7 @@ cosmwasm-storage = { path = "../../packages/storage", features = ["iterator"] } schemars = "0.8.1" serde = { version = "1.0.125", default-features = false, features = ["derive"] } thiserror = { version = "1.0.24" } +wasmer-types = { version = "1.0.2", features = ["enable-serde"] } [dev-dependencies] cosmwasm-schema = { path = "../../packages/schema" } diff --git a/contracts/dynamic-caller-contract/schema/execute_msg.json b/contracts/dynamic-caller-contract/schema/execute_msg.json index c1fe958b4..02e8b282d 100644 --- a/contracts/dynamic-caller-contract/schema/execute_msg.json +++ b/contracts/dynamic-caller-contract/schema/execute_msg.json @@ -45,6 +45,30 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "validate_interface" + ], + "properties": { + "validate_interface": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "validate_interface_err" + ], + "properties": { + "validate_interface_err": { + "type": "object" + } + }, + "additionalProperties": false } ], "definitions": { diff --git a/contracts/dynamic-caller-contract/src/contract.rs b/contracts/dynamic-caller-contract/src/contract.rs index 8d4a71e33..957ec3768 100644 --- a/contracts/dynamic-caller-contract/src/contract.rs +++ b/contracts/dynamic-caller-contract/src/contract.rs @@ -67,6 +67,10 @@ impl Callee for CalleeContract { fn do_panic(&self) { panic!() } + + fn validate_interface(&self, _deps: Deps) -> cosmwasm_std::StdResult<()> { + Ok(()) + } } // Note, you can use StdResult in some functions where you do not @@ -96,11 +100,13 @@ pub fn execute( ExecuteMsg::Ping { ping_num } => try_ping(deps, ping_num), ExecuteMsg::TryReEntrancy {} => try_re_entrancy(deps, env), ExecuteMsg::DoPanic {} => try_do_panic(deps, env), + ExecuteMsg::ValidateInterface {} => try_validate_interface(deps.as_ref(), env), + ExecuteMsg::ValidateInterfaceErr {} => try_validate_interface_err(deps.as_ref(), env), } } pub fn try_ping(deps: DepsMut, ping_num: Uint128) -> Result { - let address = from_slice(&deps.storage.get(b"dynamic_callee_contract").unwrap())?; + let address: Addr = from_slice(&deps.storage.get(b"dynamic_callee_contract").unwrap())?; let contract = CalleeContract { address }; let pong_ret = contract.pong(ping_num.u128() as u64); let struct_ret = contract.pong_with_struct(ExampleStruct { @@ -144,6 +150,26 @@ pub fn try_do_panic(deps: DepsMut, _env: Env) -> Result Ok(Response::default()) } +pub fn try_validate_interface(deps: Deps, _env: Env) -> Result { + let address = from_slice(&deps.storage.get(b"dynamic_callee_contract").unwrap())?; + let contract = CalleeContract { address }; + contract.validate_interface(deps)?; + Ok(Response::default()) +} + +// should error +pub fn try_validate_interface_err(deps: Deps, _env: Env) -> Result { + let address = from_slice(&deps.storage.get(b"dynamic_callee_contract").unwrap())?; + let err_interface: Vec> = + vec![wasmer_types::ExportType::new( + "not_exist", + ([wasmer_types::Type::I32], [wasmer_types::Type::I32]).into(), + )]; + deps.api + .validate_dynamic_link_interface(&address, &err_interface)?; + Ok(Response::default()) +} + #[callable_point] fn should_never_be_called(_deps: Deps, _env: Env) {} diff --git a/contracts/dynamic-caller-contract/src/msg.rs b/contracts/dynamic-caller-contract/src/msg.rs index 53c702e6b..7bfe3a17f 100644 --- a/contracts/dynamic-caller-contract/src/msg.rs +++ b/contracts/dynamic-caller-contract/src/msg.rs @@ -13,6 +13,8 @@ pub enum ExecuteMsg { Ping { ping_num: Uint128 }, TryReEntrancy {}, DoPanic {}, + ValidateInterface {}, + ValidateInterfaceErr {}, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] diff --git a/contracts/hackatom/Cargo.lock b/contracts/hackatom/Cargo.lock index 184615c62..02ae5313a 100644 --- a/contracts/hackatom/Cargo.lock +++ b/contracts/hackatom/Cargo.lock @@ -157,6 +157,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] diff --git a/contracts/ibc-reflect-send/Cargo.lock b/contracts/ibc-reflect-send/Cargo.lock index a87f582f8..637c5a637 100644 --- a/contracts/ibc-reflect-send/Cargo.lock +++ b/contracts/ibc-reflect-send/Cargo.lock @@ -157,6 +157,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] diff --git a/contracts/ibc-reflect/Cargo.lock b/contracts/ibc-reflect/Cargo.lock index 24aa2cd39..ef18c408e 100644 --- a/contracts/ibc-reflect/Cargo.lock +++ b/contracts/ibc-reflect/Cargo.lock @@ -157,6 +157,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] diff --git a/contracts/number/Cargo.lock b/contracts/number/Cargo.lock index 04ec824f5..00f0dbd51 100644 --- a/contracts/number/Cargo.lock +++ b/contracts/number/Cargo.lock @@ -157,6 +157,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] diff --git a/contracts/queue/Cargo.lock b/contracts/queue/Cargo.lock index 5981bfdda..66085287e 100644 --- a/contracts/queue/Cargo.lock +++ b/contracts/queue/Cargo.lock @@ -157,6 +157,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] diff --git a/contracts/reflect/Cargo.lock b/contracts/reflect/Cargo.lock index f34d799a9..5e3aa08cc 100644 --- a/contracts/reflect/Cargo.lock +++ b/contracts/reflect/Cargo.lock @@ -157,6 +157,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] diff --git a/contracts/staking/Cargo.lock b/contracts/staking/Cargo.lock index 0da4f526e..a6b08c792 100644 --- a/contracts/staking/Cargo.lock +++ b/contracts/staking/Cargo.lock @@ -157,6 +157,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] diff --git a/contracts/voting-with-uuid/Cargo.lock b/contracts/voting-with-uuid/Cargo.lock index 06247119c..d8eb2d48d 100644 --- a/contracts/voting-with-uuid/Cargo.lock +++ b/contracts/voting-with-uuid/Cargo.lock @@ -157,6 +157,7 @@ dependencies = [ "serde-json-wasm", "thiserror", "uuid", + "wasmer-types", ] [[package]] From 09ae13e71a06597b22a29b46eb426707fce8866e Mon Sep 17 00:00:00 2001 From: loloicci Date: Thu, 20 Oct 2022 20:12:52 +0900 Subject: [PATCH 5/7] doc: add some comments --- packages/std/src/imports.rs | 4 ++++ packages/std/src/mock.rs | 2 +- packages/std/src/traits.rs | 3 +++ packages/vm/src/dynamic_link.rs | 1 + packages/vm/src/testing/mock.rs | 4 ++-- 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index 23cc6379e..6101761b6 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -348,6 +348,10 @@ impl Api for ExternalApi { } } + // this calls the API to validate interface + // + // contract is the address of the contract to validate. + // interface is the arg for expected interface that the contract has. fn validate_dynamic_link_interface( &self, contract: &Addr, diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index 4fcdf5c03..11f33c1f0 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -187,7 +187,7 @@ impl Api for MockApi { println!("{}", message); } - // always true in mock + // always returning true in mock fn validate_dynamic_link_interface( &self, _contract: &Addr, diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 79e5dfab6..af4a99ebf 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -120,6 +120,9 @@ pub trait Api { fn sha1_calculate(&self, inputs: &[&[u8]]) -> Result<[u8; 20], HashCalculationError>; + /// This calls the API to validate interface + /// Contract is the address of the contract to validate. + /// Interface is the arg for expected interface that the contract has. fn validate_dynamic_link_interface( &self, contract: &Addr, diff --git a/packages/vm/src/dynamic_link.rs b/packages/vm/src/dynamic_link.rs index da43c6ea3..29383658d 100644 --- a/packages/vm/src/dynamic_link.rs +++ b/packages/vm/src/dynamic_link.rs @@ -260,6 +260,7 @@ where exported_fns.insert(f.name().to_string(), f.ty().clone()); } + // No gas fee for comparison now let mut err_msg = "The following functions are not implemented: ".to_string(); let mut is_err = false; for expected_fn in expected_interface.iter() { diff --git a/packages/vm/src/testing/mock.rs b/packages/vm/src/testing/mock.rs index a67ad0fbb..f93f8584f 100644 --- a/packages/vm/src/testing/mock.rs +++ b/packages/vm/src/testing/mock.rs @@ -44,8 +44,8 @@ pub fn mock_backend_with_balances( type MockInstance = Instance; thread_local! { - // INSTANCE_CACHE is intended to replace wasmvm's cache layer in the mock. - // Unlike wasmvm, you have to initialize it yourself in the place where you test the dynamic call. + // INSTANCE_CACHE and MODULE_CACHE are intended to replace wasmvm's cache layer in the mock. + // Unlike wasmvm, you have to initialize them yourself in the place where you test the dynamic call. pub static INSTANCE_CACHE: RwLock>> = RwLock::new(HashMap::new()); pub static MODULE_CACHE: RwLock>> = RwLock::new(HashMap::new()); } From 045ac42de920cefa0688b4017010a675595678da Mon Sep 17 00:00:00 2001 From: loloicci Date: Fri, 21 Oct 2022 16:23:41 +0900 Subject: [PATCH 6/7] ci: upgrade Rust in CI contract building test This PR upgrade Rust to 1.57.0 in some part of CI. When CI uses rust 1.51, error: linking with `cc` failed: exit code: 1 is caused in mac OS v12. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43cf6ca90..17b3e664b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: - toolchain: 1.51.0 + toolchain: 1.57.0 target: wasm32-unknown-unknown profile: minimal override: true From 1e437f36cc4393dbfe1b19e311cbdd788a94eb86 Mon Sep 17 00:00:00 2001 From: loloicci Date: Tue, 25 Oct 2022 16:50:07 +0900 Subject: [PATCH 7/7] fix: modify a error message --- packages/vm/src/dynamic_link.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vm/src/dynamic_link.rs b/packages/vm/src/dynamic_link.rs index 29383658d..e8a819f8c 100644 --- a/packages/vm/src/dynamic_link.rs +++ b/packages/vm/src/dynamic_link.rs @@ -248,7 +248,7 @@ where { let contract_addr_raw = read_region(&env.memory(), address, 64)?; let contract_addr: Addr = from_slice(&contract_addr_raw) - .map_err(|_| RuntimeError::new("Invalid contract address to detect interface"))?; + .map_err(|_| RuntimeError::new("Invalid contract address to validate interface"))?; let expected_interface_binary = read_region(&env.memory(), interface, MAX_REGIONS_LENGTH)?; let expected_interface: Vec> = from_slice(&expected_interface_binary) .map_err(|_| RuntimeError::new("Invalid expected interface"))?;