Skip to content

Commit

Permalink
Merge pull request #1127 from CosmWasm/add-ensure
Browse files Browse the repository at this point in the history
Add ensure/ensure_eq/ensure_ne macros
  • Loading branch information
webmaster128 authored Oct 11, 2021
2 parents 29cd4e1 + 4c1a3c0 commit d5ef68d
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ and this project adheres to
`Decimal{,256}::decimal_places()`.
- cosmwasm-std: New constructors `Decimal{,256}::from_atomics`.
- cosmwasm-std: New `Uint128::checked_pow`.
- cosmwasm-std: New macros `ensure!`, `ensure_eq!` and `ensure_ne!` allow
requirement checking that return errors instead of panicking ([#1103]).

[#1103]: https://github.com/CosmWasm/cosmwasm/issues/1103

### Changed

Expand Down
209 changes: 209 additions & 0 deletions packages/std/src/assertions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
//! A module containing an assertion framework for CosmWasm contracts.
//! The methods in here never panic but return errors instead.
/// Quick check for a guard. If the condition (first argument) is false,
/// then return the second argument `x` wrapped in `Err(x)`.
///
/// ```
/// # enum ContractError {
/// # DelegatePerm {},
/// # }
/// #
/// # struct Permissions {
/// # delegate: bool,
/// # }
/// #
/// # fn body() -> Result<(), ContractError> {
/// # let permissions = Permissions { delegate: true };
/// use cosmwasm_std::ensure;
/// ensure!(permissions.delegate, ContractError::DelegatePerm {});
///
/// // is the same as
///
/// if !permissions.delegate {
/// return Err(ContractError::DelegatePerm {});
/// }
/// # Ok(())
/// # }
/// ```
#[macro_export]
macro_rules! ensure {
($cond:expr, $e:expr) => {
if !($cond) {
return Err(std::convert::From::from($e));
}
};
}

/// Quick check for a guard. Like `assert_eq!`, but rather than panic,
/// it returns the third argument `x` wrapped in `Err(x)`.
///
/// ```
/// # use cosmwasm_std::{MessageInfo, Addr};
/// #
/// # enum ContractError {
/// # Unauthorized {},
/// # }
/// # struct Config {
/// # admin: String,
/// # }
/// #
/// # fn body() -> Result<(), ContractError> {
/// # let info = MessageInfo { sender: Addr::unchecked("foo"), funds: Vec::new() };
/// # let cfg = Config { admin: "foo".to_string() };
/// use cosmwasm_std::ensure_eq;
///
/// ensure_eq!(info.sender, cfg.admin, ContractError::Unauthorized {});
///
/// // is the same as
///
/// if info.sender != cfg.admin {
/// return Err(ContractError::Unauthorized {});
/// }
/// # Ok(())
/// # }
/// ```
#[macro_export]
macro_rules! ensure_eq {
($a:expr, $b:expr, $e:expr) => {
// Not implemented via `ensure!` because the caller would have to import both macros.
if !($a == $b) {
return Err(std::convert::From::from($e));
}
};
}

/// Quick check for a guard. Like `assert_ne!`, but rather than panic,
/// it returns the third argument `x` wrapped in Err(x).
///
/// ```
/// # enum ContractError {
/// # NotAVoter {},
/// # }
/// #
/// # fn body() -> Result<(), ContractError> {
/// # let voting_power = 123;
/// use cosmwasm_std::ensure_ne;
///
/// ensure_ne!(voting_power, 0, ContractError::NotAVoter {});
///
/// // is the same as
///
/// if voting_power != 0 {
/// return Err(ContractError::NotAVoter {});
/// }
/// # Ok(())
/// # }
/// ```
#[macro_export]
macro_rules! ensure_ne {
($a:expr, $b:expr, $e:expr) => {
// Not implemented via `ensure!` because the caller would have to import both macros.
if !($a != $b) {
return Err(std::convert::From::from($e));
}
};
}

#[cfg(test)]
mod tests {
use crate::StdError;

#[test]
fn ensure_works() {
fn check(a: usize, b: usize) -> Result<(), StdError> {
ensure!(a == b, StdError::generic_err("foobar"));
Ok(())
}

let err = check(5, 6).unwrap_err();
assert!(matches!(err, StdError::GenericErr { .. }));

check(5, 5).unwrap();
}

#[test]
fn ensure_can_infer_error_type() {
let check = |a, b| {
ensure!(a == b, StdError::generic_err("foobar"));
Ok(())
};

let err = check(5, 6).unwrap_err();
assert!(matches!(err, StdError::GenericErr { .. }));

check(5, 5).unwrap();
}

#[test]
fn ensure_can_convert_into() {
#[derive(Debug)]
struct ContractError;

impl From<StdError> for ContractError {
fn from(_original: StdError) -> Self {
ContractError
}
}

fn check(a: usize, b: usize) -> Result<(), ContractError> {
ensure!(a == b, StdError::generic_err("foobar"));
Ok(())
}

let err = check(5, 6).unwrap_err();
assert!(matches!(err, ContractError));

check(5, 5).unwrap();
}

#[test]
fn ensure_eq_works() {
let check = |a, b| {
ensure_eq!(a, b, StdError::generic_err("foobar"));
Ok(())
};

let err = check("123", "456").unwrap_err();
assert!(matches!(err, StdError::GenericErr { .. }));
check("123", "123").unwrap();
}

#[test]
fn ensure_eq_gets_precedence_right() {
// If this was expanded to `true || false == false` we'd get equality.
// It must be expanded to `(true || false) == false` and we expect inequality.

fn check() -> Result<(), StdError> {
ensure_eq!(true || false, false, StdError::generic_err("foobar"));
Ok(())
}

let _err = check().unwrap_err();
}

#[test]
fn ensure_ne_works() {
let check = |a, b| {
ensure_ne!(a, b, StdError::generic_err("foobar"));
Ok(())
};

let err = check("123", "123").unwrap_err();
assert!(matches!(err, StdError::GenericErr { .. }));
check("123", "456").unwrap();
}

#[test]
fn ensure_ne_gets_precedence_right() {
// If this was expanded to `true || false == false` we'd get equality.
// It must be expanded to `(true || false) == false` and we expect inequality.

fn check() -> Result<(), StdError> {
ensure_ne!(true || false, false, StdError::generic_err("foobar"));
Ok(())
}

check().unwrap();
}
}
1 change: 1 addition & 0 deletions packages/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Exposed on all platforms

mod addresses;
mod assertions;
mod binary;
mod coins;
mod conversion;
Expand Down

0 comments on commit d5ef68d

Please sign in to comment.