diff --git a/CHANGELOG.md b/CHANGELOG.md index ea10883b8a..59ff2afa77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ via `into` or `try_into`. Option to simplify response construction. - Env uses `HumanAddr` for `message.sender` and `contract_address` - Remove `Api` argument from `mock_env` +- Implement `From<&[u8]>` and `From>` for `CanonicalAddr` **cosmwasm-vm** diff --git a/contracts/hackatom/schema/handle_msg.json b/contracts/hackatom/schema/handle_msg.json index 32518d25b7..88aeaf646f 100644 --- a/contracts/hackatom/schema/handle_msg.json +++ b/contracts/hackatom/schema/handle_msg.json @@ -73,6 +73,18 @@ "type": "object" } } + }, + { + "description": "Starting with CosmWasm 0.10, some API calls return user errors back to the contract. This triggers such user errors, ensuring the transaction does not fail in the backend.", + "type": "object", + "required": [ + "user_errors_in_api_calls" + ], + "properties": { + "user_errors_in_api_calls": { + "type": "object" + } + } } ] } diff --git a/contracts/hackatom/src/contract.rs b/contracts/hackatom/src/contract.rs index 4dd92df57b..0e7328056e 100644 --- a/contracts/hackatom/src/contract.rs +++ b/contracts/hackatom/src/contract.rs @@ -51,6 +51,9 @@ pub enum HandleMsg { AllocateLargeMemory {}, /// Trigger a panic to ensure framework handles gracefully Panic {}, + /// Starting with CosmWasm 0.10, some API calls return user errors back to the contract. + /// This triggers such user errors, ensuring the transaction does not fail in the backend. + UserErrorsInApiCalls {}, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -134,6 +137,7 @@ pub fn handle( HandleMsg::MemoryLoop {} => do_memory_loop(), HandleMsg::AllocateLargeMemory {} => do_allocate_large_memory(), HandleMsg::Panic {} => do_panic(), + HandleMsg::UserErrorsInApiCalls {} => do_user_errors_in_api_calls(&deps.api), } } @@ -219,6 +223,69 @@ fn do_panic() -> StdResult { panic!("This page intentionally faulted"); } +fn do_user_errors_in_api_calls(api: &A) -> StdResult { + // Canonicalize + + let empty = HumanAddr::from(""); + match api.canonical_address(&empty).unwrap_err() { + StdError::GenericErr { .. } => {} + err => { + return Err(StdError::generic_err(format!( + "Unexpected error in do_user_errors_in_api_calls: {:?}", + err + ))) + } + } + + let invalid_bech32 = HumanAddr::from("bn93hg934hg08q340g8u4jcau3"); + match api.canonical_address(&invalid_bech32).unwrap_err() { + StdError::GenericErr { .. } => {} + err => { + return Err(StdError::generic_err(format!( + "Unexpected error in do_user_errors_in_api_calls: {:?}", + err + ))) + } + } + + // Humanize + + let empty: CanonicalAddr = vec![].into(); + match api.human_address(&empty).unwrap_err() { + StdError::GenericErr { .. } => {} + err => { + return Err(StdError::generic_err(format!( + "Unexpected error in do_user_errors_in_api_calls: {:?}", + err + ))) + } + } + + let too_short: CanonicalAddr = vec![0xAA, 0xBB, 0xCC].into(); + match api.human_address(&too_short).unwrap_err() { + StdError::GenericErr { .. } => {} + err => { + return Err(StdError::generic_err(format!( + "Unexpected error in do_user_errors_in_api_calls: {:?}", + err + ))) + } + } + + let wrong_length: CanonicalAddr = vec![0xA6; 17].into(); + match api.human_address(&wrong_length).unwrap_err() { + StdError::GenericErr { .. } => {} + err => { + return Err(StdError::generic_err(format!( + "Unexpected error in do_user_errors_in_api_calls: {:?}", + err + ))) + } + } + + Ok(HandleResponse::default()) +} + pub fn query( deps: &Extern, msg: QueryMsg, @@ -502,6 +569,22 @@ mod tests { let _ = handle(&mut deps, handle_env, HandleMsg::Panic {}); } + #[test] + fn handle_user_errors_in_api_calls() { + let mut deps = mock_dependencies(20, &[]); + + let init_msg = InitMsg { + verifier: HumanAddr::from("verifies"), + beneficiary: HumanAddr::from("benefits"), + }; + let init_env = mock_env("creator", &coins(1000, "earth")); + let init_res = init(&mut deps, init_env, init_msg).unwrap(); + assert_eq!(0, init_res.messages.len()); + + let handle_env = mock_env("anyone", &[]); + handle(&mut deps, handle_env, HandleMsg::UserErrorsInApiCalls {}).unwrap(); + } + #[test] fn query_recursive() { // the test framework doesn't handle contracts querying contracts yet, diff --git a/contracts/hackatom/tests/integration.rs b/contracts/hackatom/tests/integration.rs index 8ad6fc332a..9a15e7a814 100644 --- a/contracts/hackatom/tests/integration.rs +++ b/contracts/hackatom/tests/integration.rs @@ -18,22 +18,35 @@ //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) use cosmwasm_std::{ - coins, from_binary, log, AllBalanceResponse, BankMsg, HandleResponse, HandleResult, HumanAddr, - InitResponse, InitResult, MigrateResponse, StdError, + coins, from_binary, log, to_vec, AllBalanceResponse, BankMsg, Empty, HandleResponse, + HandleResult, HumanAddr, InitResponse, InitResult, MigrateResponse, StdError, }; use cosmwasm_vm::{ - from_slice, + call_handle, from_slice, testing::{ handle, init, migrate, mock_env, mock_instance, mock_instance_with_balances, query, test_io, MOCK_CONTRACT_ADDR, }, - Api, Storage, + Api, Storage, VmError, }; use hackatom::contract::{HandleMsg, InitMsg, MigrateMsg, QueryMsg, State, CONFIG_KEY}; static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/hackatom.wasm"); +fn make_init_msg() -> (InitMsg, HumanAddr) { + let verifier = HumanAddr::from("verifies"); + let beneficiary = HumanAddr::from("benefits"); + let creator = HumanAddr::from("creator"); + ( + InitMsg { + verifier: verifier.clone(), + beneficiary: beneficiary.clone(), + }, + creator, + ) +} + #[test] fn proper_initialization() { let mut deps = mock_instance(WASM, &[]); @@ -270,6 +283,43 @@ fn handle_release_fails_for_wrong_sender() { ); } +#[test] +fn handle_panic() { + let mut deps = mock_instance(WASM, &[]); + + let (init_msg, creator) = make_init_msg(); + let init_env = mock_env(creator.as_str(), &[]); + let init_res: InitResponse = init(&mut deps, init_env, init_msg).unwrap(); + assert_eq!(0, init_res.messages.len()); + + let handle_env = mock_env(creator.as_str(), &[]); + // panic inside contract should not panic out here + // Note: we need to use the production-call, not the testing call (which unwraps any vm error) + let handle_res = call_handle::<_, _, _, Empty>( + &mut deps, + &handle_env, + &to_vec(&HandleMsg::Panic {}).unwrap(), + ); + match handle_res.unwrap_err() { + // TODO: Don't accept GasDepletion here (https://github.com/CosmWasm/cosmwasm/issues/501) + VmError::RuntimeErr { .. } | VmError::GasDepletion => {} + err => panic!("Unexpected error: {:?}", err), + } +} + +#[test] +fn handle_user_errors_in_api_calls() { + let mut deps = mock_instance(WASM, &[]); + + let (init_msg, creator) = make_init_msg(); + let init_env = mock_env(creator.as_str(), &[]); + let _init_res: InitResponse = init(&mut deps, init_env, init_msg).unwrap(); + + let handle_env = mock_env(creator.as_str(), &[]); + let _handle_res: HandleResponse = + handle(&mut deps, handle_env, HandleMsg::UserErrorsInApiCalls {}).unwrap(); +} + #[test] fn passes_io_tests() { let mut deps = mock_instance(WASM, &[]); @@ -280,42 +330,6 @@ fn passes_io_tests() { mod singlepass_tests { use super::*; - use cosmwasm_std::{to_vec, Empty}; - use cosmwasm_vm::call_handle; - - fn make_init_msg() -> (InitMsg, HumanAddr) { - let verifier = HumanAddr::from("verifies"); - let beneficiary = HumanAddr::from("benefits"); - let creator = HumanAddr::from("creator"); - ( - InitMsg { - verifier: verifier.clone(), - beneficiary: beneficiary.clone(), - }, - creator, - ) - } - - #[test] - fn handle_panic() { - let mut deps = mock_instance(WASM, &[]); - - let (init_msg, creator) = make_init_msg(); - let init_env = mock_env(creator.as_str(), &[]); - let init_res: InitResponse = init(&mut deps, init_env, init_msg).unwrap(); - assert_eq!(0, init_res.messages.len()); - - let handle_env = mock_env(creator.as_str(), &[]); - // panic inside contract should not panic out here - // Note: we need to use the production-call, not the testing call (which unwraps any vm error) - let handle_res = call_handle::<_, _, _, Empty>( - &mut deps, - &handle_env, - &to_vec(&HandleMsg::Panic {}).unwrap(), - ); - assert!(handle_res.is_err()); - } - #[test] fn handle_cpu_loop() { let mut deps = mock_instance(WASM, &[]); diff --git a/packages/std/src/addresses.rs b/packages/std/src/addresses.rs new file mode 100644 index 0000000000..cba8fc089f --- /dev/null +++ b/packages/std/src/addresses.rs @@ -0,0 +1,88 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt; + +use crate::encoding::Binary; + +// Added Eq and Hash to allow this to be a key in a HashMap (MockQuerier) +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, JsonSchema, Hash)] +pub struct HumanAddr(pub String); + +impl HumanAddr { + pub fn as_str(&self) -> &str { + &self.0 + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl fmt::Display for HumanAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +impl From<&str> for HumanAddr { + fn from(addr: &str) -> Self { + HumanAddr(addr.to_string()) + } +} + +impl From<&HumanAddr> for HumanAddr { + fn from(addr: &HumanAddr) -> Self { + HumanAddr(addr.0.to_string()) + } +} + +impl From<&&HumanAddr> for HumanAddr { + fn from(addr: &&HumanAddr) -> Self { + HumanAddr(addr.0.to_string()) + } +} + +impl From for HumanAddr { + fn from(addr: String) -> Self { + HumanAddr(addr) + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, JsonSchema)] +pub struct CanonicalAddr(pub Binary); + +impl From<&[u8]> for CanonicalAddr { + fn from(source: &[u8]) -> Self { + Self(source.into()) + } +} + +impl From> for CanonicalAddr { + fn from(source: Vec) -> Self { + Self(source.into()) + } +} + +impl CanonicalAddr { + pub fn as_slice(&self) -> &[u8] { + &self.0.as_slice() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl fmt::Display for CanonicalAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index 00554a0e7c..a8d6c3e14d 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -1,5 +1,6 @@ use std::vec::Vec; +use crate::addresses::{CanonicalAddr, HumanAddr}; use crate::encoding::Binary; use crate::errors::{StdError, StdResult}; #[cfg(feature = "iterator")] @@ -7,7 +8,6 @@ use crate::iterator::{Order, KV}; use crate::memory::{alloc, build_region, consume_region, Region}; use crate::serde::from_slice; use crate::traits::{Api, Querier, QuerierResult, ReadonlyStorage, Storage}; -use crate::types::{CanonicalAddr, HumanAddr}; /// An upper bound for typical canonical address lengths (e.g. 20 in Cosmos SDK/Ethereum or 32 in Nano/Substrate) const CANONICAL_ADDRESS_BUFFER_LENGTH: usize = 32; diff --git a/packages/std/src/init_handle.rs b/packages/std/src/init_handle.rs index 823fb080e4..c21df3790f 100644 --- a/packages/std/src/init_handle.rs +++ b/packages/std/src/init_handle.rs @@ -5,10 +5,11 @@ use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::fmt; +use crate::addresses::HumanAddr; use crate::coins::Coin; use crate::encoding::Binary; use crate::errors::{StdError, StdResult}; -use crate::types::{Empty, HumanAddr}; +use crate::types::Empty; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 5b0418c03b..0011a5b7ab 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -1,5 +1,6 @@ // Exposed on all platforms +mod addresses; mod coins; mod encoding; mod entry_points; @@ -14,6 +15,7 @@ mod storage; mod traits; mod types; +pub use crate::addresses::{CanonicalAddr, HumanAddr}; pub use crate::coins::{coin, coins, has_coins, Coin}; pub use crate::encoding::Binary; pub use crate::errors::{StdError, StdResult, SystemError, SystemResult}; @@ -32,9 +34,7 @@ pub use crate::query::{ pub use crate::serde::{from_binary, from_slice, to_binary, to_vec}; pub use crate::storage::MemoryStorage; pub use crate::traits::{Api, Extern, Querier, QuerierResult, ReadonlyStorage, Storage}; -pub use crate::types::{ - BlockInfo, CanonicalAddr, ContractInfo, Empty, Env, HumanAddr, MessageInfo, -}; +pub use crate::types::{BlockInfo, ContractInfo, Empty, Env, MessageInfo}; // Exposed in wasm build only diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index ad834621b7..7f4ec52161 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -1,6 +1,7 @@ use serde::de::DeserializeOwned; use std::collections::HashMap; +use crate::addresses::{CanonicalAddr, HumanAddr}; use crate::coins::Coin; use crate::encoding::Binary; use crate::errors::{StdError, StdResult, SystemError, SystemResult}; @@ -12,7 +13,7 @@ use crate::query::{ use crate::serde::{from_slice, to_binary}; use crate::storage::MemoryStorage; use crate::traits::{Api, Extern, Querier, QuerierResult}; -use crate::types::{BlockInfo, CanonicalAddr, ContractInfo, Empty, Env, HumanAddr, MessageInfo}; +use crate::types::{BlockInfo, ContractInfo, Empty, Env, MessageInfo}; pub const MOCK_CONTRACT_ADDR: &str = "cosmos2contract"; diff --git a/packages/std/src/query.rs b/packages/std/src/query.rs index 722856be8c..70ca2b0033 100644 --- a/packages/std/src/query.rs +++ b/packages/std/src/query.rs @@ -2,11 +2,11 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt; +use crate::addresses::HumanAddr; use crate::coins::Coin; use crate::encoding::Binary; use crate::errors::StdResult; use crate::math::Decimal; -use crate::types::HumanAddr; pub type QueryResponse = Binary; diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index bfebb59f72..98bb1e5b44 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -1,5 +1,6 @@ use serde::{de::DeserializeOwned, Serialize}; +use crate::addresses::{CanonicalAddr, HumanAddr}; use crate::coins::Coin; use crate::encoding::Binary; use crate::errors::{StdError, StdResult, SystemResult}; @@ -12,7 +13,7 @@ use crate::query::{ StakingQuery, Validator, ValidatorsResponse, }; use crate::serde::{from_binary, to_vec}; -use crate::types::{CanonicalAddr, Empty, HumanAddr}; +use crate::types::Empty; /// Holds all external dependencies of the contract. /// Designed to allow easy dependency injection at runtime. diff --git a/packages/std/src/types.rs b/packages/std/src/types.rs index 5948fc072b..9f368deb4a 100644 --- a/packages/std/src/types.rs +++ b/packages/std/src/types.rs @@ -1,77 +1,8 @@ -use std::fmt; - use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::addresses::HumanAddr; use crate::coins::Coin; -use crate::encoding::Binary; - -// Added Eq and Hash to allow this to be a key in a HashMap (MockQuerier) -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, JsonSchema, Hash)] -pub struct HumanAddr(pub String); - -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, JsonSchema)] -pub struct CanonicalAddr(pub Binary); - -impl HumanAddr { - pub fn as_str(&self) -> &str { - &self.0 - } - pub fn len(&self) -> usize { - self.0.len() - } - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -impl fmt::Display for HumanAddr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", &self.0) - } -} - -impl From<&str> for HumanAddr { - fn from(addr: &str) -> Self { - HumanAddr(addr.to_string()) - } -} - -impl From<&HumanAddr> for HumanAddr { - fn from(addr: &HumanAddr) -> Self { - HumanAddr(addr.0.to_string()) - } -} - -impl From<&&HumanAddr> for HumanAddr { - fn from(addr: &&HumanAddr) -> Self { - HumanAddr(addr.0.to_string()) - } -} - -impl From for HumanAddr { - fn from(addr: String) -> Self { - HumanAddr(addr) - } -} - -impl CanonicalAddr { - pub fn as_slice(&self) -> &[u8] { - &self.0.as_slice() - } - pub fn len(&self) -> usize { - self.0.len() - } - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -impl fmt::Display for CanonicalAddr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} #[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, JsonSchema)] pub struct Env {