From c48eb5c01d1fbd042b64358d965413fce0395261 Mon Sep 17 00:00:00 2001 From: CECILIA-MULANDI Date: Sat, 13 Dec 2025 00:57:52 +0300 Subject: [PATCH 1/5] Add ensure! macro to ink! and replace manual error checks in integration test contracts --- crates/ink/src/ensure.rs | 95 +++++++++++++++ crates/ink/src/lib.rs | 49 ++------ .../public/ensure-test/Cargo.toml | 31 +++++ integration-tests/public/ensure-test/lib.rs | 109 ++++++++++++++++++ integration-tests/public/erc1155/lib.rs | 12 +- integration-tests/public/erc20/lib.rs | 10 +- .../public/fallible-setter/lib.rs | 15 +-- .../public/payment-channel/lib.rs | 38 ++---- integration-tests/public/trait-erc20/lib.rs | 9 +- 9 files changed, 266 insertions(+), 102 deletions(-) create mode 100644 crates/ink/src/ensure.rs create mode 100644 integration-tests/public/ensure-test/Cargo.toml create mode 100644 integration-tests/public/ensure-test/lib.rs diff --git a/crates/ink/src/ensure.rs b/crates/ink/src/ensure.rs new file mode 100644 index 0000000000..d4d5c03ccc --- /dev/null +++ b/crates/ink/src/ensure.rs @@ -0,0 +1,95 @@ +// Copyright (C) Use Ink (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Evaluate `$condition:expr` and if not true return `Err($error:expr)`. +/// +/// This macro is similar to `frame_support::ensure!` and provides a convenient +/// way to check conditions and return errors in ink! contracts. +/// +/// # Example +/// +/// # use ink::ensure; +/// # #[derive(Debug, PartialEq, Eq)] +/// # #[ink::error] +/// # pub enum Error { +/// # InsufficientBalance, +/// # } +/// # pub type Result = core::result::Result; +/// # +/// # fn example(balance: u32, amount: u32) -> Result<()> { +/// ensure!(balance >= amount, Error::InsufficientBalance); +/// // ... rest of the function +/// # Ok(()) +/// # } +/// ``` +#[macro_export] +macro_rules! ensure { + ( $condition:expr, $error:expr $(,)? ) => {{ + if !$condition { + return ::core::result::Result::Err(::core::convert::Into::into($error)); + } + }}; +} + +#[cfg(test)] +mod tests { + + #[derive(Debug, PartialEq, Eq)] + enum TestError { + TooSmall, + TooLarge, + } + type TestResult = core::result::Result; + + #[test] + fn ensure_works_when_condition_is_true() { + fn test_function(value: u32) -> TestResult<()> { + crate::ensure!(value > 0, TestError::TooSmall); + crate::ensure!(value < 100, TestError::TooLarge); + Ok(()) + } + // This should succeed when the conditions are met + assert_eq!(test_function(50), Ok(())); + } + #[test] + fn ensure_returns_error_when_condition_is_false() { + fn test_function(value: u32) -> TestResult<()> { + crate::ensure!(value > 10, TestError::TooSmall); + Ok(()) + } + // This should return error when condition fails + assert_eq!(test_function(5), Err(TestError::TooSmall)); + } + #[test] + fn ensure_works_with_trailing_comma() { + fn test_function(value: u32) -> TestResult<()> { + crate::ensure!(value > 0, TestError::TooSmall,); + Ok(()) + } + + assert!(test_function(1).is_ok()); + assert_eq!(test_function(0), Err(TestError::TooSmall)); + } + + #[test] + fn ensure_works_with_string_error() { + fn test_function(value: u32) -> Result<(), String> { + crate::ensure!(value > 0, "Value must be positive".to_string()); + Ok(()) + } + + assert!(test_function(1).is_ok()); + assert_eq!(test_function(0), Err("Value must be positive".to_string())); + } +} diff --git a/crates/ink/src/lib.rs b/crates/ink/src/lib.rs index 684c402096..95e426ba69 100644 --- a/crates/ink/src/lib.rs +++ b/crates/ink/src/lib.rs @@ -34,6 +34,7 @@ pub mod codegen; pub use ink_env::reflect; mod contract_ref; +mod ensure; mod env_access; mod message_builder; pub mod precompiles; @@ -57,56 +58,24 @@ pub use polkavm_derive::*; pub mod storage { pub mod traits { - pub use ink_macro::{ - Storable, - StorableHint, - StorageKey, - StorageLayout, - }; + pub use ink_macro::{Storable, StorableHint, StorageKey, StorageLayout}; pub use ink_storage::traits::*; } - pub use ink_storage::{ - Lazy, - Mapping, - StorageVec, - }; + pub use ink_storage::{Lazy, Mapping, StorageVec}; } pub use self::{ - contract_ref::ToAddr, - env_access::EnvAccess, + contract_ref::ToAddr, env_access::EnvAccess, prelude::IIP2_WILDCARD_COMPLEMENT_SELECTOR, }; pub use ink_macro::{ - Event, - EventMetadata, - SolDecode, - SolEncode, - SolErrorDecode, - SolErrorEncode, - SolErrorMetadata, - blake2x256, - contract, - contract_ref, - error, - event, - scale_derive, - selector_bytes, - selector_id, - storage_item, - test, - trait_definition, + Event, EventMetadata, SolDecode, SolEncode, SolErrorDecode, SolErrorEncode, + SolErrorMetadata, blake2x256, contract, contract_ref, error, event, scale_derive, + selector_bytes, selector_id, storage_item, test, trait_definition, }; pub use ink_primitives::{ - Address, - ConstructorResult, - H160, - H256, - LangError, - MessageResult, - SolDecode, - SolEncode, - U256, + Address, ConstructorResult, H160, H256, LangError, MessageResult, SolDecode, + SolEncode, U256, }; #[cfg(feature = "std")] diff --git a/integration-tests/public/ensure-test/Cargo.toml b/integration-tests/public/ensure-test/Cargo.toml new file mode 100644 index 0000000000..2a9eb2d424 --- /dev/null +++ b/integration-tests/public/ensure-test/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "ensure-test" +version = "6.0.0-beta.1" +authors = ["Use Ink "] +edition = "2024" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] + +[package.metadata.ink-lang] +abi = "ink" +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = [ + 'cfg(ink_abi, values("ink", "sol", "all"))' +] \ No newline at end of file diff --git a/integration-tests/public/ensure-test/lib.rs b/integration-tests/public/ensure-test/lib.rs new file mode 100644 index 0000000000..deed9d62b7 --- /dev/null +++ b/integration-tests/public/ensure-test/lib.rs @@ -0,0 +1,109 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod ensure_test { + use ink::{U256, ensure}; + + /// A simple contract to test the ensure! macro. + #[ink(storage)] + #[derive(Default)] + pub struct EnsureTest { + balance: U256, + } + + /// Error types for testing ensure! + #[derive(Debug, PartialEq, Eq)] + #[ink::error] + pub enum Error { + InsufficientBalance, + ValueTooLarge, + ValueMustBePositive, + } + + /// Result type. + pub type Result = core::result::Result; + + impl EnsureTest { + /// Creates a new contract with initial balance. + #[ink(constructor)] + pub fn new(initial_balance: U256) -> Self { + Self { + balance: initial_balance, + } + } + + /// Get the current balance. + #[ink(message)] + pub fn balance(&self) -> U256 { + self.balance + } + + /// Transfer tokens - uses ensure! to check balance. + #[ink(message)] + pub fn transfer(&mut self, amount: U256) -> Result<()> { + // Test ensure! with positive value check + ensure!(amount > U256::from(0), Error::ValueMustBePositive); + + // Test ensure! with balance check + ensure!(self.balance >= amount, Error::InsufficientBalance); + + // Test ensure! with maximum value check + ensure!(amount <= U256::from(1000), Error::ValueTooLarge); + + self.balance -= amount; + Ok(()) + } + + /// Deposit tokens - uses ensure! with trailing comma. + #[ink(message)] + pub fn deposit(&mut self, amount: U256) -> Result<()> { + ensure!(amount > U256::from(0), Error::ValueMustBePositive,); + self.balance += amount; + Ok(()) + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn ensure_works_with_positive_value() { + let mut contract = EnsureTest::new(U256::from(100)); + assert!(contract.transfer(U256::from(50)).is_ok()); + } + + #[ink::test] + fn ensure_returns_error_for_zero_value() { + let mut contract = EnsureTest::new(U256::from(100)); + assert_eq!( + contract.transfer(U256::from(0)), + Err(Error::ValueMustBePositive) + ); + } + + #[ink::test] + fn ensure_returns_error_for_insufficient_balance() { + let mut contract = EnsureTest::new(U256::from(50)); + assert_eq!( + contract.transfer(U256::from(100)), + Err(Error::InsufficientBalance) + ); + } + + #[ink::test] + fn ensure_returns_error_for_value_too_large() { + let mut contract = EnsureTest::new(U256::from(2000)); + assert_eq!( + contract.transfer(U256::from(1001)), + Err(Error::ValueTooLarge) + ); + } + + #[ink::test] + fn ensure_works_with_trailing_comma() { + let mut contract = EnsureTest::new(U256::from(100)); + assert!(contract.deposit(U256::from(50)).is_ok()); + } + } +} diff --git a/integration-tests/public/erc1155/lib.rs b/integration-tests/public/erc1155/lib.rs index 744422331a..280ec5a6dd 100644 --- a/integration-tests/public/erc1155/lib.rs +++ b/integration-tests/public/erc1155/lib.rs @@ -4,6 +4,7 @@ use ink::{ Address, U256, prelude::vec::Vec, + ensure, }; // This is the return value that we expect if a smart contract supports receiving ERC-1155 @@ -48,16 +49,7 @@ pub enum Error { // The ERC-1155 result types. pub type Result = core::result::Result; -/// Evaluate `$x:expr` and if not true return `Err($y:expr)`. -/// -/// Used as `ensure!(expression_to_ensure, expression_to_return_on_false)`. -macro_rules! ensure { - ( $condition:expr, $error:expr $(,)? ) => {{ - if !$condition { - return ::core::result::Result::Err(::core::convert::Into::into($error)) - } - }}; -} + /// The interface for an ERC-1155 compliant contract. /// diff --git a/integration-tests/public/erc20/lib.rs b/integration-tests/public/erc20/lib.rs index fd41432384..67f543379d 100644 --- a/integration-tests/public/erc20/lib.rs +++ b/integration-tests/public/erc20/lib.rs @@ -5,6 +5,7 @@ mod erc20 { use ink::{ U256, storage::Mapping, + ensure, }; /// A simple ERC-20 contract. @@ -177,9 +178,7 @@ mod erc20 { ) -> Result<()> { let caller = self.env().caller(); let allowance = self.allowance_impl(&from, &caller); - if allowance < value { - return Err(Error::InsufficientAllowance) - } + ensure!(allowance >= value, Error::InsufficientAllowance); self.transfer_from_to(&from, &to, value)?; // We checked that allowance >= value #[allow(clippy::arithmetic_side_effects)] @@ -203,9 +202,8 @@ mod erc20 { value: U256, ) -> Result<()> { let from_balance = self.balance_of_impl(from); - if from_balance < value { - return Err(Error::InsufficientBalance) - } + ensure!(from_balance >= value, Error::InsufficientBalance); + // We checked that from_balance >= value #[allow(clippy::arithmetic_side_effects)] self.balances.insert(from, &(from_balance - value)); diff --git a/integration-tests/public/fallible-setter/lib.rs b/integration-tests/public/fallible-setter/lib.rs index e32c487a4a..c8f7da915b 100644 --- a/integration-tests/public/fallible-setter/lib.rs +++ b/integration-tests/public/fallible-setter/lib.rs @@ -13,6 +13,7 @@ pub enum Error { #[ink::contract] pub mod fallible_setter { use super::Error; + use ink::ensure; #[ink(storage)] pub struct FallibleSetter { @@ -24,9 +25,7 @@ pub mod fallible_setter { /// Returns an error if `init_value > 100`. #[ink(constructor)] pub fn new(init_value: u8) -> Result { - if init_value > 100 { - return Err(Error::TooLarge) - } + ensure!(init_value <= 100, Error::TooLarge); Ok(Self { value: init_value }) } @@ -36,14 +35,8 @@ pub mod fallible_setter { /// - `init_value > 100` #[ink(message)] pub fn try_set(&mut self, value: u8) -> Result<(), Error> { - if self.value == value { - return Err(Error::NoChange); - } - - if value > 100 { - return Err(Error::TooLarge); - } - + ensure!(self.value != value, Error::NoChange); + ensure!(value <= 100, Error::TooLarge); self.value = value; Ok(()) } diff --git a/integration-tests/public/payment-channel/lib.rs b/integration-tests/public/payment-channel/lib.rs index 5fe1ae3516..467f0054d7 100755 --- a/integration-tests/public/payment-channel/lib.rs +++ b/integration-tests/public/payment-channel/lib.rs @@ -41,7 +41,7 @@ #[ink::contract] mod payment_channel { - use ink::U256; + use ink::{U256, ensure}; /// Struct for storing the payment channel details. /// The creator of the contract, i.e. the `sender`, can deposit funds to the payment @@ -126,18 +126,11 @@ mod payment_channel { /// We split this out in order to make testing `close` simpler. fn close_inner(&mut self, amount: U256, signature: [u8; 65]) -> Result<()> { - if self.env().caller() != self.recipient { - return Err(Error::CallerIsNotRecipient) - } - - if amount < self.withdrawn { - return Err(Error::AmountIsLessThanWithdrawn) - } + ensure!(self.env().caller() == self.recipient, Error::CallerIsNotRecipient); + ensure!(amount >= self.withdrawn, Error::AmountIsLessThanWithdrawn); // Signature validation - if !self.is_signature_valid(amount, signature) { - return Err(Error::InvalidSignature) - } + ensure!(self.is_signature_valid(amount, signature), Error::InvalidSignature); // We checked that amount >= self.withdrawn #[allow(clippy::arithmetic_side_effects)] @@ -155,9 +148,7 @@ mod payment_channel { /// listen to in order to withdraw the funds before the `expiration`. #[ink(message)] pub fn start_sender_close(&mut self) -> Result<()> { - if self.env().caller() != self.sender { - return Err(Error::CallerIsNotSender) - } + ensure!(self.env().caller() == self.sender, Error::CallerIsNotSender); let now = self.env().block_timestamp(); let expiration = now.checked_add(self.close_duration).unwrap(); @@ -182,10 +173,7 @@ mod payment_channel { // expiration is set. Check if it's reached and if so, release the // funds and terminate the contract. let now = self.env().block_timestamp(); - if now < expiration { - return Err(Error::NotYetExpired) - } - + ensure!(now >= expiration, Error::NotYetExpired); self.env().terminate_contract(self.sender); } @@ -196,19 +184,11 @@ mod payment_channel { /// The `recipient` can withdraw the funds from the channel at any time. #[ink(message)] pub fn withdraw(&mut self, amount: U256, signature: [u8; 65]) -> Result<()> { - if self.env().caller() != self.recipient { - return Err(Error::CallerIsNotRecipient) - } - - // Signature validation - if !self.is_signature_valid(amount, signature) { - return Err(Error::InvalidSignature) - } + ensure!(self.env().caller() == self.recipient, Error::CallerIsNotRecipient); + ensure!(self.is_signature_valid(amount, signature), Error::InvalidSignature); // Make sure there's something to withdraw (guards against underflow) - if amount < self.withdrawn { - return Err(Error::AmountIsLessThanWithdrawn) - } + ensure!(amount >= self.withdrawn, Error::AmountIsLessThanWithdrawn); // We checked that amount >= self.withdrawn #[allow(clippy::arithmetic_side_effects)] diff --git a/integration-tests/public/trait-erc20/lib.rs b/integration-tests/public/trait-erc20/lib.rs index 55e823ba4b..d29aa5969a 100644 --- a/integration-tests/public/trait-erc20/lib.rs +++ b/integration-tests/public/trait-erc20/lib.rs @@ -5,6 +5,7 @@ mod erc20 { use ink::{ U256, storage::Mapping, + ensure, }; /// The ERC-20 error types. @@ -189,9 +190,7 @@ mod erc20 { ) -> Result<()> { let caller = self.env().caller(); let allowance = self.allowance_impl(&from, &caller); - if allowance < value { - return Err(Error::InsufficientAllowance) - } + ensure!(allowance >= value, Error::InsufficientAllowance); self.transfer_from_to(&from, &to, value)?; // We checked that allowance >= value #[allow(clippy::arithmetic_side_effects)] @@ -244,9 +243,7 @@ mod erc20 { value: U256, ) -> Result<()> { let from_balance = self.balance_of_impl(from); - if from_balance < value { - return Err(Error::InsufficientBalance) - } + ensure!(from_balance >= value, Error::InsufficientBalance); // We checked that from_balance >= value #[allow(clippy::arithmetic_side_effects)] self.balances.insert(from, &(from_balance - value)); From e9f58ec27a00bebc8b8a6561d98f229b76995b1b Mon Sep 17 00:00:00 2001 From: CECILIA-MULANDI Date: Sat, 13 Dec 2025 01:13:32 +0300 Subject: [PATCH 2/5] Add CHANGELOG entry for ensure! macro --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d3a5228d..775efbd4e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [Unreleased] ### Added +- Implement `ensure!` macro similar to `frame_support::ensure!` for convenient error checking in ink! contracts - [#2747](https://github.com/use-ink/ink/issues/2747) - Implements the API for the `pallet-revive` host functions `chain_id`, `balance_of`, `base_fee`, `origin`, `code_size`, `block_hash`, `block_author` - [#2719](https://github.com/use-ink/ink/pull/2719) - Implement `From` for "ink-as-dependency" contract refs - [#2728](https://github.com/use-ink/ink/pull/2728) From bd15298adfd7587f219623465ab498b2b137f205 Mon Sep 17 00:00:00 2001 From: CECILIA-MULANDI Date: Sat, 13 Dec 2025 01:36:41 +0300 Subject: [PATCH 3/5] Fix formatting in lib.rs --- crates/ink/src/lib.rs | 48 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/crates/ink/src/lib.rs b/crates/ink/src/lib.rs index 95e426ba69..3027fa4949 100644 --- a/crates/ink/src/lib.rs +++ b/crates/ink/src/lib.rs @@ -58,24 +58,56 @@ pub use polkavm_derive::*; pub mod storage { pub mod traits { - pub use ink_macro::{Storable, StorableHint, StorageKey, StorageLayout}; + pub use ink_macro::{ + Storable, + StorableHint, + StorageKey, + StorageLayout, + }; pub use ink_storage::traits::*; } - pub use ink_storage::{Lazy, Mapping, StorageVec}; + pub use ink_storage::{ + Lazy, + Mapping, + StorageVec, + }; } pub use self::{ - contract_ref::ToAddr, env_access::EnvAccess, + contract_ref::ToAddr, + env_access::EnvAccess, prelude::IIP2_WILDCARD_COMPLEMENT_SELECTOR, }; pub use ink_macro::{ - Event, EventMetadata, SolDecode, SolEncode, SolErrorDecode, SolErrorEncode, - SolErrorMetadata, blake2x256, contract, contract_ref, error, event, scale_derive, - selector_bytes, selector_id, storage_item, test, trait_definition, + Event, + EventMetadata, + SolDecode, + SolEncode, + SolErrorDecode, + SolErrorEncode, + SolErrorMetadata, + blake2x256, + contract, + contract_ref, + error, + event, + scale_derive, + selector_bytes, + selector_id, + storage_item, + test, + trait_definition, }; pub use ink_primitives::{ - Address, ConstructorResult, H160, H256, LangError, MessageResult, SolDecode, - SolEncode, U256, + Address, + ConstructorResult, + H160, + H256, + LangError, + MessageResult, + SolDecode, + SolEncode, + U256, }; #[cfg(feature = "std")] From d5df2f4ba2576b23098abd7afb9bee01ad7515c0 Mon Sep 17 00:00:00 2001 From: CECILIA-MULANDI Date: Sat, 13 Dec 2025 10:04:44 +0300 Subject: [PATCH 4/5] Fix formatting in ensure-test contract --- integration-tests/public/ensure-test/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/integration-tests/public/ensure-test/lib.rs b/integration-tests/public/ensure-test/lib.rs index deed9d62b7..4ad380e5f8 100644 --- a/integration-tests/public/ensure-test/lib.rs +++ b/integration-tests/public/ensure-test/lib.rs @@ -2,7 +2,10 @@ #[ink::contract] mod ensure_test { - use ink::{U256, ensure}; + use ink::{ + U256, + ensure, + }; /// A simple contract to test the ensure! macro. #[ink(storage)] From 2b0470975aaa8dca49bcb7322241c2c9f258bdbb Mon Sep 17 00:00:00 2001 From: CECILIA-MULANDI Date: Sun, 14 Dec 2025 17:42:15 +0300 Subject: [PATCH 5/5] Add E2E tests for ensure! macro --- integration-tests/public/ensure-test/lib.rs | 100 ++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/integration-tests/public/ensure-test/lib.rs b/integration-tests/public/ensure-test/lib.rs index 4ad380e5f8..5573bf3f87 100644 --- a/integration-tests/public/ensure-test/lib.rs +++ b/integration-tests/public/ensure-test/lib.rs @@ -109,4 +109,104 @@ mod ensure_test { assert!(contract.deposit(U256::from(50)).is_ok()); } } + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + type E2EResult = std::result::Result>; + #[ink_e2e::test] + async fn e2e_transfer_succeeds(mut client: Client) -> E2EResult<()> { + // Deploy the contract + let mut constructor = EnsureTestRef::new(U256::from(1000)); + let contract = client + .instantiate("ensure_test", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder = contract.call_builder::(); + let transfer = call_builder.transfer(U256::from(500)); + let result = client + .call(&ink_e2e::alice(), &transfer) + .submit() + .await + .expect("transfer should succeed"); + assert!(result.return_value().is_ok()); + + Ok(()) + } + #[ink_e2e::test] + async fn e2e_transfer_fails_with_value_must_be_positive( + mut client: Client, + ) -> E2EResult<()> { + // Deploy contract + let mut constructor = EnsureTestRef::new(U256::from(1000)); + let contract = client + .instantiate("ensure_test", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder = contract.call_builder::(); + + // Try to transfer zero - should fail + let transfer = call_builder.transfer(U256::from(0)); + let result = client.call(&ink_e2e::alice(), &transfer).dry_run().await?; + + // Check it reverted and decode the error + assert!(result.did_revert(), "should revert"); + let error_data = result.return_data(); + let decoded_error = + ::decode(&mut &error_data[..])?; + assert_eq!(decoded_error, Error::ValueMustBePositive); + + Ok(()) + } + #[ink_e2e::test] + async fn e2e_transfer_fails_with_insufficient_balance( + mut client: Client, + ) -> E2EResult<()> { + let mut constructor = EnsureTestRef::new(U256::from(100)); + let contract = client + .instantiate("ensure_test", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder = contract.call_builder::(); + + // Try to transfer more than balance (200 when balance is 100) + let transfer = call_builder.transfer(U256::from(200)); + let result = client.call(&ink_e2e::alice(), &transfer).dry_run().await?; + + assert!(result.did_revert()); + let error_data = result.return_data(); + let decoded_error = + ::decode(&mut &error_data[..])?; + assert_eq!(decoded_error, Error::InsufficientBalance); + + Ok(()) + } + #[ink_e2e::test] + async fn e2e_transfer_fails_with_value_too_large( + mut client: Client, + ) -> E2EResult<()> { + let mut constructor = EnsureTestRef::new(U256::from(2000)); + let contract = client + .instantiate("ensure_test", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder = contract.call_builder::(); + + // Try to transfer more than max (1001 when max is 1000) + let transfer = call_builder.transfer(U256::from(1001)); + let result = client.call(&ink_e2e::alice(), &transfer).dry_run().await?; + + assert!(result.did_revert()); + let error_data = result.return_data(); + let decoded_error = + ::decode(&mut &error_data[..])?; + assert_eq!(decoded_error, Error::ValueTooLarge); + + Ok(()) + } + } }