diff --git a/CHANGELOG.md b/CHANGELOG.md index f3c0773892..cb8e7f08af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,8 @@ and this project adheres to - cosmwasm-std: Split up `Validator` type into `Validator` and `ValidatorMetadata` to allow adding more fields to `ValidatorResponse` in the future. ([#2501]) +- cosmwasm-std: Redesigned `StdError` to be more flexible and less immutable + ([#2500]) ## Fixed @@ -149,6 +151,7 @@ and this project adheres to [#2480]: https://github.com/CosmWasm/cosmwasm/pull/2480 [#2484]: https://github.com/CosmWasm/cosmwasm/pull/2484 [#2495]: https://github.com/CosmWasm/cosmwasm/pull/2495 +[#2500]: https://github.com/CosmWasm/cosmwasm/pull/2500 [#2501]: https://github.com/CosmWasm/cosmwasm/pull/2501 ## [2.2.0] - 2024-12-17 diff --git a/contracts/burner/src/contract.rs b/contracts/burner/src/contract.rs index b39f4f09c0..64c148fbc5 100644 --- a/contracts/burner/src/contract.rs +++ b/contracts/burner/src/contract.rs @@ -13,7 +13,7 @@ pub fn instantiate( _info: MessageInfo, _msg: InstantiateMsg, ) -> StdResult { - Err(StdError::generic_err( + Err(StdError::msg( "You can only use this contract for migrations", )) } @@ -23,7 +23,7 @@ pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult let denom_len = msg.denoms.len(); let denoms = BTreeSet::::from_iter(msg.denoms); // Ensure uniqueness if denoms.len() != denom_len { - return Err(StdError::generic_err("Denoms not unique")); + return Err(StdError::msg("Denoms not unique")); } // get balance and send to recipient @@ -98,7 +98,7 @@ mod tests { use cosmwasm_std::testing::{ message_info, mock_dependencies, mock_dependencies_with_balance, mock_env, }; - use cosmwasm_std::{coin, coins, Attribute, StdError, Storage, SubMsg}; + use cosmwasm_std::{coin, coins, Attribute, Storage, SubMsg}; /// Gets the value of the first attribute with the given key fn first_attr(data: impl AsRef<[Attribute]>, search_key: &str) -> Option { @@ -121,12 +121,10 @@ mod tests { let info = message_info(&creator, &coins(1000, "earth")); // we can just call .unwrap() to assert this was a success let res = instantiate(deps.as_mut(), mock_env(), info, msg); - match res.unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "You can only use this contract for migrations") - } - _ => panic!("expected migrate error message"), - } + assert!(res + .unwrap_err() + .to_string() + .ends_with("You can only use this contract for migrations")); } #[test] @@ -142,10 +140,7 @@ mod tests { delete: 0, }; let err = migrate(deps.as_mut(), mock_env(), msg).unwrap_err(); - match err { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Denoms not unique"), - err => panic!("Unexpected error: {err:?}"), - } + assert!(err.to_string().ends_with("Denoms not unique")); // One denom let msg = MigrateMsg { diff --git a/contracts/burner/tests/integration.rs b/contracts/burner/tests/integration.rs index f7e6f4485a..8529cbf8e3 100644 --- a/contracts/burner/tests/integration.rs +++ b/contracts/burner/tests/integration.rs @@ -35,7 +35,7 @@ fn instantiate_fails() { let msg = res.unwrap_err(); assert_eq!( msg, - "Generic error: You can only use this contract for migrations" + "kind: Other, error: You can only use this contract for migrations" ); } diff --git a/contracts/crypto-verify/src/contract.rs b/contracts/crypto-verify/src/contract.rs index 974e0fe845..bbf52308a2 100644 --- a/contracts/crypto-verify/src/contract.rs +++ b/contracts/crypto-verify/src/contract.rs @@ -210,7 +210,7 @@ pub fn query_verify_ethereum_text( // Decompose signature let (v, rs) = match signature.split_last() { Some(pair) => pair, - None => return Err(StdError::generic_err("Signature must not be empty")), + None => return Err(StdError::msg("Signature must not be empty")), }; let recovery = get_recovery_param(*v)?; @@ -326,7 +326,7 @@ mod tests { use cosmwasm_std::testing::{ message_info, mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage, }; - use cosmwasm_std::{from_json, OwnedDeps, RecoverPubkeyError, VerificationError}; + use cosmwasm_std::{from_json, OwnedDeps, StdErrorKind}; use hex_literal::hex; const CREATOR: &str = "creator"; @@ -426,11 +426,8 @@ mod tests { let res = query(deps.as_ref(), mock_env(), verify_msg); assert!(res.is_err()); assert!(matches!( - res.unwrap_err(), - StdError::VerificationErr { - source: VerificationError::InvalidPubkeyFormat, - .. - } + res.unwrap_err().kind(), + StdErrorKind::Cryptography, )) } @@ -500,11 +497,8 @@ mod tests { signer_address: signer_address.into(), }; let result = query(deps.as_ref(), mock_env(), verify_msg); - match result.unwrap_err() { - StdError::RecoverPubkeyErr { - source: RecoverPubkeyError::UnknownErr { .. }, - .. - } => {} + match result.unwrap_err().kind() { + StdErrorKind::Cryptography => {} err => panic!("Unexpected error: {err:?}"), } } @@ -715,11 +709,8 @@ mod tests { let res = query(deps.as_ref(), mock_env(), verify_msg); assert!(res.is_err()); assert!(matches!( - res.unwrap_err(), - StdError::VerificationErr { - source: VerificationError::InvalidPubkeyFormat, - .. - } + res.unwrap_err().kind(), + StdErrorKind::Cryptography )) } @@ -781,11 +772,8 @@ mod tests { let res = query(deps.as_ref(), mock_env(), verify_msg); assert!(res.is_err()); assert!(matches!( - res.unwrap_err(), - StdError::VerificationErr { - source: VerificationError::InvalidPubkeyFormat, - .. - } + res.unwrap_err().kind(), + StdErrorKind::Cryptography, )) } diff --git a/contracts/crypto-verify/src/ethereum.rs b/contracts/crypto-verify/src/ethereum.rs index 403e0b4297..79f64a6f2c 100644 --- a/contracts/crypto-verify/src/ethereum.rs +++ b/contracts/crypto-verify/src/ethereum.rs @@ -72,7 +72,7 @@ pub fn get_recovery_param(v: u8) -> StdResult { match v { 27 => Ok(0), 28 => Ok(1), - _ => Err(StdError::generic_err("Values of v other than 27 and 28 not supported. Replay protection (EIP-155) cannot be used here.")) + _ => Err(StdError::msg("Values of v other than 27 and 28 not supported. Replay protection (EIP-155) cannot be used here.")) } } @@ -87,7 +87,7 @@ pub fn get_recovery_param_with_chain_id(v: u64, chain_id: u64) -> StdResult let recovery = v - chain_id * 2 - 35; match recovery { 0 | 1 => Ok(recovery as u8), - _ => Err(StdError::generic_err(format!( + _ => Err(StdError::msg(format_args!( "Calculated recovery parameter must be 0 or 1 but is {recovery}." ))), } @@ -97,13 +97,13 @@ pub fn get_recovery_param_with_chain_id(v: u64, chain_id: u64) -> StdResult pub fn ethereum_address_raw(pubkey: &[u8]) -> StdResult<[u8; 20]> { let (tag, data) = match pubkey.split_first() { Some(pair) => pair, - None => return Err(StdError::generic_err("Public key must not be empty")), + None => return Err(StdError::msg("Public key must not be empty")), }; if *tag != 0x04 { - return Err(StdError::generic_err("Public key must start with 0x04")); + return Err(StdError::msg("Public key must start with 0x04")); } if data.len() != 64 { - return Err(StdError::generic_err("Public key must be 65 bytes long")); + return Err(StdError::msg("Public key must be 65 bytes long")); } let hash = Keccak256::digest(data); @@ -112,14 +112,12 @@ pub fn ethereum_address_raw(pubkey: &[u8]) -> StdResult<[u8; 20]> { pub fn decode_address(input: &str) -> StdResult<[u8; 20]> { if input.len() != 42 { - return Err(StdError::generic_err( - "Ethereum address must be 42 characters long", - )); + return Err(StdError::msg("Ethereum address must be 42 characters long")); } if !input.starts_with("0x") { - return Err(StdError::generic_err("Ethereum address must start with 0x")); + return Err(StdError::msg("Ethereum address must start with 0x")); } - let data = hex::decode(&input[2..]).map_err(|_| StdError::generic_err("hex decoding error"))?; + let data = hex::decode(&input[2..])?; Ok(data.try_into().unwrap()) } diff --git a/contracts/crypto-verify/tests/integration.rs b/contracts/crypto-verify/tests/integration.rs index 2635133f8e..468fa38944 100644 --- a/contracts/crypto-verify/tests/integration.rs +++ b/contracts/crypto-verify/tests/integration.rs @@ -221,7 +221,7 @@ fn cosmos_signature_verify_errors() { let res = query(&mut deps, mock_env(), verify_msg); assert_eq!( res.unwrap_err(), - "Verification error: Invalid public key format" + "kind: Cryptography, error: Invalid public key format" ) } @@ -283,7 +283,7 @@ fn secp256r1_signature_verify_errors() { let res = query(&mut deps, mock_env(), verify_msg); assert_eq!( res.unwrap_err(), - "Verification error: Invalid public key format" + "kind: Cryptography, error: Invalid public key format" ) } @@ -354,7 +354,7 @@ fn ethereum_signature_verify_fails_for_corrupted_signature() { }; let result = query(&mut deps, mock_env(), verify_msg); let msg = result.unwrap_err(); - assert_eq!(msg, "Recover pubkey error: Unknown error: 10"); + assert_eq!(msg, "kind: Cryptography, error: Unknown error: 10"); } #[test] @@ -466,7 +466,7 @@ fn tendermint_signature_verify_errors() { let res = query(&mut deps, mock_env(), verify_msg); assert_eq!( res.unwrap_err(), - "Verification error: Invalid public key format" + "kind: Cryptography, error: Invalid public key format" ) } @@ -625,7 +625,7 @@ fn tendermint_signatures_batch_verify_errors() { let res = query(&mut deps, mock_env(), verify_msg); assert_eq!( res.unwrap_err(), - "Verification error: Invalid public key format" + "kind: Cryptography, error: Invalid public key format" ) } diff --git a/contracts/cyberpunk/src/contract.rs b/contracts/cyberpunk/src/contract.rs index 2e0ed0f923..c0a96e7e16 100644 --- a/contracts/cyberpunk/src/contract.rs +++ b/contracts/cyberpunk/src/contract.rs @@ -56,8 +56,7 @@ fn execute_argon2(mem_cost: u32, time_cost: u32) -> Result Result use core::arch::wasm32; let old_size = wasm32::memory_grow(0, pages as usize); if old_size == usize::max_value() { - return Err(StdError::generic_err("memory.grow failed").into()); + return Err(StdError::msg("memory.grow failed").into()); } Ok(Response::new().set_data((old_size as u32).to_be_bytes())) } #[cfg(not(target_arch = "wasm32"))] - Err(StdError::generic_err("Unsupported architecture").into()) + Err(StdError::msg("Unsupported architecture").into()) } fn execute_panic() -> Result { @@ -139,7 +138,7 @@ fn execute_unreachable() -> Result { core::arch::wasm32::unreachable(); #[cfg(not(target_arch = "wasm32"))] - Err(StdError::generic_err("Unsupported architecture").into()) + Err(StdError::msg("Unsupported architecture").into()) } fn execute_mirror_env(env: Env) -> Result { diff --git a/contracts/cyberpunk/src/errors.rs b/contracts/cyberpunk/src/errors.rs index 970745b4cc..53c9be9908 100644 --- a/contracts/cyberpunk/src/errors.rs +++ b/contracts/cyberpunk/src/errors.rs @@ -1,7 +1,7 @@ use cosmwasm_std::StdError; use thiserror::Error; -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug)] pub enum ContractError { #[error("{0}")] /// this is needed so we can use `bucket.load(...)?` and have it auto-converted to the custom error diff --git a/contracts/hackatom/src/contract.rs b/contracts/hackatom/src/contract.rs index 746c3ee806..a388624f6d 100644 --- a/contracts/hackatom/src/contract.rs +++ b/contracts/hackatom/src/contract.rs @@ -3,7 +3,7 @@ use sha2::{Digest, Sha256}; use cosmwasm_std::{ entry_point, from_json, to_json_binary, to_json_vec, Addr, Api, BankMsg, CanonicalAddr, Deps, DepsMut, Env, Event, MessageInfo, MigrateInfo, QueryRequest, QueryResponse, Response, StdError, - StdResult, WasmMsg, WasmQuery, + StdErrorKind, StdResult, WasmMsg, WasmQuery, }; use crate::errors::HackError; @@ -53,7 +53,7 @@ pub fn migrate( let data = deps .storage .get(CONFIG_KEY) - .ok_or_else(|| StdError::not_found("State"))?; + .ok_or_else(|| StdError::msg("State not found"))?; let mut config: State = from_json(data)?; config.verifier = deps.api.addr_validate(&msg.verifier)?; deps.storage.set(CONFIG_KEY, &to_json_vec(&config)?); @@ -102,7 +102,7 @@ fn do_release( let data = deps .storage .get(CONFIG_KEY) - .ok_or_else(|| StdError::not_found("State"))?; + .ok_or_else(|| StdError::msg("State not found"))?; let state: State = from_json(data)?; if info.sender == state.verifier { @@ -171,13 +171,13 @@ fn do_allocate_large_memory(pages: u32) -> Result { use core::arch::wasm32; let old_size = wasm32::memory_grow(0, pages as usize); if old_size == usize::max_value() { - return Err(StdError::generic_err("memory.grow failed").into()); + return Err(StdError::msg("memory.grow failed").into()); } Ok(Response::new().set_data((old_size as u32).to_be_bytes())) } #[cfg(not(target_arch = "wasm32"))] - Err(StdError::generic_err("Unsupported architecture").into()) + Err(StdError::msg("Unsupported architecture").into()) } fn do_panic() -> Result { @@ -203,10 +203,10 @@ fn do_user_errors_in_api_calls(api: &dyn Api) -> Result { // Canonicalize let empty = ""; - match api.addr_canonicalize(empty).unwrap_err() { - StdError::GenericErr { .. } => {} + match api.addr_canonicalize(empty).unwrap_err().kind() { + StdErrorKind::Other => {} err => { - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "Unexpected error in do_user_errors_in_api_calls: {err:?}" )) .into()) @@ -214,10 +214,10 @@ fn do_user_errors_in_api_calls(api: &dyn Api) -> Result { } let invalid = "bn9hhssomeltvhzgvuqkwjkpwxoj"; - match api.addr_canonicalize(invalid).unwrap_err() { - StdError::GenericErr { .. } => {} + match api.addr_canonicalize(invalid).unwrap_err().kind() { + StdErrorKind::Other => {} err => { - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "Unexpected error in do_user_errors_in_api_calls: {err:?}" )) .into()) @@ -225,10 +225,10 @@ fn do_user_errors_in_api_calls(api: &dyn Api) -> Result { } let too_long = "cosmwasm1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqehqqkz"; - match api.addr_canonicalize(too_long).unwrap_err() { - StdError::GenericErr { .. } => {} + match api.addr_canonicalize(too_long).unwrap_err().kind() { + StdErrorKind::Other => {} err => { - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "Unexpected error in do_user_errors_in_api_calls: {err:?}" )) .into()) @@ -237,10 +237,10 @@ fn do_user_errors_in_api_calls(api: &dyn Api) -> Result { // Humanize let empty: CanonicalAddr = vec![].into(); - match api.addr_humanize(&empty).unwrap_err() { - StdError::GenericErr { .. } => {} + match api.addr_humanize(&empty).unwrap_err().kind() { + StdErrorKind::Other => {} err => { - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "Unexpected error in do_user_errors_in_api_calls: {err:?}" )) .into()) @@ -265,7 +265,7 @@ fn query_verifier(deps: Deps) -> StdResult { let data = deps .storage .get(CONFIG_KEY) - .ok_or_else(|| StdError::not_found("State"))?; + .ok_or_else(|| StdError::msg("State not found"))?; let state: State = from_json(data)?; Ok(VerifierResponse { verifier: state.verifier.into(), @@ -582,7 +582,7 @@ mod tests { denom: "earth".to_string(), }, ); - assert_eq!(execute_res.unwrap_err(), HackError::Unauthorized {}); + assert_eq!(execute_res.unwrap_err().to_string(), "Unauthorized"); // state should not change let data = deps.storage.get(CONFIG_KEY).expect("no data stored"); diff --git a/contracts/hackatom/src/errors.rs b/contracts/hackatom/src/errors.rs index a72d292460..1913a804f9 100644 --- a/contracts/hackatom/src/errors.rs +++ b/contracts/hackatom/src/errors.rs @@ -1,7 +1,7 @@ use cosmwasm_std::StdError; use thiserror::Error; -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug)] pub enum HackError { #[error("{0}")] /// this is needed so we can use `bucket.load(...)?` and have it auto-converted to the custom error diff --git a/contracts/hackatom/tests/integration.rs b/contracts/hackatom/tests/integration.rs index be2aa6de57..c547823de8 100644 --- a/contracts/hackatom/tests/integration.rs +++ b/contracts/hackatom/tests/integration.rs @@ -97,7 +97,7 @@ fn instantiate_and_query() { // bad query returns parse error (pass wrong type - this connection is not enforced) let qres = query(&mut deps, mock_env(), ExecuteMsg::Panic {}); let msg = qres.unwrap_err(); - assert!(msg.contains("Error parsing")); + assert!(msg.contains("kind: Serialization"), "{msg}"); } #[test] @@ -178,7 +178,7 @@ fn fails_on_bad_init() { let res: ContractResult = instantiate(&mut deps, mock_env(), info, ExecuteMsg::Panic {}); let msg = res.unwrap_err(); - assert!(msg.contains("Error parsing")); + assert!(msg.contains("kind: Serialization"), "{msg}"); } #[test] @@ -407,7 +407,10 @@ fn execute_allocate_large_memory() { execute_info, ExecuteMsg::AllocateLargeMemory { pages: 1600 }, ); - assert_eq!(result.unwrap_err(), "Generic error: memory.grow failed"); + assert_eq!( + result.unwrap_err(), + "kind: Other, error: memory.grow failed" + ); let gas_used = gas_before - deps.get_gas_left(); // Gas consumption is relatively small // Note: the exact gas usage depends on the Rust version used to compile Wasm, diff --git a/contracts/ibc-callbacks/src/contract.rs b/contracts/ibc-callbacks/src/contract.rs index 9ce24b694b..43728cd430 100644 --- a/contracts/ibc-callbacks/src/contract.rs +++ b/contracts/ibc-callbacks/src/contract.rs @@ -65,7 +65,7 @@ fn execute_transfer( let coin = match &*info.funds { [coin] if !coin.amount.is_zero() => coin, _ => { - return Err(StdError::generic_err( + return Err(StdError::msg( "Must send exactly one denom to trigger ics-20 transfer", )) } @@ -142,7 +142,7 @@ pub fn ibc_destination_callback( .query_balance(&transfer.receiver, &coin.denom)?; ensure!( balance.amount >= coin.amount, - StdError::generic_err(format!( + StdError::msg(format_args!( "Didn't receive expected funds. expected: {coin}, have: {balance}" )) ); diff --git a/contracts/ibc-callbacks/src/state.rs b/contracts/ibc-callbacks/src/state.rs index ece4fb1ac9..3eeaca0c5e 100644 --- a/contracts/ibc-callbacks/src/state.rs +++ b/contracts/ibc-callbacks/src/state.rs @@ -29,7 +29,7 @@ pub fn save_stats(storage: &mut dyn Storage, counts: &CallbackStats) -> StdResul fn load_item(storage: &dyn Storage, key: &[u8]) -> StdResult { storage .get(&to_length_prefixed(key)) - .ok_or_else(|| StdError::not_found(type_name::())) + .ok_or_else(|| StdError::msg(format_args!("{} not found", type_name::()))) .and_then(from_json) } diff --git a/contracts/ibc-reflect-send/src/contract.rs b/contracts/ibc-reflect-send/src/contract.rs index f550c2104c..1801483d95 100644 --- a/contracts/ibc-reflect-send/src/contract.rs +++ b/contracts/ibc-reflect-send/src/contract.rs @@ -50,7 +50,7 @@ pub fn handle_update_admin( // auth check let mut cfg = load_config(deps.storage)?; if info.sender != cfg.admin { - return Err(StdError::generic_err("Only admin may set new admin")); + return Err(StdError::msg("Only admin may set new admin")); } cfg.admin = deps.api.addr_validate(&new_admin)?; save_config(deps.storage, &cfg)?; @@ -70,7 +70,7 @@ pub fn handle_send_msgs( // auth check let cfg = load_config(deps.storage)?; if info.sender != cfg.admin { - return Err(StdError::generic_err("Only admin may send messages")); + return Err(StdError::msg("Only admin may send messages")); } // ensure the channel exists (not found if not registered) load_account(deps.storage, &channel_id)?; @@ -98,7 +98,7 @@ pub fn handle_check_remote_balance( // auth check let cfg = load_config(deps.storage)?; if info.sender != cfg.admin { - return Err(StdError::generic_err("Only admin may send messages")); + return Err(StdError::msg("Only admin may send messages")); } // ensure the channel exists (not found if not registered) load_account(deps.storage, &channel_id)?; @@ -130,14 +130,14 @@ pub fn handle_send_funds( let amount = match info.funds.pop() { Some(coin) => coin, None => { - return Err(StdError::generic_err( + return Err(StdError::msg( "you must send the coins you wish to ibc transfer", )) } }; // if there are any more coins, reject the message if !info.funds.is_empty() { - return Err(StdError::generic_err("you can only ibc transfer one coin")); + return Err(StdError::msg("you can only ibc transfer one coin")); } // load remote account @@ -145,7 +145,7 @@ pub fn handle_send_funds( let remote_addr = match data.remote_addr { Some(addr) => addr, None => { - return Err(StdError::generic_err( + return Err(StdError::msg( "We don't have the remote address for this channel", )) } diff --git a/contracts/ibc-reflect-send/src/ibc.rs b/contracts/ibc-reflect-send/src/ibc.rs index a39c0797be..5ccee4a936 100644 --- a/contracts/ibc-reflect-send/src/ibc.rs +++ b/contracts/ibc-reflect-send/src/ibc.rs @@ -26,17 +26,17 @@ pub fn ibc_channel_open( let channel = msg.channel(); if channel.order != IbcOrder::Ordered { - return Err(StdError::generic_err("Only supports ordered channels")); + return Err(StdError::msg("Only supports ordered channels")); } if channel.version.as_str() != IBC_APP_VERSION { - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "Must set version to `{IBC_APP_VERSION}`" ))); } if let Some(counter_version) = msg.counterparty_version() { if counter_version != IBC_APP_VERSION { - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "Counterparty version must be `{IBC_APP_VERSION}`" ))); } @@ -165,7 +165,7 @@ fn acknowledge_who_am_i( } save_account(deps.storage, &caller, &acct)?; } - None => return Err(StdError::generic_err("no account to update")), + None => return Err(StdError::msg("no account to update")), } Ok(IbcBasicResponse::new().add_attribute("action", "acknowledge_who_am_i")) @@ -192,7 +192,7 @@ fn acknowledge_balances( Some(acct) => { if let Some(old_addr) = acct.remote_addr { if old_addr != account { - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "remote account changed from {old_addr} to {account}" ))); } @@ -207,7 +207,7 @@ fn acknowledge_balances( }, )?; } - None => return Err(StdError::generic_err("no account to update")), + None => return Err(StdError::msg("no account to update")), } Ok(IbcBasicResponse::new().add_attribute("action", "acknowledge_balances")) diff --git a/contracts/ibc-reflect-send/src/state.rs b/contracts/ibc-reflect-send/src/state.rs index 97c0720f36..79ac1110fc 100644 --- a/contracts/ibc-reflect-send/src/state.rs +++ b/contracts/ibc-reflect-send/src/state.rs @@ -38,7 +38,7 @@ pub fn may_load_account(storage: &dyn Storage, id: &str) -> StdResult StdResult { - may_load_account(storage, id)?.ok_or_else(|| StdError::not_found(format!("account {id}"))) + may_load_account(storage, id)?.ok_or_else(|| StdError::msg(format!("account {id} not found"))) } pub fn save_account(storage: &mut dyn Storage, id: &str, account: &AccountData) -> StdResult<()> { @@ -71,7 +71,7 @@ pub fn range_accounts( pub fn load_config(storage: &dyn Storage) -> StdResult { storage .get(&to_length_prefixed(KEY_CONFIG)) - .ok_or_else(|| StdError::not_found("config")) + .ok_or_else(|| StdError::msg("config not found")) .and_then(from_json) } diff --git a/contracts/ibc-reflect/src/contract.rs b/contracts/ibc-reflect/src/contract.rs index de085bd6b8..dfe7c5984c 100644 --- a/contracts/ibc-reflect/src/contract.rs +++ b/contracts/ibc-reflect/src/contract.rs @@ -72,7 +72,7 @@ pub fn reply(deps: DepsMut, _env: Env, reply: Reply) -> StdResult { Ok(Response::new().set_data(encode_ibc_error(err))) } (INIT_CALLBACK_ID, SubMsgResult::Ok(response)) => handle_init_callback(deps, response), - _ => Err(StdError::generic_err("invalid reply id or result")), + _ => Err(StdError::msg("invalid reply id or result")), } } @@ -97,7 +97,7 @@ pub fn handle_init_callback(deps: DepsMut, response: SubMsgResponse) -> StdResul // parse contract info from events let contract_addr = match parse_contract_from_event(response.events) { Some(addr) => deps.api.addr_validate(&addr), - None => Err(StdError::generic_err( + None => Err(StdError::msg( "No _contract_address found in callback events", )), }?; @@ -105,11 +105,7 @@ pub fn handle_init_callback(deps: DepsMut, response: SubMsgResponse) -> StdResul // store id -> contract_addr if it is empty // id comes from: `let chan_id = msg.endpoint.channel_id;` in `ibc_channel_connect` match may_load_account(deps.storage, &id)? { - Some(_) => { - return Err(StdError::generic_err( - "Cannot register over an existing channel", - )) - } + Some(_) => return Err(StdError::msg("Cannot register over an existing channel")), None => save_account(deps.storage, &id, &contract_addr)?, } @@ -156,14 +152,14 @@ pub fn ibc_channel_open( let channel = msg.channel(); if channel.order != IbcOrder::Ordered { - return Err(StdError::generic_err("Only supports ordered channels")); + return Err(StdError::msg("Only supports ordered channels")); } // In ibcv3 we don't check the version string passed in the message // and only check the counterparty version. if let Some(counter_version) = msg.counterparty_version() { if counter_version != IBC_APP_VERSION { - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "Counterparty version must be `{IBC_APP_VERSION}`" ))); } @@ -320,7 +316,7 @@ fn execute_panic() -> StdResult { } fn execute_error(text: String) -> StdResult { - Err(StdError::generic_err(text)) + Err(StdError::msg(text)) } fn execute_return_msgs(msgs: Vec) -> StdResult { @@ -564,7 +560,7 @@ mod tests { from_json(res.acknowledgement.unwrap()).unwrap(); assert_eq!( ack.unwrap_err(), - "invalid packet: account channel-123 not found" + "invalid packet: kind: Other, error: account channel-123 not found" ); // register the channel @@ -614,7 +610,7 @@ mod tests { // acknowledgement is an error let ack: AcknowledgementMsg = from_json(res.acknowledgement.unwrap()).unwrap(); - assert_eq!(ack.unwrap_err(), "invalid packet: Error parsing into type ibc_reflect::msg::PacketMsg: unknown variant `reflect_code_id`, expected one of `dispatch`, `who_am_i`, `balance`, `panic`, `return_err`, `return_msgs`, `no_ack` at line 1 column 18"); + assert_eq!(ack.unwrap_err(), "invalid packet: kind: Serialization, error: unknown variant `reflect_code_id`, expected one of `dispatch`, `who_am_i`, `balance`, `panic`, `return_err`, `return_msgs`, `no_ack` at line 1 column 18"); } #[test] diff --git a/contracts/ibc-reflect/src/state.rs b/contracts/ibc-reflect/src/state.rs index 7c7e68c730..21669a7294 100644 --- a/contracts/ibc-reflect/src/state.rs +++ b/contracts/ibc-reflect/src/state.rs @@ -27,7 +27,7 @@ pub fn may_load_account(storage: &dyn Storage, id: &str) -> StdResult StdResult { - may_load_account(storage, id)?.ok_or_else(|| StdError::not_found(format!("account {id}"))) + may_load_account(storage, id)?.ok_or_else(|| StdError::msg(format!("account {id} not found"))) } pub fn save_account(storage: &mut dyn Storage, id: &str, account: &Addr) -> StdResult<()> { @@ -60,7 +60,7 @@ pub fn range_accounts( pub fn load_item(storage: &dyn Storage, key: &[u8]) -> StdResult { storage .get(&to_length_prefixed(key)) - .ok_or_else(|| StdError::not_found(type_name::())) + .ok_or_else(|| StdError::msg(format!("{} not found", type_name::()))) .and_then(from_json) } diff --git a/contracts/ibc-reflect/tests/integration.rs b/contracts/ibc-reflect/tests/integration.rs index e3baf5a00d..2dfe49a547 100644 --- a/contracts/ibc-reflect/tests/integration.rs +++ b/contracts/ibc-reflect/tests/integration.rs @@ -232,7 +232,7 @@ fn handle_dispatch_packet() { from_slice(&res.acknowledgement.unwrap(), DESERIALIZATION_LIMIT).unwrap(); assert_eq!( ack.unwrap_err(), - "invalid packet: account channel-123 not found" + "invalid packet: kind: Other, error: account channel-123 not found" ); // register the channel @@ -283,5 +283,5 @@ fn handle_dispatch_packet() { // acknowledgement is an error let ack: AcknowledgementMsg = from_slice(&res.acknowledgement.unwrap(), DESERIALIZATION_LIMIT).unwrap(); - assert_eq!(ack.unwrap_err(), "invalid packet: Error parsing into type ibc_reflect::msg::PacketMsg: unknown variant `reflect_code_id`, expected one of `dispatch`, `who_am_i`, `balance`, `panic`, `return_err`, `return_msgs`, `no_ack` at line 1 column 18"); + assert_eq!(ack.unwrap_err(), "invalid packet: kind: Serialization, error: unknown variant `reflect_code_id`, expected one of `dispatch`, `who_am_i`, `balance`, `panic`, `return_err`, `return_msgs`, `no_ack` at line 1 column 18"); } diff --git a/contracts/ibc2/src/contract.rs b/contracts/ibc2/src/contract.rs index 03f6d372c5..fe4b12db7a 100644 --- a/contracts/ibc2/src/contract.rs +++ b/contracts/ibc2/src/contract.rs @@ -37,7 +37,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { let data = deps .storage .get(STATE_KEY) - .ok_or_else(|| StdError::generic_err("State not found."))?; + .ok_or_else(|| StdError::msg("State not found."))?; Ok(Binary::from(data)) } } @@ -52,7 +52,7 @@ pub fn ibc2_packet_ack( let data = deps .storage .get(STATE_KEY) - .ok_or_else(|| StdError::generic_err("State not found."))?; + .ok_or_else(|| StdError::msg("State not found."))?; let state: State = from_json(data)?; deps.storage.set( @@ -78,7 +78,7 @@ pub fn ibc2_packet_receive( let data = deps .storage .get(STATE_KEY) - .ok_or_else(|| StdError::generic_err("State not found."))?; + .ok_or_else(|| StdError::msg("State not found."))?; let state: State = from_json(data)?; deps.storage.set( @@ -133,7 +133,7 @@ pub fn ibc2_packet_timeout( let data = deps .storage .get(STATE_KEY) - .ok_or_else(|| StdError::generic_err("State not found."))?; + .ok_or_else(|| StdError::msg("State not found."))?; let state: State = from_json(data)?; deps.storage.set( @@ -156,7 +156,7 @@ pub fn ibc2_packet_send( let data = deps .storage .get(STATE_KEY) - .ok_or_else(|| StdError::generic_err("State not found."))?; + .ok_or_else(|| StdError::msg("State not found."))?; let state: State = from_json(data)?; deps.storage.set( diff --git a/contracts/nested-contracts/inner-contract/Cargo.lock b/contracts/nested-contracts/inner-contract/Cargo.lock index e11afa6705..0dbf36487d 100644 --- a/contracts/nested-contracts/inner-contract/Cargo.lock +++ b/contracts/nested-contracts/inner-contract/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ahash" diff --git a/contracts/reflect/src/contract.rs b/contracts/reflect/src/contract.rs index b53daa996c..3cc3508399 100644 --- a/contracts/reflect/src/contract.rs +++ b/contracts/reflect/src/contract.rs @@ -150,13 +150,13 @@ fn query_chain( request: &QueryRequest, ) -> StdResult { let raw = to_json_vec(request).map_err(|serialize_err| { - StdError::generic_err(format!("Serializing QueryRequest: {serialize_err}")) + StdError::msg(format_args!("Serializing QueryRequest: {serialize_err}")) })?; match deps.querier.raw_query(&raw) { - SystemResult::Err(system_err) => Err(StdError::generic_err(format!( + SystemResult::Err(system_err) => Err(StdError::msg(format_args!( "Querier system error: {system_err}" ))), - SystemResult::Ok(ContractResult::Err(contract_err)) => Err(StdError::generic_err(format!( + SystemResult::Ok(ContractResult::Err(contract_err)) => Err(StdError::msg(format_args!( "Querier contract error: {contract_err}" ))), SystemResult::Ok(ContractResult::Ok(value)) => Ok(ChainResponse { data: value }), @@ -177,7 +177,7 @@ mod tests { use cosmwasm_std::testing::{message_info, mock_env, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ coin, coins, from_json, BalanceResponse, BankMsg, BankQuery, Binary, Event, StakingMsg, - StdError, SubMsgResponse, SubMsgResult, + SubMsgResponse, SubMsgResult, }; #[test] @@ -261,7 +261,10 @@ mod tests { let msg = ExecuteMsg::ReflectMsg { msgs: payload }; let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); - assert_eq!(err, ReflectError::MessagesEmpty); + assert_eq!( + err.to_string(), + "Messages empty. Must reflect at least one message" + ); } #[test] @@ -338,11 +341,8 @@ mod tests { let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); assert_eq!( - err, - ReflectError::NotCurrentOwner { - expected: creator.to_string(), - actual: random.to_string(), - } + err.to_string(), + "Permission denied: the sender is not the current owner" ); } @@ -361,8 +361,11 @@ mod tests { }; let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); match err { - ReflectError::Std(StdError::GenericErr { msg, .. }) => { - assert!(msg.contains("Error decoding bech32")) + ReflectError::Std(err) => { + assert!( + err.to_string().contains("kind: Other, error: parse failed"), + "{err}" + ) } e => panic!("Unexpected error: {e:?}"), } diff --git a/contracts/reflect/src/errors.rs b/contracts/reflect/src/errors.rs index 98db940523..3d05bc0b91 100644 --- a/contracts/reflect/src/errors.rs +++ b/contracts/reflect/src/errors.rs @@ -1,7 +1,7 @@ use cosmwasm_std::StdError; use thiserror::Error; -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug)] pub enum ReflectError { #[error("{0}")] // let thiserror implement From for you diff --git a/contracts/reflect/src/state.rs b/contracts/reflect/src/state.rs index 2fc752b225..6761145bba 100644 --- a/contracts/reflect/src/state.rs +++ b/contracts/reflect/src/state.rs @@ -18,7 +18,7 @@ pub struct State { pub fn load_reply(storage: &dyn Storage, id: u64) -> StdResult { storage .get(&namespace_with_key(&[RESULT_PREFIX], &id.to_be_bytes())) - .ok_or_else(|| StdError::not_found(format!("reply {id}"))) + .ok_or_else(|| StdError::msg(format!("reply {id} not found"))) .and_then(from_json) } @@ -37,7 +37,7 @@ pub fn remove_reply(storage: &mut dyn Storage, id: u64) { pub fn load_config(storage: &dyn Storage) -> StdResult { storage .get(&to_length_prefixed(CONFIG_KEY)) - .ok_or_else(|| StdError::not_found("config")) + .ok_or_else(|| StdError::msg("config not found")) .and_then(from_json) } diff --git a/contracts/replier/src/lib.rs b/contracts/replier/src/lib.rs index 5bc6b2de2d..24fa0ad23a 100644 --- a/contracts/replier/src/lib.rs +++ b/contracts/replier/src/lib.rs @@ -68,7 +68,7 @@ pub fn execute( } if msg.exec_error { - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "Err in exec msg_id: {}", msg.msg_id ))); @@ -125,7 +125,7 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult { deps.storage.set(CONFIG_KEY, &to_json_vec(&config)?); if should_return_error { - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "Err in reply msg_id: {}", msg_id ))); diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index 2ca4de1ce8..7f7e15d13c 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{ entry_point, to_json_binary, BankMsg, Coin, Decimal, Decimal256, Deps, DepsMut, DistributionMsg, Env, MessageInfo, QuerierWrapper, QueryResponse, Response, StakingMsg, - StdError, StdResult, Uint128, Uint256, WasmMsg, + StdError, StdErrorKind, StdResult, Uint128, Uint256, WasmMsg, }; use crate::errors::{StakingError, Unauthorized}; @@ -26,7 +26,7 @@ pub fn instantiate( // ensure the validator is registered let validator = deps.querier.query_validator(msg.validator.clone())?; if validator.is_none() { - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "{} is not in the current validator set", msg.validator ))); @@ -115,7 +115,7 @@ fn get_bonded(querier: &QuerierWrapper, contract_addr: impl Into) -> Std let denom = bonds[0].amount.denom.as_str(); bonds.iter().try_fold(Uint256::zero(), |acc, d| { if d.amount.denom.as_str() != denom { - Err(StdError::generic_err(format!( + Err(StdError::msg(format_args!( "different denoms in bonds: '{}' vs '{}'", denom, &d.amount.denom ))) @@ -127,7 +127,7 @@ fn get_bonded(querier: &QuerierWrapper, contract_addr: impl Into) -> Std fn assert_bonds(supply: &Supply, bonded: Uint256) -> StdResult<()> { if supply.bonded != bonded { - Err(StdError::generic_err(format!( + Err(StdError::msg(format_args!( "Stored bonded {}, but query bonded: {}", supply.bonded, bonded ))) @@ -146,7 +146,7 @@ pub fn bond(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { .funds .iter() .find(|x| x.denom == invest.bond_denom) - .ok_or_else(|| StdError::generic_err(format!("No {} tokens sent", &invest.bond_denom)))?; + .ok_or_else(|| StdError::msg(format_args!("No {} tokens sent", &invest.bond_denom)))?; // bonded is the total number of tokens we have delegated from this address let bonded = get_bonded(&deps.querier, env.contract.address)?; @@ -187,7 +187,7 @@ pub fn unbond(deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128) -> St let invest: InvestmentInfo = load_item(deps.storage, KEY_INVESTMENT)?; // ensure it is big enough to care if amount < invest.min_withdrawal { - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "Must unbond at least {} {}", invest.min_withdrawal, invest.bond_denom ))); @@ -255,7 +255,7 @@ pub fn claim(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult .querier .query_balance(env.contract.address, invest.bond_denom)?; if balance.amount < invest.min_withdrawal.into() { - return Err(StdError::generic_err( + return Err(StdError::msg( "Insufficient balance in contract to process claim", )); } @@ -263,7 +263,7 @@ pub fn claim(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult // check how much to send - min(balance, claims[sender]), and reduce the claim let sender_raw = deps.api.addr_canonicalize(info.sender.as_str())?; let claim = may_load_map(deps.storage, PREFIX_CLAIMS, &sender_raw)? - .ok_or_else(|| StdError::generic_err("no claim for this address"))?; + .ok_or_else(|| StdError::msg("no claim for this address"))?; let to_send = balance.amount.min(claim); save_map( deps.storage, @@ -329,17 +329,18 @@ pub fn _bond_all_tokens( // we deduct pending claims from our account balance before reinvesting. // if there is not enough funds, we just return a no-op - let updated = update_item(deps.storage, KEY_TOTAL_SUPPLY, |mut supply: Supply| { - balance.amount = balance.amount.checked_sub(supply.claims)?; - // this just triggers the "no op" case if we don't have min_withdrawal left to reinvest - balance.amount.checked_sub(invest.min_withdrawal.into())?; - supply.bonded += balance.amount; - Ok(supply) - }); + let updated: StdResult<_> = + update_item(deps.storage, KEY_TOTAL_SUPPLY, |mut supply: Supply| { + balance.amount = balance.amount.checked_sub(supply.claims)?; + // this just triggers the "no op" case if we don't have min_withdrawal left to reinvest + balance.amount.checked_sub(invest.min_withdrawal.into())?; + supply.bonded += balance.amount; + Ok(supply) + }); match updated { Ok(_) => {} // if it is below the minimum, we do a no-op (do not revert other state from withdrawal) - Err(StdError::Overflow { .. }) => return Ok(Response::default()), + Err(e) if e.kind() == StdErrorKind::Overflow => return Ok(Response::default()), Err(e) => return Err(e.into()), } @@ -406,7 +407,7 @@ pub fn query_investment(deps: Deps) -> StdResult { } else { Decimal256::from_ratio(supply.bonded, supply.issued) .try_into() - .map_err(|_| StdError::generic_err("nominal value too high"))? + .map_err(|_| StdError::msg("nominal value too high"))? }, }; Ok(res) @@ -418,7 +419,9 @@ mod tests { use cosmwasm_std::testing::{ message_info, mock_dependencies, mock_env, MockQuerier, StakingQuerier, MOCK_CONTRACT_ADDR, }; - use cosmwasm_std::{coin, coins, Addr, Coin, CosmosMsg, Decimal, FullDelegation, Validator}; + use cosmwasm_std::{ + coin, coins, Addr, Coin, CosmosMsg, Decimal, FullDelegation, StdErrorKind, Validator, + }; use std::str::FromStr; fn sample_validator(addr: &str) -> Validator { @@ -495,12 +498,10 @@ mod tests { // make sure we can instantiate with this let res = instantiate(deps.as_mut(), mock_env(), info, msg); - match res.unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "my-validator is not in the current validator set") - } - _ => panic!("expected unregistered validator error"), - } + assert!(res + .unwrap_err() + .to_string() + .ends_with("my-validator is not in the current validator set")); } #[test] @@ -682,9 +683,9 @@ mod tests { // try to bond and make sure we trigger delegation let res = execute(deps.as_mut(), mock_env(), info, bond_msg); match res.unwrap_err() { - StakingError::Std { - original: StdError::GenericErr { msg, .. }, - } => assert_eq!(msg, "No ustake tokens sent"), + StakingError::Std { original } => { + assert!(original.to_string().ends_with("No ustake tokens sent")) + } err => panic!("Unexpected error: {err:?}"), }; } @@ -735,11 +736,12 @@ mod tests { }; let info = message_info(&creator, &[]); let res = execute(deps.as_mut(), mock_env(), info, unbond_msg); - match res.unwrap_err() { + let err = res.unwrap_err(); + match err { StakingError::Std { - original: StdError::Overflow { .. }, - } => {} - err => panic!("Unexpected error: {err:?}"), + original: std_err, .. + } if std_err.kind() == StdErrorKind::Overflow => (), + _ => panic!("Unexpected error: {err:?}"), } // bob unbonds 600 tokens at 10% tax... diff --git a/contracts/staking/src/state.rs b/contracts/staking/src/state.rs index e2a9543057..d3e56797c7 100644 --- a/contracts/staking/src/state.rs +++ b/contracts/staking/src/state.rs @@ -43,7 +43,7 @@ pub fn load_map( key: &CanonicalAddr, ) -> StdResult { may_load_map(storage, prefix, key)? - .ok_or_else(|| StdError::not_found(format!("map value for {key}"))) + .ok_or_else(|| StdError::msg(format!("map value for {key} not found"))) } /// Investment info is fixed at initialization, and is used to control the function of the contract @@ -88,7 +88,7 @@ pub struct Supply { pub fn load_item(storage: &dyn Storage, key: &[u8]) -> StdResult { storage .get(&to_length_prefixed(key)) - .ok_or_else(|| StdError::not_found(type_name::())) + .ok_or_else(|| StdError::msg(format_args!("{} not found", type_name::()))) .and_then(from_json) } diff --git a/contracts/staking/tests/integration.rs b/contracts/staking/tests/integration.rs index 560c9bdd93..1dc938cccf 100644 --- a/contracts/staking/tests/integration.rs +++ b/contracts/staking/tests/integration.rs @@ -52,7 +52,7 @@ fn initialization_with_missing_validator() { let msg = res.unwrap_err(); assert_eq!( msg, - "Generic error: my-validator is not in the current validator set" + "kind: Other, error: my-validator is not in the current validator set" ); } diff --git a/contracts/virus/src/errors.rs b/contracts/virus/src/errors.rs index 5a5361b574..dd4f4a4991 100644 --- a/contracts/virus/src/errors.rs +++ b/contracts/virus/src/errors.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Instantiate2AddressError, StdError}; use thiserror::Error; -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug)] pub enum ContractError { #[error("{0}")] /// this is needed so we can use `bucket.load(...)?` and have it auto-converted to the custom error diff --git a/devtools/build_min.sh b/devtools/build_min.sh index 880cd91293..c8f4166c4a 100755 --- a/devtools/build_min.sh +++ b/devtools/build_min.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" diff --git a/devtools/check_contracts_fast.sh b/devtools/check_contracts_fast.sh index a0f03a009b..49e20eba0b 100755 --- a/devtools/check_contracts_fast.sh +++ b/devtools/check_contracts_fast.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" diff --git a/devtools/check_contracts_full.sh b/devtools/check_contracts_full.sh index 35947c1029..fe4ecda001 100755 --- a/devtools/check_contracts_full.sh +++ b/devtools/check_contracts_full.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" diff --git a/devtools/check_contracts_medium.sh b/devtools/check_contracts_medium.sh index e8ecc78ad6..f59952ecb7 100755 --- a/devtools/check_contracts_medium.sh +++ b/devtools/check_contracts_medium.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" diff --git a/devtools/check_workspace.sh b/devtools/check_workspace.sh index f9c72e2f79..966222a5fe 100755 --- a/devtools/check_workspace.sh +++ b/devtools/check_workspace.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" diff --git a/devtools/clean.sh b/devtools/clean.sh index ad30e74210..ae8a28dfe9 100755 --- a/devtools/clean.sh +++ b/devtools/clean.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" diff --git a/devtools/clean_contracts.sh b/devtools/clean_contracts.sh index 96f08663b2..62aed3d386 100755 --- a/devtools/clean_contracts.sh +++ b/devtools/clean_contracts.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" diff --git a/devtools/format_contracts.sh b/devtools/format_contracts.sh new file mode 100755 index 0000000000..1d8d089a3f --- /dev/null +++ b/devtools/format_contracts.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -o errexit -o nounset -o pipefail +command -v shellcheck >/dev/null && shellcheck "$0" + +for contract_dir in contracts/*/; do + ( + cd "$contract_dir" + cargo fmt + ) +done diff --git a/devtools/format_md.sh b/devtools/format_md.sh index 8e01e82656..b800dc6d46 100755 --- a/devtools/format_md.sh +++ b/devtools/format_md.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" diff --git a/devtools/format_sh.sh b/devtools/format_sh.sh index 236395b538..8e186911f2 100755 --- a/devtools/format_sh.sh +++ b/devtools/format_sh.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" diff --git a/devtools/format_yml.sh b/devtools/format_yml.sh index 0d6f5e88ba..4db25c50dd 100755 --- a/devtools/format_yml.sh +++ b/devtools/format_yml.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" diff --git a/devtools/test_workspace.sh b/devtools/test_workspace.sh index aa0fa25665..39fe143df4 100755 --- a/devtools/test_workspace.sh +++ b/devtools/test_workspace.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" diff --git a/devtools/update_crate.sh b/devtools/update_crate.sh index 108c040056..716bbaa5d3 100755 --- a/devtools/update_crate.sh +++ b/devtools/update_crate.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" diff --git a/devtools/update_schemas.sh b/devtools/update_schemas.sh index f5f8c083e3..59632c015f 100755 --- a/devtools/update_schemas.sh +++ b/devtools/update_schemas.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" diff --git a/packages/check/src/main.rs b/packages/check/src/main.rs index 818a9ccff2..57c29c504b 100644 --- a/packages/check/src/main.rs +++ b/packages/check/src/main.rs @@ -9,7 +9,7 @@ use anyhow::Context; use clap::{Arg, ArgAction, Command}; use colored::Colorize; -use cosmwasm_std::from_json; +use cosmwasm_std::{from_json, StdResultExt}; use cosmwasm_vm::internals::{check_wasm, compile, make_compiling_engine, LogOutput, Logger}; use cosmwasm_vm::{capabilities_from_csv, WasmLimits}; @@ -119,11 +119,18 @@ If this is not provided, the default values are used.") fn read_wasm_limits(input: &str) -> anyhow::Result { // try to parse JSON, if that fails, try to open as File and parse that from_json::(input) + .unwrap_std_error() + .map_err(anyhow::Error::from_boxed) .context("error parsing wasm limits") .or_else(|_| { std::fs::read_to_string(input) .context("error reading wasm limits file") - .and_then(|s| from_json(s).context("error parsing wasm limits file")) + .and_then(|s| { + from_json(s) + .unwrap_std_error() + .map_err(anyhow::Error::from_boxed) + .context("error parsing wasm limits file") + }) }) } diff --git a/packages/check/tests/cosmwasm_check_tests.rs b/packages/check/tests/cosmwasm_check_tests.rs index 9dc3f085de..7319a18838 100644 --- a/packages/check/tests/cosmwasm_check_tests.rs +++ b/packages/check/tests/cosmwasm_check_tests.rs @@ -1,5 +1,5 @@ use assert_cmd::prelude::*; -use cosmwasm_std::{to_json_string, to_json_vec}; +use cosmwasm_std::{to_json_string, to_json_vec, StdResult}; use cosmwasm_vm::WasmLimits; use predicates::prelude::*; use std::{io::Write, process::Command}; @@ -116,7 +116,7 @@ fn wasm_limits_string_check() -> Result<(), Box> { } #[test] -fn wasm_limits_file_check() -> Result<(), Box> { +fn wasm_limits_file_check() -> StdResult<()> { let mut cmd = Command::cargo_bin("cosmwasm-check")?; let mut limits = WasmLimits::default(); diff --git a/packages/crypto/testdata/extract_sig_gen.sh b/packages/crypto/testdata/extract_sig_gen.sh index 8bc2e35d3c..a6314c62e6 100755 --- a/packages/crypto/testdata/extract_sig_gen.sh +++ b/packages/crypto/testdata/extract_sig_gen.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash function usage() { echo "Usage: $0 [-c|--curves] [-h|--hashes] .txt [CURVE] [HASH]" @@ -37,7 +37,7 @@ HASH="$3" [ -z "$HASH" ] && HASH="SHA-256" cat $SIGGEN | - sed 's/ //' | + sed 's/\r//' | sed -n -E "/^\[$CURVE,$HASH\]/,/^\[/{s/\[$CURVE,$HASH\]/\[/;s/^Msg = *([^ ]*)/ {\n \"message\": \"\1\",/;/^Qx =/{N;s/^Qx = *([^ ]*)\nQy = *([^ ]*)/ \"pubkey\": \"04\1\2\",/};/^R =/{N;s/^R = *([^ ]*)\nS = *([^ ]*)/ \"signature\": \"\1\2\"\n\ },/};s/^\[.*\]/\]/;p}" | grep -E '[[{}"]|]' | tac | diff --git a/packages/std/src/addresses.rs b/packages/std/src/addresses.rs index fb861914cf..f76a4d00d8 100644 --- a/packages/std/src/addresses.rs +++ b/packages/std/src/addresses.rs @@ -309,8 +309,7 @@ impl fmt::Display for Instantiate2AddressError { /// let canonical_creator = deps.api.addr_canonicalize(env.contract.address.as_str())?; /// let checksum = HexBinary::from_hex("9af782a3a1bcbcd22dbb6a45c751551d9af782a3a1bcbcd22dbb6a45c751551d")?; /// let salt = b"instance 1231"; -/// let canonical_addr = instantiate2_address(&checksum, &canonical_creator, salt) -/// .map_err(|_| StdError::generic_err("Could not calculate addr"))?; +/// let canonical_addr = instantiate2_address(&checksum, &canonical_creator, salt)?; /// let addr = deps.api.addr_humanize(&canonical_addr)?; /// /// # Ok(Default::default()) diff --git a/packages/std/src/assertions.rs b/packages/std/src/assertions.rs index 30b7ccc6fb..d97991a8e5 100644 --- a/packages/std/src/assertions.rs +++ b/packages/std/src/assertions.rs @@ -107,17 +107,17 @@ macro_rules! ensure_ne { #[cfg(test)] mod tests { - use crate::StdError; + use crate::{errors::ErrorKind, StdError}; #[test] fn ensure_works() { fn check(a: usize, b: usize) -> Result<(), StdError> { - ensure!(a == b, StdError::generic_err("foobar")); + ensure!(a == b, StdError::msg("foobar")); Ok(()) } let err = check(5, 6).unwrap_err(); - assert!(matches!(err, StdError::GenericErr { .. })); + assert!(matches!(err.kind(), ErrorKind::Other)); check(5, 5).unwrap(); } @@ -125,12 +125,12 @@ mod tests { #[test] fn ensure_can_infer_error_type() { let check = |a, b| { - ensure!(a == b, StdError::generic_err("foobar")); + ensure!(a == b, StdError::msg("foobar")); Ok(()) }; - let err = check(5, 6).unwrap_err(); - assert!(matches!(err, StdError::GenericErr { .. })); + let err: StdError = check(5, 6).unwrap_err(); + assert!(matches!(err.kind(), ErrorKind::Other)); check(5, 5).unwrap(); } @@ -147,7 +147,7 @@ mod tests { } fn check(a: usize, b: usize) -> Result<(), ContractError> { - ensure!(a == b, StdError::generic_err("foobar")); + ensure!(a == b, StdError::msg("foobar")); Ok(()) } @@ -160,12 +160,12 @@ mod tests { #[test] fn ensure_eq_works() { let check = |a, b| { - ensure_eq!(a, b, StdError::generic_err("foobar")); + ensure_eq!(a, b, StdError::msg("foobar")); Ok(()) }; - let err = check("123", "456").unwrap_err(); - assert!(matches!(err, StdError::GenericErr { .. })); + let err: StdError = check("123", "456").unwrap_err(); + assert!(matches!(err.kind(), ErrorKind::Other)); check("123", "123").unwrap(); } @@ -176,7 +176,7 @@ mod tests { #[allow(clippy::nonminimal_bool)] fn check() -> Result<(), StdError> { - ensure_eq!(true || false, false, StdError::generic_err("foobar")); + ensure_eq!(true || false, false, StdError::msg("foobar")); Ok(()) } @@ -186,12 +186,12 @@ mod tests { #[test] fn ensure_ne_works() { let check = |a, b| { - ensure_ne!(a, b, StdError::generic_err("foobar")); + ensure_ne!(a, b, StdError::msg("foobar")); Ok(()) }; - let err = check("123", "123").unwrap_err(); - assert!(matches!(err, StdError::GenericErr { .. })); + let err: StdError = check("123", "123").unwrap_err(); + assert!(matches!(err.kind(), ErrorKind::Other)); check("123", "456").unwrap(); } @@ -202,7 +202,7 @@ mod tests { #[allow(clippy::nonminimal_bool)] fn check() -> Result<(), StdError> { - ensure_ne!(true || false, false, StdError::generic_err("foobar")); + ensure_ne!(true || false, false, StdError::msg("foobar")); Ok(()) } diff --git a/packages/std/src/binary.rs b/packages/std/src/binary.rs index d627679ab7..e921c3e455 100644 --- a/packages/std/src/binary.rs +++ b/packages/std/src/binary.rs @@ -6,7 +6,7 @@ use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::{ encoding::{from_base64, to_base64}, - errors::{StdError, StdResult}, + errors::{ErrorKind, StdError, StdResult}, }; /// Binary is a wrapper around Vec to add base64 de/serialization @@ -73,7 +73,10 @@ impl Binary { /// ``` pub fn to_array(&self) -> StdResult<[u8; LENGTH]> { if self.len() != LENGTH { - return Err(StdError::invalid_data_size(LENGTH, self.len())); + return Err(StdError::msg(format_args!( + "invalid length. expected {LENGTH}, got {}", + self.len() + ))); } let mut out: [u8; LENGTH] = [0; LENGTH]; @@ -82,7 +85,8 @@ impl Binary { } pub fn from_hex(input: &str) -> StdResult { - let binary = hex::decode(input).map_err(StdError::invalid_hex)?; + let binary = hex::decode(input) + .map_err(|err| StdError::from(err).with_kind(ErrorKind::InvalidData))?; Ok(Self(binary)) } @@ -291,7 +295,6 @@ impl de::Visitor<'_> for BytesVisitor { mod tests { use super::*; use crate::assert_hash_works; - use crate::errors::StdError; #[test] fn to_array_works() { @@ -308,15 +311,9 @@ mod tests { // invalid size let binary = Binary::from(&[1, 2, 3]); let error = binary.to_array::<8>().unwrap_err(); - match error { - StdError::InvalidDataSize { - expected, actual, .. - } => { - assert_eq!(expected, 8); - assert_eq!(actual, 3); - } - err => panic!("Unexpected error: {err:?}"), - } + assert!(error + .to_string() + .ends_with("invalid length. expected 8, got 3")); // long array (32 bytes) let binary = Binary::from_base64("t119JOQox4WUQEmO/nyqOZfO+wjJm91YG2sfn4ZglvA=").unwrap(); @@ -356,8 +353,8 @@ mod tests { ] { let value = Binary::from(value); assert_eq!(encoded, value.to_base64()); - assert_eq!(Ok(value.clone()), Binary::from_base64(encoded)); - assert_eq!(Ok(value.clone()), Binary::from_base64(encoded_no_pad)); + assert_eq!(value.clone(), Binary::from_base64(encoded).unwrap()); + assert_eq!(value.clone(), Binary::from_base64(encoded_no_pad).unwrap()); } } @@ -367,10 +364,10 @@ mod tests { ("cm%uZG9taVo", "Invalid symbol 37, offset 2."), ("cmFuZ", "Invalid input length: 5"), ] { - match Binary::from_base64(invalid_base64) { - Err(StdError::InvalidBase64 { msg, .. }) => assert_eq!(want, msg), - result => panic!("Unexpected result: {result:?}"), - } + assert!(Binary::from_base64(invalid_base64) + .unwrap_err() + .to_string() + .ends_with(want)); } } diff --git a/packages/std/src/checksum.rs b/packages/std/src/checksum.rs index c1d8cfb0a2..7c57bf9589 100644 --- a/packages/std/src/checksum.rs +++ b/packages/std/src/checksum.rs @@ -5,6 +5,7 @@ use serde::{de, ser, Deserialize, Deserializer, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; +use crate::errors::ErrorKind; use crate::prelude::*; use crate::{StdError, StdResult}; @@ -26,7 +27,8 @@ impl Checksum { /// Errors if the string contains non-hex characters or does not contain 32 bytes. pub fn from_hex(input: &str) -> StdResult { let mut binary = [0u8; 32]; - hex::decode_to_slice(input, &mut binary).map_err(StdError::invalid_hex)?; + hex::decode_to_slice(input, &mut binary) + .map_err(|err| StdError::from(err).with_kind(ErrorKind::Parsing))?; Ok(Self(binary)) } diff --git a/packages/std/src/coins.rs b/packages/std/src/coins.rs index 09354b52b0..d311025997 100644 --- a/packages/std/src/coins.rs +++ b/packages/std/src/coins.rs @@ -380,7 +380,7 @@ mod tests { assert_eq!(Coins::default().to_string(), ""); assert_eq!( Coins::from_str(invalid).unwrap_err().to_string(), - "Generic error: Parsing Coin: Missing amount or non-digit characters in amount" + "kind: Other, error: Missing amount or non-digit characters in amount" ); } @@ -452,12 +452,10 @@ mod tests { let mut coins: Coins = coin(12345, "uatom").into(); // sub more than available - let err = coins.sub(coin(12346, "uatom")).unwrap_err(); - assert!(matches!(err, StdError::Overflow { .. })); + assert!(coins.sub(coin(12346, "uatom")).is_err()); // sub non-existent denom - let err = coins.sub(coin(12345, "uusd")).unwrap_err(); - assert!(matches!(err, StdError::Overflow { .. })); + assert!(coins.sub(coin(12345, "uusd")).is_err()); // partial sub coins.sub(coin(1, "uatom")).unwrap(); diff --git a/packages/std/src/encoding.rs b/packages/std/src/encoding.rs index 6b835b3c2d..3dfc27bea2 100644 --- a/packages/std/src/encoding.rs +++ b/packages/std/src/encoding.rs @@ -1,7 +1,7 @@ use alloc::{string::String, vec::Vec}; use base64::{engine::GeneralPurpose, Engine}; -use crate::{StdError, StdResult}; +use crate::StdResult; /// Base64 encoding engine used in conversion to/from base64. /// @@ -18,7 +18,7 @@ pub fn from_base64(input: I) -> StdResult> where I: AsRef<[u8]>, { - B64_ENGINE.decode(input).map_err(StdError::invalid_base64) + Ok(B64_ENGINE.decode(input)?) } /// Encode a bag of bytes into the Base64 format @@ -34,7 +34,7 @@ pub fn from_hex(input: I) -> StdResult> where I: AsRef<[u8]>, { - hex::decode(input).map_err(StdError::invalid_hex) + Ok(hex::decode(input)?) } /// Encode a bag of bytes into the hex format diff --git a/packages/std/src/errors/backtrace.rs b/packages/std/src/errors/backtrace.rs index 14b92d851e..41bc3f06f1 100644 --- a/packages/std/src/errors/backtrace.rs +++ b/packages/std/src/errors/backtrace.rs @@ -54,24 +54,6 @@ impl Display for Stub { } } -/// This macro implements `From` for a given error type to a given error type where -/// the target error has a `backtrace` field. -/// This is meant as a replacement for `thiserror`'s `#[from]` attribute, which does not -/// work with our custom backtrace wrapper. -macro_rules! impl_from_err { - ($from:ty, $to:ty, $map:path) => { - impl From<$from> for $to { - fn from(err: $from) -> Self { - $map { - source: err, - backtrace: $crate::errors::backtrace::BT::capture(), - } - } - } - }; -} -pub(crate) use impl_from_err; - #[cfg(test)] mod tests { use super::*; diff --git a/packages/std/src/errors/mod.rs b/packages/std/src/errors/mod.rs index 768af1d633..6edf0bf84d 100644 --- a/packages/std/src/errors/mod.rs +++ b/packages/std/src/errors/mod.rs @@ -4,13 +4,13 @@ mod std_error; mod system_error; mod verification_error; -pub(crate) use backtrace::{impl_from_err, BT}; +pub(crate) use backtrace::BT; pub use recover_pubkey_error::RecoverPubkeyError; pub use std_error::{ CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError, CoinFromStrError, CoinsError, ConversionOverflowError, DivideByZeroError, DivisionError, - OverflowError, OverflowOperation, RoundDownOverflowError, RoundUpOverflowError, StdError, - StdResult, + ErrorKind, OverflowError, OverflowOperation, RoundDownOverflowError, RoundUpOverflowError, + StdError, StdResult, StdResultExt, }; pub use system_error::SystemError; pub use verification_error::{AggregationError, PairingEqualityError, VerificationError}; diff --git a/packages/std/src/errors/std_error.rs b/packages/std/src/errors/std_error.rs index 875d299cac..7950eba803 100644 --- a/packages/std/src/errors/std_error.rs +++ b/packages/std/src/errors/std_error.rs @@ -1,10 +1,27 @@ -use alloc::string::{String, ToString}; +use alloc::string::ToString; use core::fmt; +use std::{error::Error, ops::Deref, str, string}; -use super::{impl_from_err, BT}; +use super::BT; use crate::errors::{RecoverPubkeyError, VerificationError}; +mod sealed { + pub trait Sealed {} + + impl Sealed for Result {} +} + +pub trait StdResultExt: sealed::Sealed { + fn unwrap_std_error(self) -> Result>; +} + +impl StdResultExt for Result { + fn unwrap_std_error(self) -> Result> { + self.map_err(|err| err.0.inner) + } +} + /// Structured error type for init, execute and query. /// /// This can be serialized and passed over the Wasm/VM boundary, which allows us to use structured @@ -20,377 +37,127 @@ use crate::errors::{RecoverPubkeyError, VerificationError}; /// Checklist for adding a new error: /// - Add enum case /// - Add creator function in std_error_helpers.rs -#[derive(Debug, thiserror::Error)] -pub enum StdError { - #[error("Verification error: {source}")] - VerificationErr { - source: VerificationError, - backtrace: BT, - }, - #[error("Recover pubkey error: {source}")] - RecoverPubkeyErr { - source: RecoverPubkeyError, - backtrace: BT, - }, - /// Whenever there is no specific error type available - #[error("Generic error: {msg}")] - GenericErr { msg: String, backtrace: BT }, - #[error("Invalid Base64 string: {msg}")] - InvalidBase64 { msg: String, backtrace: BT }, - #[error("Invalid data size: expected={expected} actual={actual}")] - InvalidDataSize { - expected: u64, - actual: u64, - backtrace: BT, - }, - #[error("Invalid hex string: {msg}")] - InvalidHex { msg: String, backtrace: BT }, - /// Whenever UTF-8 bytes cannot be decoded into a unicode string, e.g. in String::from_utf8 or str::from_utf8. - #[error("Cannot decode UTF8 bytes into string: {msg}")] - InvalidUtf8 { msg: String, backtrace: BT }, - #[error("{kind} not found")] - NotFound { kind: String, backtrace: BT }, - #[error("Error parsing into type {target_type}: {msg}")] - ParseErr { - /// the target type that was attempted - target_type: String, - msg: String, - backtrace: BT, - }, - #[error("Error serializing type {source_type}: {msg}")] - SerializeErr { - /// the source type that was attempted - source_type: String, - msg: String, - backtrace: BT, - }, - #[error("Overflow: {source}")] - Overflow { - source: OverflowError, - backtrace: BT, - }, - #[error("Divide by zero: {source}")] - DivideByZero { - source: DivideByZeroError, - backtrace: BT, - }, - #[error("Conversion error: ")] - ConversionOverflow { - source: ConversionOverflowError, - backtrace: BT, - }, +#[derive(Debug)] +pub struct StdError(Box); + +#[derive(Debug)] +struct InnerError { + backtrace: BT, + kind: ErrorKind, + inner: Box, } -impl_from_err!( - ConversionOverflowError, - StdError, - StdError::ConversionOverflow -); - -impl StdError { - pub fn verification_err(source: VerificationError) -> Self { - StdError::VerificationErr { - source, - backtrace: BT::capture(), - } - } - - pub fn recover_pubkey_err(source: RecoverPubkeyError) -> Self { - StdError::RecoverPubkeyErr { - source, - backtrace: BT::capture(), - } - } - - pub fn generic_err(msg: impl Into) -> Self { - StdError::GenericErr { - msg: msg.into(), - backtrace: BT::capture(), - } - } - - pub fn invalid_base64(msg: impl ToString) -> Self { - StdError::InvalidBase64 { - msg: msg.to_string(), - backtrace: BT::capture(), - } - } - - pub fn invalid_data_size(expected: usize, actual: usize) -> Self { - StdError::InvalidDataSize { - // Cast is safe because usize is 32 or 64 bit large in all environments we support - expected: expected as u64, - actual: actual as u64, - backtrace: BT::capture(), - } - } - - pub fn invalid_hex(msg: impl ToString) -> Self { - StdError::InvalidHex { - msg: msg.to_string(), - backtrace: BT::capture(), - } - } - - pub fn invalid_utf8(msg: impl ToString) -> Self { - StdError::InvalidUtf8 { - msg: msg.to_string(), - backtrace: BT::capture(), - } - } +const _: () = { + // Assert smolness (˶ᵔ ᵕ ᵔ˶) + assert!(std::mem::size_of::() == std::mem::size_of::()); +}; - pub fn not_found(kind: impl Into) -> Self { - StdError::NotFound { - kind: kind.into(), - backtrace: BT::capture(), - } +impl AsRef for StdError { + fn as_ref(&self) -> &(dyn Error + Send + Sync + 'static) { + &*self.0.inner } +} - pub fn parse_err(target: impl Into, msg: impl ToString) -> Self { - StdError::ParseErr { - target_type: target.into(), - msg: msg.to_string(), - backtrace: BT::capture(), - } - } +impl Deref for StdError { + type Target = dyn Error + Send + Sync; - pub fn serialize_err(source: impl Into, msg: impl ToString) -> Self { - StdError::SerializeErr { - source_type: source.into(), - msg: msg.to_string(), - backtrace: BT::capture(), - } + fn deref(&self) -> &Self::Target { + &*self.0.inner } +} - pub fn overflow(source: OverflowError) -> Self { - StdError::Overflow { - source, +impl StdError { + pub fn msg(msg: D) -> Self + where + D: fmt::Display, + { + Self(Box::new(InnerError { backtrace: BT::capture(), - } + kind: ErrorKind::Other, + inner: msg.to_string().into(), + })) } - pub fn divide_by_zero(source: DivideByZeroError) -> Self { - StdError::DivideByZero { - source, - backtrace: BT::capture(), - } + pub fn backtrace(&self) -> &BT { + &self.0.backtrace } -} -impl PartialEq for StdError { - fn eq(&self, rhs: &StdError) -> bool { - match self { - StdError::VerificationErr { - source, - backtrace: _, - } => { - if let StdError::VerificationErr { - source: rhs_source, - backtrace: _, - } = rhs - { - source == rhs_source - } else { - false - } - } - StdError::RecoverPubkeyErr { - source, - backtrace: _, - } => { - if let StdError::RecoverPubkeyErr { - source: rhs_source, - backtrace: _, - } = rhs - { - source == rhs_source - } else { - false - } - } - StdError::GenericErr { msg, backtrace: _ } => { - if let StdError::GenericErr { - msg: rhs_msg, - backtrace: _, - } = rhs - { - msg == rhs_msg - } else { - false - } - } - StdError::InvalidBase64 { msg, backtrace: _ } => { - if let StdError::InvalidBase64 { - msg: rhs_msg, - backtrace: _, - } = rhs - { - msg == rhs_msg - } else { - false - } - } - StdError::InvalidDataSize { - expected, - actual, - backtrace: _, - } => { - if let StdError::InvalidDataSize { - expected: rhs_expected, - actual: rhs_actual, - backtrace: _, - } = rhs - { - expected == rhs_expected && actual == rhs_actual - } else { - false - } - } - StdError::InvalidHex { msg, backtrace: _ } => { - if let StdError::InvalidHex { - msg: rhs_msg, - backtrace: _, - } = rhs - { - msg == rhs_msg - } else { - false - } - } - StdError::InvalidUtf8 { msg, backtrace: _ } => { - if let StdError::InvalidUtf8 { - msg: rhs_msg, - backtrace: _, - } = rhs - { - msg == rhs_msg - } else { - false - } - } - StdError::NotFound { kind, backtrace: _ } => { - if let StdError::NotFound { - kind: rhs_kind, - backtrace: _, - } = rhs - { - kind == rhs_kind - } else { - false - } - } - StdError::ParseErr { - target_type, - msg, - backtrace: _, - } => { - if let StdError::ParseErr { - target_type: rhs_target_type, - msg: rhs_msg, - backtrace: _, - } = rhs - { - target_type == rhs_target_type && msg == rhs_msg - } else { - false - } - } - StdError::SerializeErr { - source_type, - msg, - backtrace: _, - } => { - if let StdError::SerializeErr { - source_type: rhs_source_type, - msg: rhs_msg, - backtrace: _, - } = rhs - { - source_type == rhs_source_type && msg == rhs_msg - } else { - false - } - } - StdError::Overflow { - source, - backtrace: _, - } => { - if let StdError::Overflow { - source: rhs_source, - backtrace: _, - } = rhs - { - source == rhs_source - } else { - false - } - } - StdError::DivideByZero { - source, - backtrace: _, - } => { - if let StdError::DivideByZero { - source: rhs_source, - backtrace: _, - } = rhs - { - source == rhs_source - } else { - false - } - } - StdError::ConversionOverflow { - source, - backtrace: _, - } => { - if let StdError::ConversionOverflow { - source: rhs_source, - backtrace: _, - } = rhs - { - source == rhs_source - } else { - false - } - } - } + pub fn is(&self) -> bool + where + T: Error + 'static, + { + self.0.inner.is::() } -} -impl From for StdError { - fn from(source: core::str::Utf8Error) -> Self { - Self::invalid_utf8(source) + pub fn kind(&self) -> ErrorKind { + self.0.kind } -} -impl From for StdError { - fn from(source: alloc::string::FromUtf8Error) -> Self { - Self::invalid_utf8(source) + pub fn with_kind(mut self, kind: ErrorKind) -> Self { + self.0.kind = kind; + self } } -impl From for StdError { - fn from(source: VerificationError) -> Self { - Self::verification_err(source) +impl fmt::Display for StdError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "kind: {:?}, error: {}", self.0.kind, self.0.inner) } } -impl From for StdError { - fn from(source: RecoverPubkeyError) -> Self { - Self::recover_pubkey_err(source) +// Impossible to implement because of blanket `From` impls :( +/*impl Error for StdError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.0.inner.source() + } +}*/ + +impl From for StdError +where + E: Error + Send + Sync + 'static, +{ + fn from(value: E) -> Self { + let inner: Box = Box::new(value); + + // "mom, can we have specialization?" + // "we have specialization at home" + // specialization at home: + let kind = if inner.is::() || inner.is::() { + ErrorKind::Parsing + } else if inner.is::() || inner.is::() { + ErrorKind::Overflow + } else if inner.is::() + || inner.is::() + || inner.is::() + { + ErrorKind::Serialization + } else if inner.is::() || inner.is::() { + ErrorKind::Cryptography + } else if inner.is::() || inner.is::() { + ErrorKind::Encoding + } else { + ErrorKind::Other + }; + + Self(Box::new(InnerError { + backtrace: BT::capture(), + kind, + inner, + })) } } -impl From for StdError { - fn from(source: OverflowError) -> Self { - Self::overflow(source) - } -} +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[non_exhaustive] +pub enum ErrorKind { + Cryptography, + Encoding, + InvalidData, + Overflow, + Parsing, + Serialization, -impl From for StdError { - fn from(source: DivideByZeroError) -> Self { - Self::divide_by_zero(source) - } + Other, } /// The return type for init, execute and query. Since the error type cannot be serialized to JSON, @@ -398,7 +165,7 @@ impl From for StdError { /// /// The prefix "Core"/"Std" means "the standard result within the core/standard library". This is not the only /// result/error type in cosmwasm-core/cosmwasm-std. -pub type StdResult = core::result::Result; +pub type StdResult = core::result::Result; #[derive(Debug, PartialEq, Eq)] pub enum OverflowOperation { @@ -513,12 +280,6 @@ pub enum CoinsError { DuplicateDenom, } -impl From for StdError { - fn from(value: CoinsError) -> Self { - Self::generic_err(format!("Creating Coins: {value}")) - } -} - #[derive(Debug, PartialEq, Eq, thiserror::Error)] pub enum CoinFromStrError { #[error("Missing denominator")] @@ -535,200 +296,16 @@ impl From for CoinFromStrError { } } -impl From for StdError { - fn from(value: CoinFromStrError) -> Self { - Self::generic_err(format!("Parsing Coin: {value}")) - } -} - #[cfg(test)] mod tests { use super::*; use core::str; + use std::string; - // constructors - - // example of reporting contract errors with format! - #[test] - fn generic_err_owned() { - let guess = 7; - let error = StdError::generic_err(format!("{guess} is too low")); - match error { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, String::from("7 is too low")); - } - e => panic!("unexpected error, {e:?}"), - } - } - - // example of reporting static contract errors - #[test] - fn generic_err_ref() { - let error = StdError::generic_err("not implemented"); - match error { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "not implemented"), - e => panic!("unexpected error, {e:?}"), - } - } - - #[test] - fn invalid_base64_works_for_strings() { - let error = StdError::invalid_base64("my text"); - match error { - StdError::InvalidBase64 { msg, .. } => { - assert_eq!(msg, "my text"); - } - _ => panic!("expect different error"), - } - } - - #[test] - fn invalid_base64_works_for_errors() { - let original = base64::DecodeError::InvalidLength(10); - let error = StdError::invalid_base64(original); - match error { - StdError::InvalidBase64 { msg, .. } => { - assert_eq!(msg, "Invalid input length: 10"); - } - _ => panic!("expect different error"), - } - } - - #[test] - fn invalid_data_size_works() { - let error = StdError::invalid_data_size(31, 14); - match error { - StdError::InvalidDataSize { - expected, actual, .. - } => { - assert_eq!(expected, 31); - assert_eq!(actual, 14); - } - _ => panic!("expect different error"), - } - } - - #[test] - fn invalid_hex_works_for_strings() { - let error = StdError::invalid_hex("my text"); - match error { - StdError::InvalidHex { msg, .. } => { - assert_eq!(msg, "my text"); - } - _ => panic!("expect different error"), - } - } - - #[test] - fn invalid_hex_works_for_errors() { - let original = hex::FromHexError::OddLength; - let error = StdError::invalid_hex(original); - match error { - StdError::InvalidHex { msg, .. } => { - assert_eq!(msg, "Odd number of digits"); - } - _ => panic!("expect different error"), - } - } - - #[test] - fn invalid_utf8_works_for_strings() { - let error = StdError::invalid_utf8("my text"); - match error { - StdError::InvalidUtf8 { msg, .. } => { - assert_eq!(msg, "my text"); - } - _ => panic!("expect different error"), - } - } - - #[test] - fn invalid_utf8_works_for_errors() { - let original = String::from_utf8(vec![0x80]).unwrap_err(); - let error = StdError::invalid_utf8(original); - match error { - StdError::InvalidUtf8 { msg, .. } => { - assert_eq!(msg, "invalid utf-8 sequence of 1 bytes from index 0"); - } - _ => panic!("expect different error"), - } - } - - #[test] - fn not_found_works() { - let error = StdError::not_found("gold"); - match error { - StdError::NotFound { kind, .. } => assert_eq!(kind, "gold"), - _ => panic!("expect different error"), - } - } - - #[test] - fn parse_err_works() { - let error = StdError::parse_err("Book", "Missing field: title"); - match error { - StdError::ParseErr { - target_type, msg, .. - } => { - assert_eq!(target_type, "Book"); - assert_eq!(msg, "Missing field: title"); - } - _ => panic!("expect different error"), - } - } - - #[test] - fn serialize_err_works() { - let error = StdError::serialize_err("Book", "Content too long"); - match error { - StdError::SerializeErr { - source_type, msg, .. - } => { - assert_eq!(source_type, "Book"); - assert_eq!(msg, "Content too long"); - } - _ => panic!("expect different error"), - } - } - - #[test] - fn underflow_works_for_u128() { - let error = StdError::overflow(OverflowError::new(OverflowOperation::Sub)); - assert!(matches!( - error, - StdError::Overflow { - source: OverflowError { - operation: OverflowOperation::Sub - }, - .. - } - )); - } - - #[test] - fn overflow_works_for_i64() { - let error = StdError::overflow(OverflowError::new(OverflowOperation::Sub)); - assert!(matches!( - error, - StdError::Overflow { - source: OverflowError { - operation: OverflowOperation::Sub - }, - .. - } - )); - } - - #[test] - fn divide_by_zero_works() { - let error = StdError::divide_by_zero(DivideByZeroError); - assert!(matches!( - error, - StdError::DivideByZero { - source: DivideByZeroError, - .. - } - )); + #[derive(Debug, thiserror::Error)] + enum AssertThiserrorWorks { + #[error(transparent)] + Std(#[from] StdError), } #[test] @@ -736,7 +313,7 @@ mod tests { let error: StdError = StdError::from(OverflowError::new(OverflowOperation::Sub)); let embedded = format!("Debug: {error:?}"); assert!(embedded - .starts_with("Debug: Overflow { source: OverflowError { operation: Sub }, backtrace:")); + .starts_with("Debug: StdError(InnerError { backtrace: , kind: Overflow, inner: OverflowError { operation: Sub } })"), "{embedded}"); } #[test] @@ -744,34 +321,20 @@ mod tests { let error: StdError = StdError::from(OverflowError::new(OverflowOperation::Sub)); let embedded = format!("Display: {error}"); assert_eq!( - embedded, - "Display: Overflow: Cannot Sub with given operands" + embedded, "Display: kind: Overflow, error: Cannot Sub with given operands", + "{embedded}" ); } - #[test] - fn implements_partial_eq() { - let u1 = StdError::from(OverflowError::new(OverflowOperation::Sub)); - let u2 = StdError::from(OverflowError::new(OverflowOperation::Sub)); - let s1 = StdError::serialize_err("Book", "Content too long"); - let s2 = StdError::serialize_err("Book", "Content too long"); - let s3 = StdError::serialize_err("Book", "Title too long"); - assert_eq!(u1, u2); - assert_ne!(u1, s1); - assert_eq!(s1, s2); - assert_ne!(s1, s3); - } - #[test] fn from_std_str_utf8error_works() { let broken = Vec::from(b"Hello \xF0\x90\x80World" as &[u8]); let error: StdError = str::from_utf8(&broken).unwrap_err().into(); - match error { - StdError::InvalidUtf8 { msg, .. } => { - assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6") - } - err => panic!("Unexpected error: {err:?}"), - } + assert!(error.is::()); + + assert!(error + .to_string() + .ends_with("invalid utf-8 sequence of 3 bytes from index 6")); } #[test] @@ -779,11 +342,10 @@ mod tests { let error: StdError = String::from_utf8(b"Hello \xF0\x90\x80World".to_vec()) .unwrap_err() .into(); - match error { - StdError::InvalidUtf8 { msg, .. } => { - assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6") - } - err => panic!("Unexpected error: {err:?}"), - } + + assert!(error.is::()); + assert!(error + .to_string() + .ends_with("invalid utf-8 sequence of 3 bytes from index 6")); } } diff --git a/packages/std/src/exports/imports.rs b/packages/std/src/exports/imports.rs index d68d2229a9..43c67f8aa1 100644 --- a/packages/std/src/exports/imports.rs +++ b/packages/std/src/exports/imports.rs @@ -328,7 +328,7 @@ impl Api for ExternalApi { // See MAX_LENGTH_HUMAN_ADDRESS in the VM. // In this case, the VM will refuse to read the input from the contract. // Stop here to allow handling the error in the contract. - return Err(StdError::generic_err("input too long for addr_validate")); + return Err(StdError::msg("input too long for addr_validate")); } let source = Region::from_slice(input_bytes); let source_ptr = source.as_ptr() as u32; @@ -337,7 +337,7 @@ impl Api for ExternalApi { if result != 0 { let error = unsafe { consume_string_region_written_by_vm(result as *mut Region) }; - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "addr_validate errored: {}", error ))); @@ -352,9 +352,7 @@ impl Api for ExternalApi { // See MAX_LENGTH_HUMAN_ADDRESS in the VM. // In this case, the VM will refuse to read the input from the contract. // Stop here to allow handling the error in the contract. - return Err(StdError::generic_err( - "input too long for addr_canonicalize", - )); + return Err(StdError::msg("input too long for addr_canonicalize")); } let send = Region::from_slice(input_bytes); let send_ptr = send.as_ptr() as u32; @@ -364,7 +362,7 @@ impl Api for ExternalApi { if result != 0 { let error = unsafe { consume_string_region_written_by_vm(result as *mut Region) }; - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "addr_canonicalize errored: {}", error ))); @@ -382,7 +380,7 @@ impl Api for ExternalApi { if result != 0 { let error = unsafe { consume_string_region_written_by_vm(result as *mut Region) }; - return Err(StdError::generic_err(format!( + return Err(StdError::msg(format_args!( "addr_humanize errored: {}", error ))); diff --git a/packages/std/src/hex_binary.rs b/packages/std/src/hex_binary.rs index 2a13a85543..2a78a7739c 100644 --- a/packages/std/src/hex_binary.rs +++ b/packages/std/src/hex_binary.rs @@ -64,7 +64,10 @@ impl HexBinary { /// ``` pub fn to_array(&self) -> StdResult<[u8; LENGTH]> { if self.len() != LENGTH { - return Err(StdError::invalid_data_size(LENGTH, self.len())); + return Err(StdError::msg(format_args!( + "invalid length. expected {LENGTH}, got {}", + self.len() + ))); } let mut out: [u8; LENGTH] = [0; LENGTH]; @@ -285,7 +288,7 @@ impl de::Visitor<'_> for BytesVisitor { mod tests { use super::*; - use crate::{assert_hash_works, StdError}; + use crate::{assert_hash_works, errors::ErrorKind}; #[test] fn from_hex_works() { @@ -304,42 +307,36 @@ mod tests { assert_eq!(data.as_slice(), b"randomiZ"); // odd - match HexBinary::from_hex("123").unwrap_err() { - StdError::InvalidHex { msg, .. } => { - assert_eq!(msg, "Odd number of digits") - } - _ => panic!("Unexpected error type"), - } + assert!(HexBinary::from_hex("123") + .unwrap_err() + .to_string() + .ends_with("Odd number of digits")); // non-hex - match HexBinary::from_hex("efgh").unwrap_err() { - StdError::InvalidHex { msg, .. } => { - assert_eq!(msg, "Invalid character 'g' at position 2") - } - _ => panic!("Unexpected error type"), - } + assert!(HexBinary::from_hex("efgh") + .unwrap_err() + .to_string() + .ends_with("Invalid character 'g' at position 2")); // 0x prefixed - match HexBinary::from_hex("0xaa").unwrap_err() { - StdError::InvalidHex { msg, .. } => { - assert_eq!(msg, "Invalid character 'x' at position 1") - } - _ => panic!("Unexpected error type"), - } + assert!(HexBinary::from_hex("0xaa") + .unwrap_err() + .to_string() + .ends_with("Invalid character 'x' at position 1")); // spaces assert!(matches!( - HexBinary::from_hex("aa ").unwrap_err(), - StdError::InvalidHex { .. } + HexBinary::from_hex("aa ").unwrap_err().kind(), + ErrorKind::Encoding, )); assert!(matches!( - HexBinary::from_hex(" aa").unwrap_err(), - StdError::InvalidHex { .. } + HexBinary::from_hex(" aa").unwrap_err().kind(), + ErrorKind::Encoding, )); assert!(matches!( - HexBinary::from_hex("a a").unwrap_err(), - StdError::InvalidHex { .. } + HexBinary::from_hex("a a").unwrap_err().kind(), + ErrorKind::Encoding, )); assert!(matches!( - HexBinary::from_hex(" aa ").unwrap_err(), - StdError::InvalidHex { .. } + HexBinary::from_hex(" aa ").unwrap_err().kind(), + ErrorKind::Encoding, )); } @@ -373,15 +370,9 @@ mod tests { // invalid size let binary = HexBinary::from(&[1, 2, 3]); let error = binary.to_array::<8>().unwrap_err(); - match error { - StdError::InvalidDataSize { - expected, actual, .. - } => { - assert_eq!(expected, 8); - assert_eq!(actual, 3); - } - err => panic!("Unexpected error: {err:?}"), - } + assert!(error + .to_string() + .ends_with("invalid length. expected 8, got 3")); // long array (32 bytes) let binary = diff --git a/packages/std/src/iterator.rs b/packages/std/src/iterator.rs index e33773df82..2726159963 100644 --- a/packages/std/src/iterator.rs +++ b/packages/std/src/iterator.rs @@ -25,7 +25,7 @@ impl TryFrom for Order { match value { 1 => Ok(Order::Ascending), 2 => Ok(Order::Descending), - _ => Err(StdError::generic_err("Order must be 1 or 2")), + _ => Err(StdError::msg("Order must be 1 or 2")), } } } diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 4ec249127d..a7509507b1 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -63,9 +63,9 @@ pub use crate::encoding::{from_base64, from_hex, to_base64, to_hex}; pub use crate::errors::{ AggregationError, CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError, CoinFromStrError, CoinsError, ConversionOverflowError, - DivideByZeroError, DivisionError, OverflowError, OverflowOperation, PairingEqualityError, - RecoverPubkeyError, RoundDownOverflowError, RoundUpOverflowError, StdError, StdResult, - SystemError, VerificationError, + DivideByZeroError, DivisionError, ErrorKind as StdErrorKind, OverflowError, OverflowOperation, + PairingEqualityError, RecoverPubkeyError, RoundDownOverflowError, RoundUpOverflowError, + StdError, StdResult, StdResultExt, SystemError, VerificationError, }; pub use crate::hex_binary::HexBinary; pub use crate::ibc::IbcChannelOpenResponse; diff --git a/packages/std/src/math/decimal.rs b/packages/std/src/math/decimal.rs index 9a1394dc5c..36c45a24ca 100644 --- a/packages/std/src/math/decimal.rs +++ b/packages/std/src/math/decimal.rs @@ -6,7 +6,7 @@ use core::str::FromStr; use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::errors::{ - CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, + CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, ErrorKind, OverflowError, OverflowOperation, RoundUpOverflowError, StdError, }; use crate::forward_ref::{forward_ref_binop, forward_ref_op_assign}; @@ -573,20 +573,14 @@ impl FromStr for Decimal { let mut parts_iter = input.split('.'); let whole_part = parts_iter.next().unwrap(); // split always returns at least one element - let whole = whole_part - .parse::() - .map_err(|_| StdError::generic_err("Error parsing whole"))?; - let mut atomics = whole - .checked_mul(Self::DECIMAL_FRACTIONAL) - .map_err(|_| StdError::generic_err("Value too big"))?; + let whole = whole_part.parse::()?; + let mut atomics = whole.checked_mul(Self::DECIMAL_FRACTIONAL)?; if let Some(fractional_part) = parts_iter.next() { - let fractional = fractional_part - .parse::() - .map_err(|_| StdError::generic_err("Error parsing fractional"))?; + let fractional = fractional_part.parse::()?; let exp = (Self::DECIMAL_PLACES.checked_sub(fractional_part.len() as u32)).ok_or_else( || { - StdError::generic_err(format!( + StdError::msg(format_args!( "Cannot parse more than {} fractional digits", Self::DECIMAL_PLACES )) @@ -594,17 +588,15 @@ impl FromStr for Decimal { )?; debug_assert!(exp <= Self::DECIMAL_PLACES); let fractional_factor = Uint128::from(10u128.pow(exp)); - atomics = atomics - .checked_add( - // The inner multiplication can't overflow because - // fractional < 10^DECIMAL_PLACES && fractional_factor <= 10^DECIMAL_PLACES - fractional.checked_mul(fractional_factor).unwrap(), - ) - .map_err(|_| StdError::generic_err("Value too big"))?; + atomics = atomics.checked_add( + // The inner multiplication can't overflow because + // fractional < 10^DECIMAL_PLACES && fractional_factor <= 10^DECIMAL_PLACES + fractional.checked_mul(fractional_factor).unwrap(), + )?; } if parts_iter.next().is_some() { - return Err(StdError::generic_err("Unexpected number of dots")); + return Err(StdError::msg("Unexpected number of dots").with_kind(ErrorKind::Parsing)); } Ok(Decimal(atomics)) @@ -1115,93 +1107,47 @@ mod tests { #[test] fn decimal_from_str_errors_for_broken_whole_part() { - match Decimal::from_str("").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), - e => panic!("Unexpected error: {e:?}"), - } - - match Decimal::from_str(" ").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), - e => panic!("Unexpected error: {e:?}"), - } - - match Decimal::from_str("-1").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), - e => panic!("Unexpected error: {e:?}"), - } + assert!(Decimal::from_str("").is_err()); + assert!(Decimal::from_str(" ").is_err()); + assert!(Decimal::from_str("-1").is_err()); } #[test] fn decimal_from_str_errors_for_broken_fractional_part() { - match Decimal::from_str("1.").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match Decimal::from_str("1. ").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match Decimal::from_str("1.e").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match Decimal::from_str("1.2e3").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } + assert!(Decimal::from_str("1.").is_err()); + assert!(Decimal::from_str("1. ").is_err()); + assert!(Decimal::from_str("1.e").is_err()); + assert!(Decimal::from_str("1.2e3").is_err()); } #[test] fn decimal_from_str_errors_for_more_than_18_fractional_digits() { - match Decimal::from_str("7.1234567890123456789").unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot parse more than 18 fractional digits",) - } - e => panic!("Unexpected error: {e:?}"), - } + assert!(Decimal::from_str("7.1234567890123456789") + .unwrap_err() + .to_string() + .ends_with("Cannot parse more than 18 fractional digits")); // No special rules for trailing zeros. This could be changed but adds gas cost for the happy path. - match Decimal::from_str("7.1230000000000000000").unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot parse more than 18 fractional digits") - } - e => panic!("Unexpected error: {e:?}"), - } + assert!(Decimal::from_str("7.1230000000000000000") + .unwrap_err() + .to_string() + .ends_with("Cannot parse more than 18 fractional digits")); } #[test] fn decimal_from_str_errors_for_invalid_number_of_dots() { - match Decimal::from_str("1.2.3").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Unexpected number of dots"), - e => panic!("Unexpected error: {e:?}"), - } - - match Decimal::from_str("1.2.3.4").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Unexpected number of dots"), - e => panic!("Unexpected error: {e:?}"), - } + assert!(Decimal::from_str("1.2.3").is_err()); + assert!(Decimal::from_str("1.2.3.4").is_err()); } #[test] fn decimal_from_str_errors_for_more_than_max_value() { // Integer - match Decimal::from_str("340282366920938463464").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } + assert!(Decimal::from_str("340282366920938463464").is_err()); // Decimal - match Decimal::from_str("340282366920938463464.0").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } - match Decimal::from_str("340282366920938463463.374607431768211456").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } + assert!(Decimal::from_str("340282366920938463464.0").is_err()); + assert!(Decimal::from_str("340282366920938463463.374607431768211456").is_err()); } #[test] diff --git a/packages/std/src/math/decimal256.rs b/packages/std/src/math/decimal256.rs index ee1e529b28..6d0fa7cb67 100644 --- a/packages/std/src/math/decimal256.rs +++ b/packages/std/src/math/decimal256.rs @@ -6,7 +6,7 @@ use core::str::FromStr; use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::errors::{ - CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, + CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, ErrorKind, OverflowError, OverflowOperation, RoundUpOverflowError, StdError, }; use crate::forward_ref::{forward_ref_binop, forward_ref_op_assign}; @@ -575,20 +575,14 @@ impl FromStr for Decimal256 { let mut parts_iter = input.split('.'); let whole_part = parts_iter.next().unwrap(); // split always returns at least one element - let whole = whole_part - .parse::() - .map_err(|_| StdError::generic_err("Error parsing whole"))?; - let mut atomics = whole - .checked_mul(Self::DECIMAL_FRACTIONAL) - .map_err(|_| StdError::generic_err("Value too big"))?; + let whole = whole_part.parse::()?; + let mut atomics = whole.checked_mul(Self::DECIMAL_FRACTIONAL)?; if let Some(fractional_part) = parts_iter.next() { - let fractional = fractional_part - .parse::() - .map_err(|_| StdError::generic_err("Error parsing fractional"))?; + let fractional = fractional_part.parse::()?; let exp = (Self::DECIMAL_PLACES.checked_sub(fractional_part.len() as u32)).ok_or_else( || { - StdError::generic_err(format!( + StdError::msg(format_args!( "Cannot parse more than {} fractional digits", Self::DECIMAL_PLACES )) @@ -596,17 +590,15 @@ impl FromStr for Decimal256 { )?; debug_assert!(exp <= Self::DECIMAL_PLACES); let fractional_factor = Uint256::from(10u128).pow(exp); - atomics = atomics - .checked_add( - // The inner multiplication can't overflow because - // fractional < 10^DECIMAL_PLACES && fractional_factor <= 10^DECIMAL_PLACES - fractional.checked_mul(fractional_factor).unwrap(), - ) - .map_err(|_| StdError::generic_err("Value too big"))?; + atomics = atomics.checked_add( + // The inner multiplication can't overflow because + // fractional < 10^DECIMAL_PLACES && fractional_factor <= 10^DECIMAL_PLACES + fractional.checked_mul(fractional_factor).unwrap(), + )?; } if parts_iter.next().is_some() { - return Err(StdError::generic_err("Unexpected number of dots")); + return Err(StdError::msg("Unexpected number of dots").with_kind(ErrorKind::Parsing)); } Ok(Self(atomics)) @@ -809,7 +801,6 @@ impl de::Visitor<'_> for Decimal256Visitor { #[cfg(test)] mod tests { use super::*; - use crate::errors::StdError; use alloc::vec::Vec; @@ -1143,101 +1134,56 @@ mod tests { #[test] fn decimal256_from_str_errors_for_broken_whole_part() { - match Decimal256::from_str("").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), - e => panic!("Unexpected error: {e:?}"), - } - - match Decimal256::from_str(" ").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), - e => panic!("Unexpected error: {e:?}"), - } - - match Decimal256::from_str("-1").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing whole"), - e => panic!("Unexpected error: {e:?}"), - } + assert!(Decimal256::from_str("").is_err()); + assert!(Decimal256::from_str(" ").is_err()); + assert!(Decimal256::from_str("-1").is_err()); } #[test] fn decimal256_from_str_errors_for_broken_fractional_part() { - match Decimal256::from_str("1.").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match Decimal256::from_str("1. ").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match Decimal256::from_str("1.e").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } - - match Decimal256::from_str("1.2e3").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Error parsing fractional"), - e => panic!("Unexpected error: {e:?}"), - } + assert!(Decimal256::from_str("1.").is_err()); + assert!(Decimal256::from_str("1. ").is_err()); + assert!(Decimal256::from_str("1.e").is_err()); + assert!(Decimal256::from_str("1.2e3").is_err()); } #[test] fn decimal256_from_str_errors_for_more_than_36_fractional_digits() { - match Decimal256::from_str("7.1234567890123456789").unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot parse more than 18 fractional digits") - } - e => panic!("Unexpected error: {e:?}"), - } + assert!(Decimal256::from_str("7.1234567890123456789") + .unwrap_err() + .to_string() + .ends_with("Cannot parse more than 18 fractional digits")); // No special rules for trailing zeros. This could be changed but adds gas cost for the happy path. - match Decimal256::from_str("7.1230000000000000000").unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "Cannot parse more than 18 fractional digits") - } - e => panic!("Unexpected error: {e:?}"), - } + assert!(Decimal256::from_str("7.1230000000000000000") + .unwrap_err() + .to_string() + .ends_with("Cannot parse more than 18 fractional digits")); } #[test] fn decimal256_from_str_errors_for_invalid_number_of_dots() { - match Decimal256::from_str("1.2.3").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Unexpected number of dots"), - e => panic!("Unexpected error: {e:?}"), - } - - match Decimal256::from_str("1.2.3.4").unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Unexpected number of dots"), - e => panic!("Unexpected error: {e:?}"), - } + assert!(Decimal256::from_str("1.2.3").is_err()); + assert!(Decimal256::from_str("1.2.3.4").is_err()); } #[test] fn decimal256_from_str_errors_for_more_than_max_value() { // Integer - match Decimal256::from_str("115792089237316195423570985008687907853269984665640564039458") - .unwrap_err() - { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } + assert!(Decimal256::from_str( + "115792089237316195423570985008687907853269984665640564039458" + ) + .is_err()); // Decimal - match Decimal256::from_str("115792089237316195423570985008687907853269984665640564039458.0") - .unwrap_err() - { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } - match Decimal256::from_str( + assert!(Decimal256::from_str( + "115792089237316195423570985008687907853269984665640564039458.0" + ) + .is_err()); + assert!(Decimal256::from_str( "115792089237316195423570985008687907853269984665640564039457.584007913129639936", ) - .unwrap_err() - { - StdError::GenericErr { msg, .. } => assert_eq!(msg, "Value too big"), - e => panic!("Unexpected error: {e:?}"), - } + .is_err()); } #[test] diff --git a/packages/std/src/math/int128.rs b/packages/std/src/math/int128.rs index 2fba6ef959..be84333aa4 100644 --- a/packages/std/src/math/int128.rs +++ b/packages/std/src/math/int128.rs @@ -6,7 +6,9 @@ use core::ops::{ }; use core::str::FromStr; -use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError}; +use crate::errors::{ + DivideByZeroError, DivisionError, ErrorKind, OverflowError, OverflowOperation, StdError, +}; use crate::forward_ref::{forward_ref_binop, forward_ref_op_assign}; use crate::{ CheckedMultiplyRatioError, Int256, Int512, Int64, Uint128, Uint256, Uint512, Uint64, @@ -341,7 +343,10 @@ impl FromStr for Int128 { fn from_str(s: &str) -> Result { match s.parse::() { Ok(u) => Ok(Self(u)), - Err(e) => Err(StdError::generic_err(format!("Parsing Int128: {e}"))), + Err(e) => { + Err(StdError::msg(format_args!("Parsing Int128: {e}")) + .with_kind(ErrorKind::Parsing)) + } } } } diff --git a/packages/std/src/math/int256.rs b/packages/std/src/math/int256.rs index 18dcf60846..3e850ea298 100644 --- a/packages/std/src/math/int256.rs +++ b/packages/std/src/math/int256.rs @@ -6,7 +6,9 @@ use core::ops::{ }; use core::str::FromStr; -use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError}; +use crate::errors::{ + DivideByZeroError, DivisionError, ErrorKind, OverflowError, OverflowOperation, StdError, +}; use crate::forward_ref::{forward_ref_binop, forward_ref_op_assign}; use crate::{ CheckedMultiplyRatioError, Int128, Int512, Int64, Uint128, Uint256, Uint512, Uint64, @@ -436,7 +438,10 @@ impl FromStr for Int256 { fn from_str(s: &str) -> Result { match I256::from_str_radix(s, 10) { Ok(u) => Ok(Self(u)), - Err(e) => Err(StdError::generic_err(format!("Parsing Int256: {e}"))), + Err(e) => { + Err(StdError::msg(format_args!("Parsing Int256: {e}")) + .with_kind(ErrorKind::Parsing)) + } } } } diff --git a/packages/std/src/math/int512.rs b/packages/std/src/math/int512.rs index 67f7ad1f93..d96de8e6da 100644 --- a/packages/std/src/math/int512.rs +++ b/packages/std/src/math/int512.rs @@ -6,7 +6,9 @@ use core::ops::{ }; use core::str::FromStr; -use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError}; +use crate::errors::{ + DivideByZeroError, DivisionError, ErrorKind, OverflowError, OverflowOperation, StdError, +}; use crate::forward_ref::{forward_ref_binop, forward_ref_op_assign}; use crate::{ Int128, Int256, Int64, Uint128, Uint256, Uint512, Uint64, __internal::forward_ref_partial_eq, @@ -432,7 +434,10 @@ impl FromStr for Int512 { fn from_str(s: &str) -> Result { match I512::from_str_radix(s, 10) { Ok(u) => Ok(Self(u)), - Err(e) => Err(StdError::generic_err(format!("Parsing Int512: {e}"))), + Err(e) => { + Err(StdError::msg(format_args!("Parsing Int512: {e}")) + .with_kind(ErrorKind::Parsing)) + } } } } diff --git a/packages/std/src/math/int64.rs b/packages/std/src/math/int64.rs index 3fb115c454..b1cc0b68fb 100644 --- a/packages/std/src/math/int64.rs +++ b/packages/std/src/math/int64.rs @@ -6,7 +6,9 @@ use core::ops::{ }; use core::str::FromStr; -use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError}; +use crate::errors::{ + DivideByZeroError, DivisionError, ErrorKind, OverflowError, OverflowOperation, StdError, +}; use crate::forward_ref::{forward_ref_binop, forward_ref_op_assign}; use crate::{ CheckedMultiplyRatioError, Int128, Int256, Int512, Uint128, Uint256, Uint512, Uint64, @@ -331,7 +333,9 @@ impl FromStr for Int64 { fn from_str(s: &str) -> Result { match s.parse::() { Ok(u) => Ok(Self(u)), - Err(e) => Err(StdError::generic_err(format!("Parsing Int64: {e}"))), + Err(e) => { + Err(StdError::msg(format_args!("Parsing Int64: {e}")).with_kind(ErrorKind::Parsing)) + } } } } diff --git a/packages/std/src/math/signed_decimal.rs b/packages/std/src/math/signed_decimal.rs index f5a26959c0..329173fe11 100644 --- a/packages/std/src/math/signed_decimal.rs +++ b/packages/std/src/math/signed_decimal.rs @@ -8,7 +8,7 @@ use core::str::FromStr; use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::errors::{ - CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, + CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, ErrorKind, OverflowError, OverflowOperation, RoundDownOverflowError, RoundUpOverflowError, StdError, }; use crate::forward_ref::{forward_ref_binop, forward_ref_op_assign}; @@ -681,20 +681,14 @@ impl FromStr for SignedDecimal { let whole_part = parts_iter.next().unwrap(); // split always returns at least one element let is_neg = whole_part.starts_with('-'); - let whole = whole_part - .parse::() - .map_err(|_| StdError::generic_err("Error parsing whole"))?; - let mut atomics = whole - .checked_mul(Self::DECIMAL_FRACTIONAL) - .map_err(|_| StdError::generic_err("Value too big"))?; + let whole = whole_part.parse::()?; + let mut atomics = whole.checked_mul(Self::DECIMAL_FRACTIONAL)?; if let Some(fractional_part) = parts_iter.next() { - let fractional = fractional_part - .parse::() // u64 is enough for 18 decimal places - .map_err(|_| StdError::generic_err("Error parsing fractional"))?; + let fractional = fractional_part.parse::()?; // u64 is enough for 18 decimal places let exp = (Self::DECIMAL_PLACES.checked_sub(fractional_part.len() as u32)).ok_or_else( || { - StdError::generic_err(format!( + StdError::msg(format_args!( "Cannot parse more than {} fractional digits", Self::DECIMAL_PLACES )) @@ -714,12 +708,11 @@ impl FromStr for SignedDecimal { atomics.checked_sub(fractional_part) } else { atomics.checked_add(fractional_part) - } - .map_err(|_| StdError::generic_err("Value too big"))?; + }?; } if parts_iter.next().is_some() { - return Err(StdError::generic_err("Unexpected number of dots")); + return Err(StdError::msg("Unexpected number of dots").with_kind(ErrorKind::Parsing)); } Ok(SignedDecimal(atomics)) @@ -1379,72 +1372,50 @@ mod tests { #[test] fn signed_decimal_from_str_errors_for_broken_whole_part() { - let expected_err = StdError::generic_err("Error parsing whole"); - assert_eq!(SignedDecimal::from_str("").unwrap_err(), expected_err); - assert_eq!(SignedDecimal::from_str(" ").unwrap_err(), expected_err); - assert_eq!(SignedDecimal::from_str("-").unwrap_err(), expected_err); + assert!(SignedDecimal::from_str("").is_err()); + assert!(SignedDecimal::from_str(" ").is_err()); + assert!(SignedDecimal::from_str("-").is_err()); } #[test] fn signed_decimal_from_str_errors_for_broken_fractional_part() { - let expected_err = StdError::generic_err("Error parsing fractional"); - assert_eq!(SignedDecimal::from_str("1.").unwrap_err(), expected_err); - assert_eq!(SignedDecimal::from_str("1. ").unwrap_err(), expected_err); - assert_eq!(SignedDecimal::from_str("1.e").unwrap_err(), expected_err); - assert_eq!(SignedDecimal::from_str("1.2e3").unwrap_err(), expected_err); - assert_eq!(SignedDecimal::from_str("1.-2").unwrap_err(), expected_err); + assert!(SignedDecimal::from_str("1.").is_err()); + assert!(SignedDecimal::from_str("1. ").is_err()); + assert!(SignedDecimal::from_str("1.e").is_err()); + assert!(SignedDecimal::from_str("1.2e3").is_err()); + assert!(SignedDecimal::from_str("1.-2").is_err()); } #[test] fn signed_decimal_from_str_errors_for_more_than_18_fractional_digits() { - let expected_err = StdError::generic_err("Cannot parse more than 18 fractional digits"); - assert_eq!( - SignedDecimal::from_str("7.1234567890123456789").unwrap_err(), - expected_err - ); + assert!(SignedDecimal::from_str("7.1234567890123456789").is_err()); // No special rules for trailing zeros. This could be changed but adds gas cost for the happy path. - assert_eq!( - SignedDecimal::from_str("7.1230000000000000000").unwrap_err(), - expected_err - ); + assert!(SignedDecimal::from_str("7.1230000000000000000").is_err()); } #[test] fn signed_decimal_from_str_errors_for_invalid_number_of_dots() { - let expected_err = StdError::generic_err("Unexpected number of dots"); - assert_eq!(SignedDecimal::from_str("1.2.3").unwrap_err(), expected_err); - assert_eq!( - SignedDecimal::from_str("1.2.3.4").unwrap_err(), - expected_err - ); + assert!(SignedDecimal::from_str("1.2.3") + .unwrap_err() + .to_string() + .ends_with("Unexpected number of dots")); + + assert!(SignedDecimal::from_str("1.2.3.4") + .unwrap_err() + .to_string() + .ends_with("Unexpected number of dots")); } #[test] fn signed_decimal_from_str_errors_for_more_than_max_value() { - let expected_err = StdError::generic_err("Value too big"); // Integer - assert_eq!( - SignedDecimal::from_str("170141183460469231732").unwrap_err(), - expected_err - ); - assert_eq!( - SignedDecimal::from_str("-170141183460469231732").unwrap_err(), - expected_err - ); + assert!(SignedDecimal::from_str("170141183460469231732").is_err()); + assert!(SignedDecimal::from_str("-170141183460469231732").is_err()); // SignedDecimal - assert_eq!( - SignedDecimal::from_str("170141183460469231732.0").unwrap_err(), - expected_err - ); - assert_eq!( - SignedDecimal::from_str("170141183460469231731.687303715884105728").unwrap_err(), - expected_err - ); - assert_eq!( - SignedDecimal::from_str("-170141183460469231731.687303715884105729").unwrap_err(), - expected_err - ); + assert!(SignedDecimal::from_str("170141183460469231732.0").is_err()); + assert!(SignedDecimal::from_str("170141183460469231731.687303715884105728").is_err()); + assert!(SignedDecimal::from_str("-170141183460469231731.687303715884105729").is_err()); } #[test] @@ -3110,7 +3081,7 @@ mod tests { // invalid: not properly defined signed decimal value assert_eq!( - "Error parsing decimal '1.e': Generic error: Error parsing fractional at line 1 column 5", + "Error parsing decimal '1.e': kind: Other, error: invalid digit found in string at line 1 column 5", serde_json::from_str::(r#""1.e""#) .err() .unwrap() diff --git a/packages/std/src/math/signed_decimal_256.rs b/packages/std/src/math/signed_decimal_256.rs index d752170c3f..e339662e56 100644 --- a/packages/std/src/math/signed_decimal_256.rs +++ b/packages/std/src/math/signed_decimal_256.rs @@ -8,7 +8,7 @@ use core::str::FromStr; use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::errors::{ - CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, + CheckedFromRatioError, CheckedMultiplyRatioError, DivideByZeroError, ErrorKind, OverflowError, OverflowOperation, RoundDownOverflowError, RoundUpOverflowError, StdError, }; use crate::forward_ref::{forward_ref_binop, forward_ref_op_assign}; @@ -682,20 +682,14 @@ impl FromStr for SignedDecimal256 { let whole_part = parts_iter.next().unwrap(); // split always returns at least one element let is_neg = whole_part.starts_with('-'); - let whole = whole_part - .parse::() - .map_err(|_| StdError::generic_err("Error parsing whole"))?; - let mut atomics = whole - .checked_mul(Self::DECIMAL_FRACTIONAL) - .map_err(|_| StdError::generic_err("Value too big"))?; + let whole = whole_part.parse::()?; + let mut atomics = whole.checked_mul(Self::DECIMAL_FRACTIONAL)?; if let Some(fractional_part) = parts_iter.next() { - let fractional = fractional_part - .parse::() // u64 is enough for 18 decimal places - .map_err(|_| StdError::generic_err("Error parsing fractional"))?; + let fractional = fractional_part.parse::()?; // u64 is enough for 18 decimal places let exp = (Self::DECIMAL_PLACES.checked_sub(fractional_part.len() as u32)).ok_or_else( || { - StdError::generic_err(format!( + StdError::msg(format_args!( "Cannot parse more than {} fractional digits", Self::DECIMAL_PLACES )) @@ -715,12 +709,11 @@ impl FromStr for SignedDecimal256 { atomics.checked_sub(fractional_part) } else { atomics.checked_add(fractional_part) - } - .map_err(|_| StdError::generic_err("Value too big"))?; + }?; } if parts_iter.next().is_some() { - return Err(StdError::generic_err("Unexpected number of dots")); + return Err(StdError::msg("Unexpected number of dots").with_kind(ErrorKind::Parsing)); } Ok(SignedDecimal256(atomics)) @@ -1384,96 +1377,65 @@ mod tests { #[test] fn signed_decimal_256_from_str_errors_for_broken_whole_part() { - let expected_err = StdError::generic_err("Error parsing whole"); - assert_eq!(SignedDecimal256::from_str("").unwrap_err(), expected_err); - assert_eq!(SignedDecimal256::from_str(" ").unwrap_err(), expected_err); - assert_eq!(SignedDecimal256::from_str("-").unwrap_err(), expected_err); + assert!(SignedDecimal256::from_str("").is_err()); + assert!(SignedDecimal256::from_str(" ").is_err()); + assert!(SignedDecimal256::from_str("-").is_err()); } #[test] fn signed_decimal_256_from_str_errors_for_broken_fractional_part() { - let expected_err = StdError::generic_err("Error parsing fractional"); - assert_eq!(SignedDecimal256::from_str("1.").unwrap_err(), expected_err); - assert_eq!(SignedDecimal256::from_str("1. ").unwrap_err(), expected_err); - assert_eq!(SignedDecimal256::from_str("1.e").unwrap_err(), expected_err); - assert_eq!( - SignedDecimal256::from_str("1.2e3").unwrap_err(), - expected_err - ); - assert_eq!( - SignedDecimal256::from_str("1.-2").unwrap_err(), - expected_err - ); + assert!(SignedDecimal256::from_str("1.").is_err()); + assert!(SignedDecimal256::from_str("1. ").is_err()); + assert!(SignedDecimal256::from_str("1.e").is_err()); + assert!(SignedDecimal256::from_str("1.2e3").is_err()); + assert!(SignedDecimal256::from_str("1.-2").is_err()); } #[test] fn signed_decimal_256_from_str_errors_for_more_than_18_fractional_digits() { - let expected_err = StdError::generic_err("Cannot parse more than 18 fractional digits"); - assert_eq!( - SignedDecimal256::from_str("7.1234567890123456789").unwrap_err(), - expected_err - ); + assert!(SignedDecimal256::from_str("7.1234567890123456789").is_err()); // No special rules for trailing zeros. This could be changed but adds gas cost for the happy path. - assert_eq!( - SignedDecimal256::from_str("7.1230000000000000000").unwrap_err(), - expected_err - ); + assert!(SignedDecimal256::from_str("7.1230000000000000000").is_err()); } #[test] fn signed_decimal_256_from_str_errors_for_invalid_number_of_dots() { - let expected_err = StdError::generic_err("Unexpected number of dots"); - assert_eq!( - SignedDecimal256::from_str("1.2.3").unwrap_err(), - expected_err - ); - assert_eq!( - SignedDecimal256::from_str("1.2.3.4").unwrap_err(), - expected_err - ); + assert!(SignedDecimal256::from_str("1.2.3") + .unwrap_err() + .to_string() + .ends_with("Unexpected number of dots")); + + assert!(SignedDecimal256::from_str("1.2.3.4") + .unwrap_err() + .to_string() + .ends_with("Unexpected number of dots")); } #[test] fn signed_decimal_256_from_str_errors_for_more_than_max_value() { - let expected_err = StdError::generic_err("Value too big"); // Integer - assert_eq!( - SignedDecimal256::from_str( - "57896044618658097711785492504343953926634992332820282019729", - ) - .unwrap_err(), - expected_err - ); - assert_eq!( - SignedDecimal256::from_str( - "-57896044618658097711785492504343953926634992332820282019729", - ) - .unwrap_err(), - expected_err - ); + assert!(SignedDecimal256::from_str( + "57896044618658097711785492504343953926634992332820282019729", + ) + .is_err()); + assert!(SignedDecimal256::from_str( + "-57896044618658097711785492504343953926634992332820282019729", + ) + .is_err()); // SignedDecimal256 - assert_eq!( - SignedDecimal256::from_str( - "57896044618658097711785492504343953926634992332820282019729.0", - ) - .unwrap_err(), - expected_err - ); - assert_eq!( - SignedDecimal256::from_str( - "57896044618658097711785492504343953926634992332820282019728.792003956564819968", - ) - .unwrap_err(), - expected_err - ); - assert_eq!( - SignedDecimal256::from_str( - "-57896044618658097711785492504343953926634992332820282019728.792003956564819969", - ) - .unwrap_err(), - expected_err - ); + assert!(SignedDecimal256::from_str( + "57896044618658097711785492504343953926634992332820282019729.0", + ) + .is_err()); + assert!(SignedDecimal256::from_str( + "57896044618658097711785492504343953926634992332820282019728.792003956564819968", + ) + .is_err()); + assert!(SignedDecimal256::from_str( + "-57896044618658097711785492504343953926634992332820282019728.792003956564819969", + ) + .is_err()); } #[test] @@ -3281,7 +3243,7 @@ mod tests { // invalid: not properly defined signed decimal value assert_eq!( - "Error parsing decimal '1.e': Generic error: Error parsing fractional at line 1 column 5", + "Error parsing decimal '1.e': kind: Other, error: invalid digit found in string at line 1 column 5", serde_json::from_str::(r#""1.e""#) .err() .unwrap() diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index 3048e7b370..7a41dea424 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -7,8 +7,8 @@ use core::ops::{ use core::str::FromStr; use crate::errors::{ - CheckedMultiplyFractionError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, - OverflowOperation, StdError, + CheckedMultiplyFractionError, CheckedMultiplyRatioError, DivideByZeroError, ErrorKind, + OverflowError, OverflowOperation, StdError, }; use crate::forward_ref::{forward_ref_binop, forward_ref_op_assign}; use crate::{ @@ -365,7 +365,9 @@ impl FromStr for Uint128 { fn from_str(s: &str) -> Result { match s.parse::() { Ok(u) => Ok(Uint128(u)), - Err(e) => Err(StdError::generic_err(format!("Parsing u128: {e}"))), + Err(e) => { + Err(StdError::msg(format_args!("Parsing u128: {e}")).with_kind(ErrorKind::Parsing)) + } } } } diff --git a/packages/std/src/math/uint256.rs b/packages/std/src/math/uint256.rs index c3a5a9dc75..7329254164 100644 --- a/packages/std/src/math/uint256.rs +++ b/packages/std/src/math/uint256.rs @@ -8,7 +8,7 @@ use core::str::FromStr; use crate::errors::{ CheckedMultiplyFractionError, CheckedMultiplyRatioError, ConversionOverflowError, - DivideByZeroError, OverflowError, OverflowOperation, StdError, + DivideByZeroError, ErrorKind, OverflowError, OverflowOperation, StdError, }; use crate::forward_ref::{forward_ref_binop, forward_ref_op_assign}; use crate::{ @@ -444,12 +444,16 @@ impl FromStr for Uint256 { fn from_str(s: &str) -> Result { if s.is_empty() { - return Err(StdError::generic_err("Parsing u256: received empty string")); + return Err( + StdError::msg("Parsing u256: received empty string").with_kind(ErrorKind::Parsing) + ); } match U256::from_str_radix(s, 10) { Ok(u) => Ok(Uint256(u)), - Err(e) => Err(StdError::generic_err(format!("Parsing u256: {e}"))), + Err(e) => { + Err(StdError::msg(format_args!("Parsing u256: {e}")).with_kind(ErrorKind::Parsing)) + } } } } diff --git a/packages/std/src/math/uint512.rs b/packages/std/src/math/uint512.rs index c633e108b2..811f8cf5e5 100644 --- a/packages/std/src/math/uint512.rs +++ b/packages/std/src/math/uint512.rs @@ -7,7 +7,8 @@ use core::ops::{ use core::str::FromStr; use crate::errors::{ - ConversionOverflowError, DivideByZeroError, OverflowError, OverflowOperation, StdError, + ConversionOverflowError, DivideByZeroError, ErrorKind, OverflowError, OverflowOperation, + StdError, }; use crate::forward_ref::{forward_ref_binop, forward_ref_op_assign}; use crate::{ @@ -440,7 +441,9 @@ impl FromStr for Uint512 { fn from_str(s: &str) -> Result { match U512::from_str_radix(s, 10) { Ok(u) => Ok(Self(u)), - Err(e) => Err(StdError::generic_err(format!("Parsing u512: {e}"))), + Err(e) => { + Err(StdError::msg(format_args!("Parsing u512: {e}")).with_kind(ErrorKind::Parsing)) + } } } } diff --git a/packages/std/src/math/uint64.rs b/packages/std/src/math/uint64.rs index 43ca13b5a2..5d2ab9e810 100644 --- a/packages/std/src/math/uint64.rs +++ b/packages/std/src/math/uint64.rs @@ -6,8 +6,8 @@ use core::ops::{ }; use crate::errors::{ - CheckedMultiplyFractionError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, - OverflowOperation, StdError, + CheckedMultiplyFractionError, CheckedMultiplyRatioError, DivideByZeroError, ErrorKind, + OverflowError, OverflowOperation, StdError, }; use crate::forward_ref::{forward_ref_binop, forward_ref_op_assign}; use crate::{ @@ -345,7 +345,9 @@ impl TryFrom<&str> for Uint64 { fn try_from(val: &str) -> Result { match val.parse::() { Ok(u) => Ok(Uint64(u)), - Err(e) => Err(StdError::generic_err(format!("Parsing u64: {e}"))), + Err(e) => { + Err(StdError::msg(format_args!("Parsing u64: {e}")).with_kind(ErrorKind::Parsing)) + } } } } diff --git a/packages/std/src/msgpack.rs b/packages/std/src/msgpack.rs index 33f4577fdb..e14513ada5 100644 --- a/packages/std/src/msgpack.rs +++ b/packages/std/src/msgpack.rs @@ -3,11 +3,9 @@ // 1. To easily ensure that all calling libraries use the same version (minimize code size) // 2. To allow us to switch out to another MessagePack library if needed -use core::any::type_name; use serde::{de::DeserializeOwned, Serialize}; -use crate::Binary; -use crate::{StdError, StdResult}; +use crate::{Binary, StdResult}; /// Deserializes the given MessagePack bytes to a data structure. /// @@ -33,7 +31,7 @@ use crate::{StdError, StdResult}; /// let decoded: MyPacket = from_msgpack(&encoded).unwrap(); /// assert_eq!(decoded, packet); pub fn from_msgpack(value: impl AsRef<[u8]>) -> StdResult { - rmp_serde::from_read(value.as_ref()).map_err(|e| StdError::parse_err(type_name::(), e)) + Ok(rmp_serde::from_read(value.as_ref())?) } /// Serializes the given data structure as a MessagePack byte vector. @@ -61,7 +59,7 @@ pub fn to_msgpack_vec(data: &T) -> StdResult> where T: Serialize + ?Sized, { - rmp_serde::to_vec_named(data).map_err(|e| StdError::serialize_err(type_name::(), e)) + Ok(rmp_serde::to_vec_named(data)?) } /// Serializes the given data structure as MessagePack bytes. diff --git a/packages/std/src/results/contract_result.rs b/packages/std/src/results/contract_result.rs index 58c9f6fee4..6b181c6526 100644 --- a/packages/std/src/results/contract_result.rs +++ b/packages/std/src/results/contract_result.rs @@ -92,7 +92,7 @@ impl From> for Result { #[cfg(test)] mod tests { use super::*; - use crate::{from_json, to_json_vec, Response, StdError, StdResult}; + use crate::{errors::ErrorKind, from_json, to_json_vec, Response, StdError, StdResult}; #[test] fn contract_result_serialization_works() { @@ -134,19 +134,19 @@ mod tests { // fails for additional attributes let parse: StdResult> = from_json(br#"{"unrelated":321,"ok":4554}"#); - match parse.unwrap_err() { - StdError::ParseErr { .. } => {} + match parse.unwrap_err().kind() { + ErrorKind::Serialization => {} err => panic!("Unexpected error: {err:?}"), } let parse: StdResult> = from_json(br#"{"ok":4554,"unrelated":321}"#); - match parse.unwrap_err() { - StdError::ParseErr { .. } => {} + match parse.unwrap_err().kind() { + ErrorKind::Serialization => {} err => panic!("Unexpected error: {err:?}"), } let parse: StdResult> = from_json(br#"{"ok":4554,"error":"What's up now?"}"#); - match parse.unwrap_err() { - StdError::ParseErr { .. } => {} + match parse.unwrap_err().kind() { + ErrorKind::Serialization => {} err => panic!("Unexpected error: {err:?}"), } } @@ -157,11 +157,11 @@ mod tests { let converted: ContractResult = original.into(); assert_eq!(converted, ContractResult::Ok(Response::default())); - let original: Result = Err(StdError::generic_err("broken")); + let original: Result = Err(StdError::msg("broken")); let converted: ContractResult = original.into(); assert_eq!( converted, - ContractResult::Err("Generic error: broken".to_string()) + ContractResult::Err("kind: Other, error: broken".to_string()) ); } diff --git a/packages/std/src/results/submessages.rs b/packages/std/src/results/submessages.rs index b97835b115..ee0a990bd8 100644 --- a/packages/std/src/results/submessages.rs +++ b/packages/std/src/results/submessages.rs @@ -326,7 +326,9 @@ pub struct MsgResponse { #[allow(deprecated)] mod tests { use super::*; - use crate::{coins, from_json, to_json_vec, Attribute, BankMsg, StdError, StdResult}; + use crate::{ + coins, errors::ErrorKind, from_json, to_json_vec, Attribute, BankMsg, StdError, StdResult, + }; #[test] fn sub_msg_new_works() { @@ -467,13 +469,13 @@ mod tests { // fails for additional attributes let parse: StdResult = from_json(br#"{"unrelated":321,"error":"broken"}"#); - match parse.unwrap_err() { - StdError::ParseErr { .. } => {} + match parse.unwrap_err().kind() { + ErrorKind::Serialization => {} err => panic!("Unexpected error: {err:?}"), } let parse: StdResult = from_json(br#"{"error":"broken","unrelated":321}"#); - match parse.unwrap_err() { - StdError::ParseErr { .. } => {} + match parse.unwrap_err().kind() { + ErrorKind::Serialization => {} err => panic!("Unexpected error: {err:?}"), } } @@ -558,11 +560,11 @@ mod tests { }) ); - let original: Result = Err(StdError::generic_err("broken")); + let original: Result = Err(StdError::msg("broken")); let converted: SubMsgResult = original.into(); assert_eq!( converted, - SubMsgResult::Err("Generic error: broken".to_string()) + SubMsgResult::Err("kind: Other, error: broken".to_string()) ); } diff --git a/packages/std/src/serde.rs b/packages/std/src/serde.rs index 93cbe17e75..0aae68dece 100644 --- a/packages/std/src/serde.rs +++ b/packages/std/src/serde.rs @@ -3,17 +3,15 @@ // 1. To easily ensure that all calling libraries use the same version (minimize code size) // 2. To allow us to switch out to eg. serde-json-core more easily -use core::any::type_name; use serde::{de::DeserializeOwned, Serialize}; -use crate::Binary; -use crate::{StdError, StdResult}; +use crate::{Binary, StdResult}; /// Deserializes the given JSON bytes to a data structure. /// /// Errors if the input is not valid JSON or cannot be deserialized to the given type. pub fn from_json(value: impl AsRef<[u8]>) -> StdResult { - serde_json::from_slice(value.as_ref()).map_err(|e| StdError::parse_err(type_name::(), e)) + Ok(serde_json::from_slice(value.as_ref())?) } /// Serializes the given data structure as a JSON byte vector. @@ -21,7 +19,7 @@ pub fn to_json_vec(data: &T) -> StdResult> where T: Serialize + ?Sized, { - serde_json::to_vec(data).map_err(|e| StdError::serialize_err(type_name::(), e)) + Ok(serde_json::to_vec(data)?) } /// Serializes the given data structure as a JSON string. @@ -29,7 +27,7 @@ pub fn to_json_string(data: &T) -> StdResult where T: Serialize + ?Sized, { - serde_json::to_string(data).map_err(|e| StdError::serialize_err(type_name::(), e)) + Ok(serde_json::to_string(data)?) } /// Serializes the given data structure as JSON bytes. diff --git a/packages/std/src/testing/mock.rs b/packages/std/src/testing/mock.rs index e6da04241f..55aa0a2c26 100644 --- a/packages/std/src/testing/mock.rs +++ b/packages/std/src/testing/mock.rs @@ -118,23 +118,20 @@ impl Api for MockApi { let canonical = self.addr_canonicalize(input)?; let normalized = self.addr_humanize(&canonical)?; if input != normalized.as_str() { - return Err(StdError::generic_err( - "Invalid input: address not normalized", - )); + return Err(StdError::msg("Invalid input: address not normalized")); } Ok(Addr::unchecked(input)) } fn addr_canonicalize(&self, input: &str) -> StdResult { - let hrp_str = CheckedHrpstring::new::(input) - .map_err(|_| StdError::generic_err("Error decoding bech32"))?; + let hrp_str = CheckedHrpstring::new::(input)?; if !hrp_str .hrp() .as_bytes() .eq_ignore_ascii_case(self.bech32_prefix.as_bytes()) { - return Err(StdError::generic_err("Wrong bech32 prefix")); + return Err(StdError::msg("Wrong bech32 prefix")); } let bytes: Vec = hrp_str.byte_iter().collect(); @@ -145,11 +142,8 @@ impl Api for MockApi { fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult { validate_length(canonical.as_ref())?; - let prefix = Hrp::parse(self.bech32_prefix) - .map_err(|_| StdError::generic_err("Invalid bech32 prefix"))?; - encode::(prefix, canonical.as_slice()) - .map(Addr::unchecked) - .map_err(|_| StdError::generic_err("Bech32 encoding error")) + let prefix = Hrp::parse(self.bech32_prefix)?; + Ok(encode::(prefix, canonical.as_slice()).map(Addr::unchecked)?) } fn bls12_381_aggregate_g1(&self, g1s: &[u8]) -> Result<[u8; 48], VerificationError> { @@ -333,7 +327,7 @@ impl MockApi { fn validate_length(bytes: &[u8]) -> StdResult<()> { match bytes.len() { 1..=255 => Ok(()), - _ => Err(StdError::generic_err("Invalid canonical address length")), + _ => Err(StdError::msg("Invalid canonical address length")), } } @@ -1652,10 +1646,11 @@ mod tests { fn addr_humanize_input_length() { let api = MockApi::default(); let input = CanonicalAddr::from(vec![]); - assert_eq!( - api.addr_humanize(&input).unwrap_err(), - StdError::generic_err("Invalid canonical address length") - ); + assert!(api + .addr_humanize(&input) + .unwrap_err() + .to_string() + .ends_with("Invalid canonical address length")); } #[test] @@ -2921,7 +2916,10 @@ mod tests { }); match result { SystemResult::Ok(ContractResult::Err(err)) => { - assert_eq!(err, "Error parsing into type cosmwasm_std::testing::mock::tests::wasm_querier_works::{{closure}}::MyMsg: expected value at line 1 column 1") + assert_eq!( + err, + "kind: Serialization, error: expected value at line 1 column 1" + ) } res => panic!("Unexpected result: {res:?}"), } diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 514f5fe44d..ed6da0102e 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -394,15 +394,13 @@ impl<'a, C: CustomQuery> QuerierWrapper<'a, C> { /// Performs a query and returns the binary result without deserializing it, /// wrapping any errors that may occur into `StdError`. fn query_raw(&self, request: &QueryRequest) -> StdResult { - let raw = to_json_vec(request).map_err(|serialize_err| { - StdError::generic_err(format!("Serializing QueryRequest: {serialize_err}")) - })?; + let raw = to_json_vec(request)?; match self.raw_query(&raw) { - SystemResult::Err(system_err) => Err(StdError::generic_err(format!( + SystemResult::Err(system_err) => Err(StdError::msg(format_args!( "Querier system error: {system_err}" ))), - SystemResult::Ok(ContractResult::Err(contract_err)) => Err(StdError::generic_err( - format!("Querier contract error: {contract_err}"), + SystemResult::Ok(ContractResult::Err(contract_err)) => Err(StdError::msg( + format_args!("Querier contract error: {contract_err}"), )), SystemResult::Ok(ContractResult::Ok(value)) => Ok(value), } @@ -554,15 +552,13 @@ impl<'a, C: CustomQuery> QuerierWrapper<'a, C> { .into(); // we cannot use query, as it will try to parse the binary data, when we just want to return it, // so a bit of code copy here... - let raw = to_json_vec(&request).map_err(|serialize_err| { - StdError::generic_err(format!("Serializing QueryRequest: {serialize_err}")) - })?; + let raw = to_json_vec(&request)?; match self.raw_query(&raw) { - SystemResult::Err(system_err) => Err(StdError::generic_err(format!( + SystemResult::Err(system_err) => Err(StdError::msg(format_args!( "Querier system error: {system_err}" ))), - SystemResult::Ok(ContractResult::Err(contract_err)) => Err(StdError::generic_err( - format!("Querier contract error: {contract_err}"), + SystemResult::Ok(ContractResult::Err(contract_err)) => Err(StdError::msg( + format_args!("Querier contract error: {contract_err}"), )), SystemResult::Ok(ContractResult::Ok(value)) => { if value.is_empty() { @@ -771,13 +767,9 @@ mod tests { let wrapper = QuerierWrapper::::new(&querier); let err = wrapper.query_wasm_contract_info("unknown").unwrap_err(); - assert!(matches!( - err, - StdError::GenericErr { - msg, - .. - } if msg == "Querier system error: No such contract: foobar" - )); + assert!(err + .to_string() + .ends_with("Querier system error: No such contract: foobar")); } #[test] diff --git a/packages/vm/examples/module_size.sh b/packages/vm/examples/module_size.sh index fbddec59ff..6e1a5e4b79 100755 --- a/packages/vm/examples/module_size.sh +++ b/packages/vm/examples/module_size.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Uses valgrind's massif tool to compute heap memory consumption of compiled modules. # For a wasmer `Module`, it has been determined that this method underestimates the size # of the module significantly.