diff --git a/.circleci/config.yml b/.circleci/config.yml index 2d2ac008d4..09dbda2c7f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -173,17 +173,17 @@ jobs: working_directory: ~/project/packages/vm command: cargo build --locked - run: - name: Build with iterator + name: Build with all features working_directory: ~/project/packages/vm - command: cargo build --locked --features iterator + command: cargo build --locked --features iterator,staking,stargate - run: name: Test working_directory: ~/project/packages/vm command: cargo test --locked - run: - name: Test with iterator + name: Test with all features working_directory: ~/project/packages/vm - command: cargo test --locked --features iterator + command: cargo test --locked --features iterator,staking,stargate - save_cache: paths: - /usr/local/cargo/registry @@ -208,17 +208,17 @@ jobs: working_directory: ~/project/packages/vm command: cargo build --locked --features cranelift - run: - name: Build with iterator + name: Build with all features working_directory: ~/project/packages/vm - command: cargo build --locked --features cranelift,iterator + command: cargo build --locked --features cranelift,iterator,staking,stargate - run: name: Test working_directory: ~/project/packages/vm command: cargo test --locked --features cranelift - run: - name: Test with iterator + name: Test with all features working_directory: ~/project/packages/vm - command: cargo test --locked --features cranelift,iterator + command: cargo test --locked --features cranelift,iterator,staking,stargate - save_cache: paths: - /usr/local/cargo/registry diff --git a/packages/vm/Cargo.toml b/packages/vm/Cargo.toml index 9423985a5c..8863e07aea 100644 --- a/packages/vm/Cargo.toml +++ b/packages/vm/Cargo.toml @@ -24,6 +24,8 @@ backtraces = [] # we keep this optional, to allow possible future integration (or different Cosmos Backends) iterator = ["cosmwasm-std/iterator"] staking = ["cosmwasm-std/staking"] +# this enables all stargate-related functionality, including the ibc entry points +stargate = ["cosmwasm-std/stargate"] # Use cranelift backend instead of singlepass. This is required for development on Windows. cranelift = ["wasmer/cranelift"] diff --git a/packages/vm/src/calls.rs b/packages/vm/src/calls.rs index 470a3f3a6b..974f965c81 100644 --- a/packages/vm/src/calls.rs +++ b/packages/vm/src/calls.rs @@ -168,7 +168,7 @@ where /// Calls a function with the given arguments. /// The exported function must return exactly one result (an offset to the result Region). -fn call_raw( +pub(crate) fn call_raw( instance: &mut Instance, name: &str, args: &[&[u8]], diff --git a/packages/vm/src/ibc_calls.rs b/packages/vm/src/ibc_calls.rs new file mode 100644 index 0000000000..fe07a20fd4 --- /dev/null +++ b/packages/vm/src/ibc_calls.rs @@ -0,0 +1,233 @@ +#![cfg(feature = "stargate")] + +use cosmwasm_std::{ + ContractResult, Env, IbcAcknowledgement, IbcBasicResponse, IbcChannel, IbcPacket, + IbcReceiveResponse, +}; +use schemars::JsonSchema; +use serde::de::DeserializeOwned; +use std::fmt; + +use crate::backend::{Api, Querier, Storage}; +use crate::calls::call_raw; +use crate::errors::VmResult; +use crate::instance::Instance; +use crate::serde::{from_slice, to_vec}; + +const MAX_LENGTH_IBC: usize = 100_000; + +pub fn call_ibc_channel_open( + instance: &mut Instance, + env: &Env, + channel: &IbcChannel, +) -> VmResult> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, +{ + let env = to_vec(env)?; + let channel = to_vec(channel)?; + let data = call_ibc_channel_open_raw(instance, &env, &channel)?; + let result: ContractResult<()> = from_slice(&data)?; + Ok(result) +} + +pub fn call_ibc_channel_connect( + instance: &mut Instance, + env: &Env, + channel: &IbcChannel, +) -> VmResult>> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, + U: DeserializeOwned + Clone + fmt::Debug + JsonSchema + PartialEq, +{ + let env = to_vec(env)?; + let channel = to_vec(channel)?; + let data = call_ibc_channel_connect_raw(instance, &env, &channel)?; + let result = from_slice(&data)?; + Ok(result) +} + +pub fn call_ibc_channel_close( + instance: &mut Instance, + env: &Env, + channel: &IbcChannel, +) -> VmResult>> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, + U: DeserializeOwned + Clone + fmt::Debug + JsonSchema + PartialEq, +{ + let env = to_vec(env)?; + let channel = to_vec(channel)?; + let data = call_ibc_channel_close_raw(instance, &env, &channel)?; + let result = from_slice(&data)?; + Ok(result) +} + +pub fn call_ibc_packet_receive( + instance: &mut Instance, + env: &Env, + packet: &IbcPacket, +) -> VmResult>> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, + U: DeserializeOwned + Clone + fmt::Debug + JsonSchema + PartialEq, +{ + let env = to_vec(env)?; + let packet = to_vec(packet)?; + let data = call_ibc_packet_receive_raw(instance, &env, &packet)?; + let result = from_slice(&data)?; + Ok(result) +} + +pub fn call_ibc_packet_ack( + instance: &mut Instance, + env: &Env, + ack: &IbcAcknowledgement, +) -> VmResult>> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, + U: DeserializeOwned + Clone + fmt::Debug + JsonSchema + PartialEq, +{ + let env = to_vec(env)?; + let ack = to_vec(ack)?; + let data = call_ibc_packet_ack_raw(instance, &env, &ack)?; + let result = from_slice(&data)?; + Ok(result) +} + +pub fn call_ibc_packet_timeout( + instance: &mut Instance, + env: &Env, + packet: &IbcPacket, +) -> VmResult>> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, + U: DeserializeOwned + Clone + fmt::Debug + JsonSchema + PartialEq, +{ + let env = to_vec(env)?; + let packet = to_vec(packet)?; + let data = call_ibc_packet_timeout_raw(instance, &env, &packet)?; + let result = from_slice(&data)?; + Ok(result) +} + +pub fn call_ibc_channel_open_raw( + instance: &mut Instance, + env: &[u8], + channel: &[u8], +) -> VmResult> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, +{ + instance.set_storage_readonly(false); + call_raw( + instance, + "ibc_channel_open", + &[env, channel], + MAX_LENGTH_IBC, + ) +} + +pub fn call_ibc_channel_connect_raw( + instance: &mut Instance, + env: &[u8], + channel: &[u8], +) -> VmResult> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, +{ + instance.set_storage_readonly(false); + call_raw( + instance, + "ibc_channel_connect", + &[env, channel], + MAX_LENGTH_IBC, + ) +} + +pub fn call_ibc_channel_close_raw( + instance: &mut Instance, + env: &[u8], + channel: &[u8], +) -> VmResult> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, +{ + instance.set_storage_readonly(false); + call_raw( + instance, + "ibc_channel_close", + &[env, channel], + MAX_LENGTH_IBC, + ) +} + +pub fn call_ibc_packet_receive_raw( + instance: &mut Instance, + env: &[u8], + packet: &[u8], +) -> VmResult> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, +{ + instance.set_storage_readonly(false); + call_raw( + instance, + "ibc_packet_receive", + &[env, packet], + MAX_LENGTH_IBC, + ) +} + +pub fn call_ibc_packet_ack_raw( + instance: &mut Instance, + env: &[u8], + ack: &[u8], +) -> VmResult> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, +{ + instance.set_storage_readonly(false); + call_raw(instance, "ibc_packet_ack", &[env, ack], MAX_LENGTH_IBC) +} + +pub fn call_ibc_packet_timeout_raw( + instance: &mut Instance, + env: &[u8], + packet: &[u8], +) -> VmResult> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, +{ + instance.set_storage_readonly(false); + call_raw( + instance, + "ibc_packet_timeout", + &[env, packet], + MAX_LENGTH_IBC, + ) +} diff --git a/packages/vm/src/lib.rs b/packages/vm/src/lib.rs index fb57ba199f..71fdf86ccf 100644 --- a/packages/vm/src/lib.rs +++ b/packages/vm/src/lib.rs @@ -9,6 +9,8 @@ mod conversion; mod environment; mod errors; mod features; +#[cfg(feature = "stargate")] +mod ibc_calls; mod imports; mod instance; mod limited; @@ -32,6 +34,13 @@ pub use crate::errors::{ VmError, VmResult, }; pub use crate::features::features_from_csv; +#[cfg(feature = "stargate")] +pub use crate::ibc_calls::{ + call_ibc_channel_close, call_ibc_channel_close_raw, call_ibc_channel_connect, + call_ibc_channel_connect_raw, call_ibc_channel_open, call_ibc_channel_open_raw, + call_ibc_packet_ack, call_ibc_packet_ack_raw, call_ibc_packet_receive, + call_ibc_packet_receive_raw, call_ibc_packet_timeout, call_ibc_packet_timeout_raw, +}; pub use crate::instance::{GasReport, Instance, InstanceOptions}; pub use crate::serde::{from_slice, to_vec}; pub use crate::size::Size; diff --git a/packages/vm/src/testing/ibc_calls.rs b/packages/vm/src/testing/ibc_calls.rs new file mode 100644 index 0000000000..d109baa4f2 --- /dev/null +++ b/packages/vm/src/testing/ibc_calls.rs @@ -0,0 +1,117 @@ +#![cfg(feature = "stargate")] +use schemars::JsonSchema; +use serde::de::DeserializeOwned; +use std::fmt; + +use cosmwasm_std::{ + ContractResult, Env, IbcAcknowledgement, IbcBasicResponse, IbcChannel, IbcPacket, + IbcReceiveResponse, +}; + +use crate::ibc_calls::{ + call_ibc_channel_close, call_ibc_channel_connect, call_ibc_channel_open, call_ibc_packet_ack, + call_ibc_packet_receive, call_ibc_packet_timeout, +}; +use crate::instance::Instance; +use crate::{Api, Querier, Storage}; + +// ibc_channel_open mimicks the call signature of the smart contracts. +// thus it moves env and channel rather than take them as reference. +// this is inefficient here, but only used in test code +pub fn ibc_channel_open( + instance: &mut Instance, + env: Env, + channel: IbcChannel, +) -> ContractResult<()> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, +{ + call_ibc_channel_open(instance, &env, &channel).expect("VM error") +} + +// ibc_channel_connect mimicks the call signature of the smart contracts. +// thus it moves env and channel rather than take them as reference. +// this is inefficient here, but only used in test code +pub fn ibc_channel_connect( + instance: &mut Instance, + env: Env, + channel: IbcChannel, +) -> ContractResult> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, + U: DeserializeOwned + Clone + PartialEq + JsonSchema + fmt::Debug, +{ + call_ibc_channel_connect(instance, &env, &channel).expect("VM error") +} + +// ibc_channel_close mimicks the call signature of the smart contracts. +// thus it moves env and channel rather than take them as reference. +// this is inefficient here, but only used in test code +pub fn ibc_channel_close( + instance: &mut Instance, + env: Env, + channel: IbcChannel, +) -> ContractResult> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, + U: DeserializeOwned + Clone + PartialEq + JsonSchema + fmt::Debug, +{ + call_ibc_channel_close(instance, &env, &channel).expect("VM error") +} + +// ibc_packet_receive mimicks the call signature of the smart contracts. +// thus it moves env and packet rather than take them as reference. +// this is inefficient here, but only used in test code +pub fn ibc_packet_receive( + instance: &mut Instance, + env: Env, + packet: IbcPacket, +) -> ContractResult> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, + U: DeserializeOwned + Clone + PartialEq + JsonSchema + fmt::Debug, +{ + call_ibc_packet_receive(instance, &env, &packet).expect("VM error") +} + +// ibc_packet_ack mimicks the call signature of the smart contracts. +// thus it moves env and acknowledgement rather than take them as reference. +// this is inefficient here, but only used in test code +pub fn ibc_packet_ack( + instance: &mut Instance, + env: Env, + ack: IbcAcknowledgement, +) -> ContractResult> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, + U: DeserializeOwned + Clone + PartialEq + JsonSchema + fmt::Debug, +{ + call_ibc_packet_ack(instance, &env, &ack).expect("VM error") +} + +// ibc_packet_timeout mimicks the call signature of the smart contracts. +// thus it moves env and packet rather than take them as reference. +// this is inefficient here, but only used in test code +pub fn ibc_packet_timeout( + instance: &mut Instance, + env: Env, + packet: IbcPacket, +) -> ContractResult> +where + A: Api + 'static, + S: Storage + 'static, + Q: Querier + 'static, + U: DeserializeOwned + Clone + PartialEq + JsonSchema + fmt::Debug, +{ + call_ibc_packet_timeout(instance, &env, &packet).expect("VM error") +} diff --git a/packages/vm/src/testing/instance.rs b/packages/vm/src/testing/instance.rs index 8d6ac75238..e1eec97fb1 100644 --- a/packages/vm/src/testing/instance.rs +++ b/packages/vm/src/testing/instance.rs @@ -92,6 +92,18 @@ pub struct MockInstanceOptions<'a> { pub memory_limit: Option, } +impl MockInstanceOptions<'_> { + #[cfg(feature = "stargate")] + fn default_features() -> HashSet { + features_from_csv("staking,stargate") + } + + #[cfg(not(feature = "stargate"))] + fn default_features() -> HashSet { + features_from_csv("staking") + } +} + impl Default for MockInstanceOptions<'_> { fn default() -> Self { Self { @@ -101,7 +113,7 @@ impl Default for MockInstanceOptions<'_> { backend_error: None, // instance - supported_features: features_from_csv("staking"), + supported_features: Self::default_features(), gas_limit: DEFAULT_GAS_LIMIT, print_debug: DEFAULT_PRINT_DEBUG, memory_limit: DEFAULT_MEMORY_LIMIT, diff --git a/packages/vm/src/testing/mod.rs b/packages/vm/src/testing/mod.rs index d47c13c093..a31bc76473 100644 --- a/packages/vm/src/testing/mod.rs +++ b/packages/vm/src/testing/mod.rs @@ -1,12 +1,18 @@ // The external interface is `use cosmwasm_vm::testing::X` for all integration testing symbols, no matter where they live internally. mod calls; +mod ibc_calls; mod instance; mod mock; mod querier; mod storage; pub use calls::{handle, init, migrate, query}; +#[cfg(feature = "stargate")] +pub use ibc_calls::{ + ibc_channel_close, ibc_channel_connect, ibc_channel_open, ibc_packet_ack, ibc_packet_receive, + ibc_packet_timeout, +}; pub use instance::{ mock_instance, mock_instance_options, mock_instance_with_balances, mock_instance_with_failing_api, mock_instance_with_gas_limit, mock_instance_with_options,