Skip to content

Commit

Permalink
Merge pull request #500 from CosmWasm/HandleMsg_UserErrorsInApiCalls
Browse files Browse the repository at this point in the history
Add HandleMsg::UserErrorsInApiCalls to hackatom
  • Loading branch information
mergify[bot] authored Jul 30, 2020
2 parents dc66e71 + e9958b4 commit 549d719
Show file tree
Hide file tree
Showing 12 changed files with 250 additions and 118 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u8>>` for `CanonicalAddr`

**cosmwasm-vm**

Expand Down
12 changes: 12 additions & 0 deletions contracts/hackatom/schema/handle_msg.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
]
}
83 changes: 83 additions & 0 deletions contracts/hackatom/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -134,6 +137,7 @@ pub fn handle<S: Storage, A: Api, Q: Querier>(
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),
}
}

Expand Down Expand Up @@ -219,6 +223,69 @@ fn do_panic() -> StdResult<HandleResponse> {
panic!("This page intentionally faulted");
}

fn do_user_errors_in_api_calls<A: Api>(api: &A) -> StdResult<HandleResponse> {
// 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<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
msg: QueryMsg,
Expand Down Expand Up @@ -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,
Expand Down
94 changes: 54 additions & 40 deletions contracts/hackatom/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, &[]);
Expand Down Expand Up @@ -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, &[]);
Expand All @@ -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, &[]);
Expand Down
88 changes: 88 additions & 0 deletions packages/std/src/addresses.rs
Original file line number Diff line number Diff line change
@@ -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<String> 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<Vec<u8>> for CanonicalAddr {
fn from(source: Vec<u8>) -> 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)
}
}
2 changes: 1 addition & 1 deletion packages/std/src/imports.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::vec::Vec;

use crate::addresses::{CanonicalAddr, HumanAddr};
use crate::encoding::Binary;
use crate::errors::{StdError, StdResult};
#[cfg(feature = "iterator")]
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;
Expand Down
3 changes: 2 additions & 1 deletion packages/std/src/init_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
Loading

0 comments on commit 549d719

Please sign in to comment.