diff --git a/Cargo.lock b/Cargo.lock index 8cae2b0..6bb98ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2392,6 +2392,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" name = "token-vesting" version = "0.2.0" dependencies = [ + "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", diff --git a/artifacts/bindings_perp.wasm b/artifacts/bindings_perp.wasm index 2651e84..a15f987 100644 Binary files a/artifacts/bindings_perp.wasm and b/artifacts/bindings_perp.wasm differ diff --git a/artifacts/checksums.txt b/artifacts/checksums.txt index b355134..2af61c4 100644 --- a/artifacts/checksums.txt +++ b/artifacts/checksums.txt @@ -1,8 +1,8 @@ -37e1b6a8583c5158568cebedbced902936d2f4f397f9ba20fe8e3f49a312fb8b bindings_perp.wasm -d0d3df98c4af178c66630777092df45462289530b731e2ddc6b1c2300d1d657e controller.wasm +65548e00849045afabb51ec80f357dc1306198ffbfd548d03660ef40fba29017 bindings_perp.wasm +67191fdbcaaaae07c19cb3fca86a7273fae46c1015a0c52b1526b5329e559d81 controller.wasm ec6cc298bf32cad7f5f426002fdcee602b133f24f6f7d65905c5aad0e3b777c9 incentives.wasm 459f3d2dd6240ef36cba48d3e102407639caffa7bca9c3b924df483b4fd40b6a lockup.wasm -663f737a0f25ec93eb6dab83f8872e3a7a289cde2a3ec4c49d1bf6127b992ec8 nibi_stargate.wasm +d2dfc9ed4a689a8b23b020421c7e3fc0a90fd985c1291a44659073cc9021bb38 nibi_stargate.wasm bbb9c32c863ff78366ac5bae241967b206a6ad463e0154e7081309ab36904dce pricefeed.wasm -76dc1fc5e9b7fd975bde8f30d364b185733402ffe64fd841866bdf85634dcd3a shifter.wasm -a2f0262bbb39bcc16a353976154de22325e53f68f264ba61549f39d78dde2291 token_vesting.wasm +8014645275544b90c2ae33002e30c6cce6859dcd1193a48c599bedf4fb1015e3 shifter.wasm +0e4db7a116f27db4973e46db4a9cdd60abde1fb3e0dfeb7b1d5d9197c788229b token_vesting.wasm diff --git a/artifacts/checksums_intermediate.txt b/artifacts/checksums_intermediate.txt index cbb8ac4..326fdc4 100644 --- a/artifacts/checksums_intermediate.txt +++ b/artifacts/checksums_intermediate.txt @@ -1,8 +1,8 @@ -1d228461eaa095ec54360c920a851ed142703721f1e5596e740068f19492a868 target/wasm32-unknown-unknown/release/bindings_perp.wasm -da3bdc0569f318d367eea17f39517e9f39d75b37c164b00bed22a4050b4e088c target/wasm32-unknown-unknown/release/controller.wasm e03f22cf0c8fe52eda23249aafdc7afc2545b227918d9480835b160187083ad3 target/wasm32-unknown-unknown/release/incentives.wasm 9a088c48bd29274d7a0a95359d3749c236dc323d31e94ed0aba0c6c7826930e5 target/wasm32-unknown-unknown/release/lockup.wasm 8c6960f4852bfde4b9ed427a1708f2d0039d439aeb7c1697ece7059a0b998989 target/wasm32-unknown-unknown/release/pricefeed.wasm -a82731d918951ed592e25fb1bd4afba60e0b2be2346d99dda1c52118730ce678 target/wasm32-unknown-unknown/release/shifter.wasm -9318d75912302fedb0f775bb2fe75a60c494e7535f0e08e8e4eb0442cc5ef49a target/wasm32-unknown-unknown/release/token_vesting.wasm -02098f31f1a0dac2f01832c40ab3fc85883533fce16bb8aef966dbb201200b83 target/wasm32-unknown-unknown/release/nibi_stargate.wasm +f95b4a566d319f6cf39de88d168c19134bdff3864916c48c71a9a7f2d4474ee2 target/wasm32-unknown-unknown/release/bindings_perp.wasm +516fc6e8ca410041834e807cfed96478b51805d5c112bdd520bfd32e0f55eb4b target/wasm32-unknown-unknown/release/controller.wasm +ae4e6405de49a97cde63c7d8abb88a77b8dcb8211759dc22ecab61b8e98f8519 target/wasm32-unknown-unknown/release/nibi_stargate.wasm +4a9bc648eaa78b691e286108dcf57111b1325c8df4c59366532b19cf80bac782 target/wasm32-unknown-unknown/release/shifter.wasm +b457a863837b7f4e93a322e9cc90887bd6b6d51f88efd0be50d9d52ee5a8c2d0 target/wasm32-unknown-unknown/release/token_vesting.wasm diff --git a/artifacts/controller.wasm b/artifacts/controller.wasm index b45bd3d..71ebcfe 100644 Binary files a/artifacts/controller.wasm and b/artifacts/controller.wasm differ diff --git a/artifacts/nibi_stargate.wasm b/artifacts/nibi_stargate.wasm index c9de847..36f72a4 100644 Binary files a/artifacts/nibi_stargate.wasm and b/artifacts/nibi_stargate.wasm differ diff --git a/artifacts/shifter.wasm b/artifacts/shifter.wasm index a062597..22ecb94 100644 Binary files a/artifacts/shifter.wasm and b/artifacts/shifter.wasm differ diff --git a/artifacts/token_vesting.wasm b/artifacts/token_vesting.wasm index b7dddae..6ce1cc6 100644 Binary files a/artifacts/token_vesting.wasm and b/artifacts/token_vesting.wasm differ diff --git a/contracts/token-vesting/Cargo.toml b/contracts/token-vesting/Cargo.toml index cb3bb5e..7a2d3bf 100644 --- a/contracts/token-vesting/Cargo.toml +++ b/contracts/token-vesting/Cargo.toml @@ -29,4 +29,7 @@ thiserror = { version = "1.0.49" } cw-storage-plus = "1.1.0" schemars = "0.8.15" serde = { version = "1.0.188", default-features = false, features = ["derive"] } -serde_json = { version = "1.0", default-features = false, features = ["alloc"] } \ No newline at end of file +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +[dev-dependencies] +anyhow = { workspace = true } \ No newline at end of file diff --git a/contracts/token-vesting/src/contract.rs b/contracts/token-vesting/src/contract.rs index 90705a8..8af8d76 100644 --- a/contracts/token-vesting/src/contract.rs +++ b/contracts/token-vesting/src/contract.rs @@ -11,6 +11,7 @@ use serde_json::to_string; use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg, Denom}; use cw_storage_plus::Bound; +use crate::errors::ContractError; use crate::msg::{ Cw20HookMsg, ExecuteMsg, InstantiateMsg, QueryMsg, VestingAccountResponse, VestingData, VestingSchedule, @@ -33,9 +34,11 @@ pub fn execute( env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> StdResult { +) -> Result { match msg { - ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), + ExecuteMsg::Receive(msg) => { + receive_cw20(deps, env, info, msg).map_err(ContractError::from) + } ExecuteMsg::RegisterVestingAccount { master_address, address, @@ -45,7 +48,8 @@ pub fn execute( if info.funds.len() != 1 { return Err(StdError::generic_err( "must deposit only one type of token", - )); + ) + .into()); } let deposit_coin = info.funds[0].clone(); @@ -87,12 +91,12 @@ fn register_vesting_account( deposit_denom: Denom, deposit_amount: Uint128, vesting_schedule: VestingSchedule, -) -> StdResult { +) -> Result { let denom_key = denom_to_key(deposit_denom.clone()); // vesting_account existence check if VESTING_ACCOUNTS.has(storage, (address.as_str(), &denom_key)) { - return Err(StdError::generic_err("already exists")); + return Err(StdError::generic_err("already exists").into()); } // validate vesting schedule @@ -131,7 +135,7 @@ fn deregister_vesting_account( denom: Denom, vested_token_recipient: Option, left_vesting_token_recipient: Option, -) -> StdResult { +) -> Result { let denom_key = denom_to_key(denom.clone()); let sender = info.sender; @@ -141,17 +145,17 @@ fn deregister_vesting_account( let account = VESTING_ACCOUNTS .may_load(deps.storage, (address.as_str(), &denom_key))?; if account.is_none() { - return Err(StdError::generic_err(format!( + return Err(ContractError::Std(StdError::generic_err(format!( "vesting entry is not found for denom {:?}", to_string(&denom).unwrap(), - ))); + )))); } let account = account.unwrap(); if account.master_address.is_none() || account.master_address.unwrap() != sender { - return Err(StdError::generic_err("unauthorized")); + return Err(StdError::generic_err("unauthorized").into()); } // remove vesting account @@ -207,7 +211,7 @@ fn claim( info: MessageInfo, denoms: Vec, recipient: Option, -) -> StdResult { +) -> Result { let sender = info.sender; let recipient = recipient.unwrap_or_else(|| sender.to_string()); @@ -223,7 +227,8 @@ fn claim( return Err(StdError::generic_err(format!( "vesting entry is not found for denom {}", to_string(&denom).unwrap(), - ))); + )) + .into()); } let mut account = account.unwrap(); @@ -301,7 +306,7 @@ pub fn receive_cw20( env: Env, info: MessageInfo, cw20_msg: Cw20ReceiveMsg, -) -> StdResult { +) -> Result { let amount = cw20_msg.amount; let _sender = cw20_msg.sender; let contract = info.sender; @@ -320,7 +325,7 @@ pub fn receive_cw20( amount, vesting_schedule, ), - Err(_) => Err(StdError::generic_err("invalid cw20 hook message")), + Err(_) => Err(StdError::generic_err("invalid cw20 hook message").into()), } } @@ -387,3 +392,153 @@ fn vesting_account( Ok(VestingAccountResponse { address, vestings }) } + +#[cfg(test)] +pub mod tests { + + use super::*; + use anyhow::anyhow; + use cosmwasm_std::{ + coin, + testing::{self, MockApi, MockQuerier, MockStorage}, + Empty, OwnedDeps, Uint64, + }; + + pub type TestResult = Result<(), anyhow::Error>; + + pub fn mock_env_with_time(block_time: u64) -> Env { + let mut env = testing::mock_env(); + env.block.time = Timestamp::from_seconds(block_time); + env + } + + /// Convenience function for instantiating the contract at and setting up + /// the env to have the given block time. + pub fn setup_with_block_time( + block_time: u64, + ) -> anyhow::Result<(OwnedDeps, Env)> + { + let mut deps = testing::mock_dependencies(); + let env = mock_env_with_time(block_time); + instantiate( + deps.as_mut(), + env.clone(), + testing::mock_info("admin-sender", &[]), + InstantiateMsg {}, + )?; + Ok((deps, env)) + } + + #[test] + fn deregister_err_nonexistent_vesting_account() -> TestResult { + let (mut deps, _env) = setup_with_block_time(0)?; + + let msg = ExecuteMsg::DeregisterVestingAccount { + address: "nonexistent".to_string(), + denom: Denom::Native("token".to_string()), + vested_token_recipient: None, + left_vesting_token_recipient: None, + }; + + let res = execute( + deps.as_mut(), + testing::mock_env(), + testing::mock_info("admin-sender", &[]), + msg, + ); + + match res { + Ok(_) => Err(anyhow!("Unexpected result: {:#?}", res)), + Err(ContractError::Std(StdError::GenericErr { msg, .. })) => { + assert!(msg.contains("vesting entry is not found for denom")); + Ok(()) + } + Err(err) => Err(anyhow!("Unexpected error: {:#?}", err)), + } + } + + #[test] + fn deregister_err_unauthorized_vesting_account() -> TestResult { + // Set up the environment with a block time before the vesting start time + let (mut deps, env) = setup_with_block_time(50)?; + + let register_msg = ExecuteMsg::RegisterVestingAccount { + master_address: Some("addr0002".to_string()), + address: "addr0001".to_string(), + vesting_schedule: VestingSchedule::LinearVesting { + start_time: Uint64::new(100), + end_time: Uint64::new(110), + vesting_amount: Uint128::new(1000000u128), + }, + }; + + execute( + deps.as_mut(), + env.clone(), // Use the custom environment with the adjusted block time + testing::mock_info("admin-sender", &[coin(1000000, "token")]), + register_msg, + )?; + + // Try to deregister with unauthorized sender + let msg = ExecuteMsg::DeregisterVestingAccount { + address: "addr0001".to_string(), + denom: Denom::Native("token".to_string()), + vested_token_recipient: None, + left_vesting_token_recipient: None, + }; + + let res = execute( + deps.as_mut(), + env, // Use the custom environment with the adjusted block time + testing::mock_info("addr0003", &[]), + msg, + ); + match res { + Err(ContractError::Std(StdError::GenericErr { msg, .. })) + if msg == "unauthorized" => {} + _ => return Err(anyhow!("Unexpected result: {:?}", res)), + } + + Ok(()) + } + + #[test] + fn deregister_successful() -> TestResult { + // Set up the environment with a block time before the vesting start time + let (mut deps, env) = setup_with_block_time(50)?; + + let register_msg = ExecuteMsg::RegisterVestingAccount { + master_address: Some("addr0002".to_string()), + address: "addr0001".to_string(), + vesting_schedule: VestingSchedule::LinearVesting { + start_time: Uint64::new(100), + end_time: Uint64::new(110), + vesting_amount: Uint128::new(1000000u128), + }, + }; + + execute( + deps.as_mut(), + env.clone(), // Use the custom environment with the adjusted block time + testing::mock_info("admin-sender", &[coin(1000000, "token")]), + register_msg, + )?; + + // Deregister with the master address + let msg = ExecuteMsg::DeregisterVestingAccount { + address: "addr0001".to_string(), + denom: Denom::Native("token".to_string()), + vested_token_recipient: None, + left_vesting_token_recipient: None, + }; + + let _res = execute( + deps.as_mut(), + env, // Use the custom environment with the adjusted block time + testing::mock_info("addr0002", &[]), + msg, + )?; + + Ok(()) + } +} diff --git a/contracts/token-vesting/src/errors.rs b/contracts/token-vesting/src/errors.rs new file mode 100644 index 0000000..aef5e59 --- /dev/null +++ b/contracts/token-vesting/src/errors.rs @@ -0,0 +1,54 @@ +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error(transparent)] + Std(#[from] cosmwasm_std::StdError), + + #[error(transparent)] + Vesting(#[from] VestingError), + + #[error(transparent)] + Cliff(#[from] CliffError), + + #[error(transparent)] + Overflow(#[from] cosmwasm_std::OverflowError), +} + +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum CliffError { + #[error("cliff_amount is zero but should be greater than 0")] + ZeroAmount, + + #[error("cliff_time ({cliff_time}) should be greater than block_time ({block_time})")] + InvalidTime { cliff_time: u64, block_time: u64 }, + + #[error("cliff_amount ({cliff_amount}) should be less than or equal to vesting_amount ({vesting_amount})")] + ExcessiveAmount { + cliff_amount: u128, + vesting_amount: u128, + }, +} + +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum VestingError { + #[error("vesting_amount is zero but should be greater than 0")] + ZeroVestingAmount, + + #[error( + "end_time ({end_time}) should be greater than start_time ({start_time})" + )] + InvalidTimeRange { start_time: u64, end_time: u64 }, + + #[error("start_time ({start_time}) should be greater than block_time ({block_time})")] + StartBeforeBlockTime { start_time: u64, block_time: u64 }, + + #[error(transparent)] + Cliff(#[from] CliffError), + + #[error("vesting_amount ({vesting_amount}) should be equal to deposit_amount ({deposit_amount})")] + MismatchedVestingAndDepositAmount { + vesting_amount: u128, + deposit_amount: u128, + }, +} diff --git a/contracts/token-vesting/src/lib.rs b/contracts/token-vesting/src/lib.rs index ffa4f02..4368eaf 100644 --- a/contracts/token-vesting/src/lib.rs +++ b/contracts/token-vesting/src/lib.rs @@ -1,4 +1,5 @@ pub mod contract; +pub mod errors; pub mod msg; pub mod state; diff --git a/contracts/token-vesting/src/msg.rs b/contracts/token-vesting/src/msg.rs index 0474643..c5c5c0d 100644 --- a/contracts/token-vesting/src/msg.rs +++ b/contracts/token-vesting/src/msg.rs @@ -1,7 +1,9 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{StdError, StdResult, Timestamp, Uint128, Uint64}; +use cosmwasm_std::{StdResult, Timestamp, Uint128, Uint64}; use cw20::{Cw20ReceiveMsg, Denom}; +use crate::errors::{CliffError, VestingError}; + /// Structure for the message that instantiates the smart contract. #[cw_serde] pub struct InstantiateMsg {} @@ -110,21 +112,25 @@ impl Cliff { &self, block_time: Timestamp, vesting_amount: Uint128, - ) -> StdResult<()> { + ) -> Result<(), CliffError> { if self.amount.is_zero() { - return Err(StdError::generic_err("assert(cliff_amount > 0)")); + return Err(CliffError::ZeroAmount); } - if self.time.u64() < block_time.seconds() { - return Err(StdError::generic_err( - "assert(cliff_time > block_time)", - )); + let cliff_time_seconds = self.time.u64(); + if cliff_time_seconds < block_time.seconds() { + return Err(CliffError::InvalidTime { + cliff_time: cliff_time_seconds, + block_time: block_time.seconds(), + }); } - if self.amount.u128() > vesting_amount.u128() { - return Err(StdError::generic_err( - "assert(cliff_amount <= vesting_amount)", - )); + let cliff_amount = self.amount.u128(); + if cliff_amount > vesting_amount.u128() { + return Err(CliffError::ExcessiveAmount { + cliff_amount, + vesting_amount: vesting_amount.u128(), + }); } Ok(()) } @@ -186,7 +192,7 @@ impl VestingSchedule { &self, block_time: Timestamp, deposit_amount: Uint128, - ) -> StdResult<()> { + ) -> Result<(), VestingError> { match &self { VestingSchedule::LinearVesting { start_time, @@ -194,27 +200,30 @@ impl VestingSchedule { vesting_amount, } => { if vesting_amount.is_zero() { - return Err(StdError::generic_err( - "assert(vesting_amount > 0)", - )); + return Err(VestingError::ZeroVestingAmount); } if start_time.u64() < block_time.seconds() { - return Err(StdError::generic_err( - "assert(start_time < block_time)", - )); + return Err(VestingError::StartBeforeBlockTime { + start_time: start_time.u64(), + block_time: block_time.seconds(), + }); } if end_time <= start_time { - return Err(StdError::generic_err( - "assert(end_time <= start_time)", - )); + return Err(VestingError::InvalidTimeRange { + start_time: start_time.u64(), + end_time: end_time.u64(), + }); } if vesting_amount != deposit_amount { - return Err(StdError::generic_err( - "assert(deposit_amount == vesting_amount)", - )); + return Err( + VestingError::MismatchedVestingAndDepositAmount { + vesting_amount: vesting_amount.u128(), + deposit_amount: deposit_amount.u128(), + }, + ); } Ok(()) } @@ -227,21 +236,21 @@ impl VestingSchedule { cliff_amount, } => { if vesting_amount.is_zero() { - return Err(StdError::generic_err( - "assert(vesting_amount > 0)", - )); + return Err(VestingError::ZeroVestingAmount); } if end_time <= start_time { - return Err(StdError::generic_err( - "assert(end_time > start_time)", - )); + return Err(VestingError::InvalidTimeRange { + start_time: start_time.u64(), + end_time: end_time.u64(), + }); } if start_time.u64() < block_time.seconds() { - return Err(StdError::generic_err( - "assert(start_time > block_time)", - )); + return Err(VestingError::StartBeforeBlockTime { + start_time: start_time.u64(), + block_time: block_time.seconds(), + }); } let cliff = Cliff { @@ -255,51 +264,43 @@ impl VestingSchedule { } } -#[test] -fn linear_vesting_vested_amount() { - let schedule = VestingSchedule::LinearVesting { - start_time: Uint64::new(100), - end_time: Uint64::new(110), - vesting_amount: Uint128::new(1000000u128), - }; - - assert_eq!(schedule.vested_amount(100).unwrap(), Uint128::zero()); - assert_eq!( - schedule.vested_amount(105).unwrap(), - Uint128::new(500000u128) - ); - assert_eq!( - schedule.vested_amount(110).unwrap(), - Uint128::new(1000000u128) - ); - assert_eq!( - schedule.vested_amount(115).unwrap(), - Uint128::new(1000000u128) - ); -} +#[cfg(test)] +pub mod tests { + use super::*; + use crate::contract::tests::TestResult; -#[test] -fn linear_vesting_with_cliff_vested_amount() { - let schedule = VestingSchedule::LinearVestingWithCliff { - start_time: Uint64::new(100), - end_time: Uint64::new(110), - vesting_amount: Uint128::new(1_000_000_u128), - cliff_amount: Uint128::new(100_000_u128), - cliff_time: Uint64::new(105), - }; - - assert_eq!(schedule.vested_amount(100).unwrap(), Uint128::zero()); - assert_eq!( - schedule.vested_amount(105).unwrap(), - Uint128::new(100000u128) - ); // cliff time then the cliff amount - assert_eq!( - // complete vesting - schedule.vested_amount(120).unwrap(), - Uint128::new(1000000u128) - ); - - // other permutations - assert_eq!(schedule.vested_amount(104).unwrap(), Uint128::zero()); // before cliff time - assert_eq!(schedule.vested_amount(109).unwrap(), Uint128::new(820_000)); // after cliff time but before end time + #[test] + fn linear_vesting_vested_amount() -> TestResult { + let schedule = VestingSchedule::LinearVesting { + start_time: Uint64::new(100), + end_time: Uint64::new(110), + vesting_amount: Uint128::new(1000000u128), + }; + + assert_eq!(schedule.vested_amount(100)?, Uint128::zero()); + assert_eq!(schedule.vested_amount(105)?, Uint128::new(500000u128)); + assert_eq!(schedule.vested_amount(110)?, Uint128::new(1000000u128)); + assert_eq!(schedule.vested_amount(115)?, Uint128::new(1000000u128)); + + Ok(()) + } + + #[test] + fn linear_vesting_with_cliff_vested_amount() -> TestResult { + let schedule = VestingSchedule::LinearVestingWithCliff { + start_time: Uint64::new(100), + end_time: Uint64::new(110), + vesting_amount: Uint128::new(1_000_000_u128), + cliff_amount: Uint128::new(100_000_u128), + cliff_time: Uint64::new(105), + }; + + assert_eq!(schedule.vested_amount(100)?, Uint128::zero()); + assert_eq!(schedule.vested_amount(105)?, Uint128::new(100000u128)); // cliff time then the cliff amount + assert_eq!(schedule.vested_amount(120)?, Uint128::new(1000000u128)); // complete vesting + assert_eq!(schedule.vested_amount(104)?, Uint128::zero()); // before cliff time + assert_eq!(schedule.vested_amount(109)?, Uint128::new(820_000)); // after cliff time but before end time + + Ok(()) + } } diff --git a/contracts/token-vesting/src/testing.rs b/contracts/token-vesting/src/testing.rs index b8d8c98..dfcff73 100644 --- a/contracts/token-vesting/src/testing.rs +++ b/contracts/token-vesting/src/testing.rs @@ -1,4 +1,6 @@ +use crate::contract::tests::TestResult; use crate::contract::{execute, instantiate, query}; +use crate::errors::{CliffError, ContractError, VestingError}; use crate::msg::{ Cw20HookMsg, ExecuteMsg, InstantiateMsg, QueryMsg, VestingAccountResponse, VestingData, VestingSchedule, @@ -14,27 +16,26 @@ use cosmwasm_std::{ use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg, Denom}; #[test] -fn proper_initialization() { +fn proper_initialization() -> TestResult { let mut deps = mock_dependencies(); let msg = InstantiateMsg {}; let info = mock_info("addr0000", &[]); - // we can just call .unwrap() to assert this was a success - let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg)?; + Ok(()) } #[test] -fn register_cliff_vesting_account_with_native_token() { +fn register_cliff_vesting_account_with_native_token() -> TestResult { let mut deps = mock_dependencies(); let _res = instantiate( deps.as_mut(), mock_env(), mock_info("addr0000", &[]), InstantiateMsg {}, - ) - .unwrap(); + )?; let mut env = mock_env(); env.block.time = Timestamp::from_seconds(100); @@ -60,60 +61,101 @@ fn register_cliff_vesting_account_with_native_token() { // zero amount vesting token let msg = create_msg(100, 110, 0, 1000, 105); - require_error(&mut deps, &env, msg, "assert(vesting_amount > 0)"); + require_error( + &mut deps, + &env, + msg, + ContractError::Vesting(VestingError::ZeroVestingAmount), + ); // zero amount cliff token let msg = create_msg(100, 110, 1000, 0, 105); - require_error(&mut deps, &env, msg, "assert(cliff_amount > 0)"); + require_error( + &mut deps, + &env, + msg, + ContractError::Vesting(VestingError::Cliff(CliffError::ZeroAmount)), + ); // cliff time less than block time let msg = create_msg(100, 110, 1000, 1000, 99); - require_error(&mut deps, &env, msg, "assert(cliff_time > block_time)"); + require_error( + &mut deps, + &env, + msg, + ContractError::Vesting(VestingError::Cliff(CliffError::InvalidTime { + cliff_time: 99, + block_time: 100, + })), + ); // end time less than start time let msg = create_msg(110, 100, 1000, 1000, 105); - require_error(&mut deps, &env, msg, "assert(end_time > start_time)"); + require_error( + &mut deps, + &env, + msg, + ContractError::Vesting(VestingError::InvalidTimeRange { + start_time: 110, + end_time: 100, + }), + ); // start time less than block time let msg = create_msg(99, 110, 1000, 1000, 105); - require_error(&mut deps, &env, msg, "assert(start_time > block_time)"); + require_error( + &mut deps, + &env, + msg, + ContractError::Vesting(VestingError::StartBeforeBlockTime { + start_time: 99, + block_time: 100, + }), + ); // cliff amount greater than vesting amount - let msg = create_msg(100, 110, 1000, 1001, 105); + let (vesting_amount, cliff_amount, cliff_time) = (1000, 1001, 105); + let msg = create_msg(100, 110, vesting_amount, cliff_amount, cliff_time); require_error( &mut deps, &env, msg, - "assert(cliff_amount <= vesting_amount)", + ContractError::Vesting( + CliffError::ExcessiveAmount { + cliff_amount, + vesting_amount, + } + .into(), + ), ); + Ok(()) } fn require_error( deps: &mut OwnedDeps, env: &Env, msg: ExecuteMsg, - error_message: &str, + expected_error: ContractError, ) { let info = mock_info("addr0000", &[Coin::new(0u128, "uusd")]); let res = execute(deps.as_mut(), env.clone(), info, msg); - match res.unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, error_message) + match res { + Err(err) => { + assert_eq!(err, expected_error) } - _ => panic!("should not enter"), + Ok(_) => panic!("Expected error but got success: {res:?}"), } } #[test] -fn register_vesting_account_with_native_token() { +fn register_vesting_account_with_native_token() -> TestResult { let mut deps = mock_dependencies(); let _res = instantiate( deps.as_mut(), mock_env(), mock_info("addr0000", &[]), InstantiateMsg {}, - ) - .unwrap(); + )?; let mut env = mock_env(); env.block.time = Timestamp::from_seconds(100); @@ -128,7 +170,12 @@ fn register_vesting_account_with_native_token() { vesting_amount: Uint128::zero(), }, }; - require_error(&mut deps, &env, msg, "assert(vesting_amount > 0)"); + require_error( + &mut deps, + &env, + msg, + ContractError::Vesting(VestingError::ZeroVestingAmount), + ); // normal amount vesting token let msg = ExecuteMsg::RegisterVestingAccount { @@ -144,11 +191,11 @@ fn register_vesting_account_with_native_token() { // invalid amount let info = mock_info("addr0000", &[]); let res = execute(deps.as_mut(), env.clone(), info, msg.clone()); - match res.unwrap_err() { - StdError::GenericErr { msg, .. } => { + match res { + Err(ContractError::Std(StdError::GenericErr { msg, .. })) => { assert_eq!(msg, "must deposit only one type of token") } - _ => panic!("should not enter"), + _ => panic!("should not enter. got result: {res:?}"), } // invalid amount @@ -157,26 +204,33 @@ fn register_vesting_account_with_native_token() { &[Coin::new(100u128, "uusd"), Coin::new(10u128, "ukrw")], ); let res = execute(deps.as_mut(), env.clone(), info, msg.clone()); - match res.unwrap_err() { - StdError::GenericErr { msg, .. } => { + match res { + Err(ContractError::Std(StdError::GenericErr { msg, .. })) => { assert_eq!(msg, "must deposit only one type of token") } - _ => panic!("should not enter"), + _ => panic!("should not enter. got result: {res:?}"), } // invalid amount let info = mock_info("addr0000", &[Coin::new(10u128, "uusd")]); let res = execute(deps.as_mut(), env.clone(), info, msg.clone()); - match res.unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "assert(deposit_amount == vesting_amount)") + match res { + Err(err) => { + assert_eq!( + err, + VestingError::MismatchedVestingAndDepositAmount { + vesting_amount: 1_000_000, + deposit_amount: 10 + } + .into() + ) } - _ => panic!("should not enter"), + _ => panic!("should not enter. got result: {res:?}"), } // valid amount let info = mock_info("addr0000", &[Coin::new(1000000u128, "uusd")]); - let res: Response = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); + let res: Response = execute(deps.as_mut(), env.clone(), info, msg)?; assert_eq!( res.attributes, vec![ @@ -190,19 +244,15 @@ fn register_vesting_account_with_native_token() { // query vesting account assert_eq!( - from_binary::( - &query( - deps.as_ref(), - env, - QueryMsg::VestingAccount { - address: "addr0001".to_string(), - start_after: None, - limit: None, - }, - ) - .unwrap() - ) - .unwrap(), + from_binary::(&query( + deps.as_ref(), + env, + QueryMsg::VestingAccount { + address: "addr0001".to_string(), + start_after: None, + limit: None, + }, + )?)?, VestingAccountResponse { address: "addr0001".to_string(), vestings: vec![VestingData { @@ -219,18 +269,18 @@ fn register_vesting_account_with_native_token() { }], } ); + Ok(()) } #[test] -fn register_vesting_account_with_cw20_token() { +fn register_vesting_account_with_cw20_token() -> TestResult { let mut deps = mock_dependencies(); let _res = instantiate( deps.as_mut(), mock_env(), mock_info("addr0000", &[]), InstantiateMsg {}, - ) - .unwrap(); + )?; let info = mock_info("token0000", &[]); let mut env = mock_env(); env.block.time = Timestamp::from_seconds(100); @@ -247,17 +297,19 @@ fn register_vesting_account_with_cw20_token() { end_time: Uint64::new(110), vesting_amount: Uint128::zero(), }, - }) - .unwrap(), + })?, }); // invalid zero amount let res = execute(deps.as_mut(), env.clone(), info.clone(), msg); - match res.unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "assert(vesting_amount > 0)") + match res { + Err(err) => { + assert_eq!( + err, + ContractError::Vesting(VestingError::ZeroVestingAmount) + ) } - _ => panic!("should not enter"), + _ => panic!("should not enter. got result: {res:?}"), } // invariant amount @@ -272,17 +324,22 @@ fn register_vesting_account_with_cw20_token() { end_time: Uint64::new(110), vesting_amount: Uint128::new(999000u128), }, - }) - .unwrap(), + })?, }); // invalid amount let res = execute(deps.as_mut(), env.clone(), info.clone(), msg); - match res.unwrap_err() { - StdError::GenericErr { msg, .. } => { - assert_eq!(msg, "assert(deposit_amount == vesting_amount)") + match res { + Err(ContractError::Vesting( + VestingError::MismatchedVestingAndDepositAmount { + vesting_amount, + deposit_amount, + }, + )) => { + assert_eq!(vesting_amount, 999000u128); + assert_eq!(deposit_amount, 1000000u128); } - _ => panic!("should not enter"), + _ => panic!("should not enter. got result: {res:?}"), } // valid amount @@ -297,12 +354,11 @@ fn register_vesting_account_with_cw20_token() { end_time: Uint64::new(110), vesting_amount: Uint128::new(1000000u128), }, - }) - .unwrap(), + })?, }); // valid amount - let res: Response = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); + let res: Response = execute(deps.as_mut(), env.clone(), info, msg)?; assert_eq!( res.attributes, vec![ @@ -316,19 +372,15 @@ fn register_vesting_account_with_cw20_token() { // query vesting account assert_eq!( - from_binary::( - &query( - deps.as_ref(), - env, - QueryMsg::VestingAccount { - address: "addr0001".to_string(), - start_after: None, - limit: None, - }, - ) - .unwrap() - ) - .unwrap(), + from_binary::(&query( + deps.as_ref(), + env, + QueryMsg::VestingAccount { + address: "addr0001".to_string(), + start_after: None, + limit: None, + }, + )?)?, VestingAccountResponse { address: "addr0001".to_string(), vestings: vec![VestingData { @@ -345,18 +397,18 @@ fn register_vesting_account_with_cw20_token() { }], } ); + Ok(()) } #[test] -fn claim_native() { +fn claim_native() -> TestResult { let mut deps = mock_dependencies(); let _res = instantiate( deps.as_mut(), mock_env(), mock_info("addr0000", &[]), InstantiateMsg {}, - ) - .unwrap(); + )?; // init env to time 100 let mut env = mock_env(); @@ -374,7 +426,7 @@ fn claim_native() { }; let info = mock_info("addr0000", &[Coin::new(1000000u128, "uusd")]); - let _ = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); + let _ = execute(deps.as_mut(), env.clone(), info, msg)?; // make time to half claimable env.block.time = Timestamp::from_seconds(105); @@ -390,12 +442,12 @@ fn claim_native() { let info = mock_info("addr0001", &[]); let res = execute(deps.as_mut(), env.clone(), info.clone(), msg); - match res.unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!( + match res { + Err(ContractError::Std(StdError::GenericErr { msg, .. })) => assert_eq!( msg, "vesting entry is not found for denom {\"native\":\"ukrw\"}" ), - _ => panic!("should not enter"), + _ => panic!("should not enter. got result: {res:?}"), } // valid claim @@ -404,8 +456,7 @@ fn claim_native() { recipient: None, }; - let res = - execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone())?; assert_eq!( res.messages, vec![SubMsg::new(BankMsg::Send { @@ -430,19 +481,15 @@ fn claim_native() { // query vesting account assert_eq!( - from_binary::( - &query( - deps.as_ref(), - env.clone(), - QueryMsg::VestingAccount { - address: "addr0001".to_string(), - start_after: None, - limit: None, - }, - ) - .unwrap() - ) - .unwrap(), + from_binary::(&query( + deps.as_ref(), + env.clone(), + QueryMsg::VestingAccount { + address: "addr0001".to_string(), + start_after: None, + limit: None, + }, + )?)?, VestingAccountResponse { address: "addr0001".to_string(), vestings: vec![VestingData { @@ -463,7 +510,7 @@ fn claim_native() { // make time to half claimable env.block.time = Timestamp::from_seconds(110); - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info, msg)?; assert_eq!( res.messages, vec![SubMsg::new(BankMsg::Send { @@ -488,36 +535,32 @@ fn claim_native() { // query vesting account assert_eq!( - from_binary::( - &query( - deps.as_ref(), - env, - QueryMsg::VestingAccount { - address: "addr0001".to_string(), - start_after: None, - limit: None, - }, - ) - .unwrap() - ) - .unwrap(), + from_binary::(&query( + deps.as_ref(), + env, + QueryMsg::VestingAccount { + address: "addr0001".to_string(), + start_after: None, + limit: None, + }, + )?)?, VestingAccountResponse { address: "addr0001".to_string(), vestings: vec![], } ); + Ok(()) } #[test] -fn claim_cw20() { +fn claim_cw20() -> TestResult { let mut deps = mock_dependencies(); - let _res = instantiate( + instantiate( deps.as_mut(), mock_env(), mock_info("addr0000", &[]), InstantiateMsg {}, - ) - .unwrap(); + )?; // init env to time 100 let mut env = mock_env(); @@ -535,13 +578,12 @@ fn claim_cw20() { end_time: Uint64::new(110), vesting_amount: Uint128::new(1000000u128), }, - }) - .unwrap(), + })?, }); // valid amount let info = mock_info("token0001", &[]); - let _ = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); + execute(deps.as_mut(), env.clone(), info, msg)?; // make time to half claimable env.block.time = Timestamp::from_seconds(105); @@ -557,12 +599,12 @@ fn claim_cw20() { let info = mock_info("addr0001", &[]); let res = execute(deps.as_mut(), env.clone(), info.clone(), msg); - match res.unwrap_err() { - StdError::GenericErr { msg, .. } => assert_eq!( + match res { + Err(ContractError::Std(StdError::GenericErr { msg, .. })) => assert_eq!( msg, "vesting entry is not found for denom {\"cw20\":\"token0002\"}" ), - _ => panic!("should not enter"), + _ => panic!("should not enter. got result: {res:?}"), } // valid claim @@ -571,8 +613,7 @@ fn claim_cw20() { recipient: None, }; - let res = - execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone())?; assert_eq!( res.messages, vec![SubMsg::new(WasmMsg::Execute { @@ -581,10 +622,10 @@ fn claim_cw20() { msg: to_binary(&Cw20ExecuteMsg::Transfer { recipient: "addr0001".to_string(), amount: Uint128::new(500000u128), - }) - .unwrap(), + })?, }),] ); + assert_eq!( res.attributes, vec![ @@ -599,19 +640,15 @@ fn claim_cw20() { // query vesting account assert_eq!( - from_binary::( - &query( - deps.as_ref(), - env.clone(), - QueryMsg::VestingAccount { - address: "addr0001".to_string(), - start_after: None, - limit: None, - }, - ) - .unwrap() - ) - .unwrap(), + from_binary::(&query( + deps.as_ref(), + env.clone(), + QueryMsg::VestingAccount { + address: "addr0001".to_string(), + start_after: None, + limit: None, + }, + )?)?, VestingAccountResponse { address: "addr0001".to_string(), vestings: vec![VestingData { @@ -632,7 +669,7 @@ fn claim_cw20() { // make time to half claimable env.block.time = Timestamp::from_seconds(110); - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info, msg)?; assert_eq!( res.messages, vec![SubMsg::new(WasmMsg::Execute { @@ -641,8 +678,7 @@ fn claim_cw20() { msg: to_binary(&Cw20ExecuteMsg::Transfer { recipient: "addr0001".to_string(), amount: Uint128::new(500000u128), - }) - .unwrap(), + })?, }),] ); assert_eq!( @@ -659,36 +695,33 @@ fn claim_cw20() { // query vesting account assert_eq!( - from_binary::( - &query( - deps.as_ref(), - env, - QueryMsg::VestingAccount { - address: "addr0001".to_string(), - start_after: None, - limit: None, - }, - ) - .unwrap() - ) - .unwrap(), + from_binary::(&query( + deps.as_ref(), + env, + QueryMsg::VestingAccount { + address: "addr0001".to_string(), + start_after: None, + limit: None, + }, + )?)?, VestingAccountResponse { address: "addr0001".to_string(), vestings: vec![], } ); + + Ok(()) } #[test] -fn query_vesting_account() { +fn query_vesting_account() -> TestResult { let mut deps = mock_dependencies(); let _res = instantiate( deps.as_mut(), mock_env(), mock_info("addr0000", &[]), InstantiateMsg {}, - ) - .unwrap(); + )?; // init env to time 100 let mut env = mock_env(); @@ -706,7 +739,7 @@ fn query_vesting_account() { }; let info = mock_info("addr0000", &[Coin::new(1000000u128, "uusd")]); - let _ = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); + let _ = execute(deps.as_mut(), env.clone(), info, msg)?; let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { sender: "addr0000".to_string(), @@ -719,32 +752,27 @@ fn query_vesting_account() { end_time: Uint64::new(110), vesting_amount: Uint128::new(1000000u128), }, - }) - .unwrap(), + })?, }); // valid amount let info = mock_info("token0001", &[]); - let _ = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); + let _ = execute(deps.as_mut(), env.clone(), info, msg)?; // half claimable env.block.time = Timestamp::from_seconds(105); // query all entry assert_eq!( - from_binary::( - &query( - deps.as_ref(), - env.clone(), - QueryMsg::VestingAccount { - address: "addr0001".to_string(), - start_after: None, - limit: None, - }, - ) - .unwrap() - ) - .unwrap(), + from_binary::(&query( + deps.as_ref(), + env.clone(), + QueryMsg::VestingAccount { + address: "addr0001".to_string(), + start_after: None, + limit: None, + }, + )?)?, VestingAccountResponse { address: "addr0001".to_string(), vestings: vec![ @@ -778,19 +806,15 @@ fn query_vesting_account() { // query one entry assert_eq!( - from_binary::( - &query( - deps.as_ref(), - env.clone(), - QueryMsg::VestingAccount { - address: "addr0001".to_string(), - start_after: None, - limit: Some(1), - }, - ) - .unwrap() - ) - .unwrap(), + from_binary::(&query( + deps.as_ref(), + env.clone(), + QueryMsg::VestingAccount { + address: "addr0001".to_string(), + start_after: None, + limit: Some(1), + }, + )?)?, VestingAccountResponse { address: "addr0001".to_string(), vestings: vec![VestingData { @@ -810,19 +834,15 @@ fn query_vesting_account() { // query one entry after first one assert_eq!( - from_binary::( - &query( - deps.as_ref(), - env, - QueryMsg::VestingAccount { - address: "addr0001".to_string(), - start_after: Some(Denom::Cw20(Addr::unchecked("token0001"))), - limit: Some(1), - }, - ) - .unwrap() - ) - .unwrap(), + from_binary::(&query( + deps.as_ref(), + env, + QueryMsg::VestingAccount { + address: "addr0001".to_string(), + start_after: Some(Denom::Cw20(Addr::unchecked("token0001"))), + limit: Some(1), + }, + )?)?, VestingAccountResponse { address: "addr0001".to_string(), vestings: vec![VestingData { @@ -839,4 +859,5 @@ fn query_vesting_account() { }], } ); + Ok(()) }