diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index b822fe9acf..cb68a3bdaf 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -10,7 +10,7 @@ use cosmwasm_std::{ }; use mixnet_contract::IdentityKey; -pub const NUM_VESTING_PERIODS: u64 = 8; +pub const NUM_VESTING_PERIODS: usize = 8; pub const VESTING_PERIOD: u64 = 3 * 30 * 86400; pub const ADMIN_ADDRESS: &str = "admin"; @@ -55,7 +55,7 @@ pub fn execute( } } -fn try_withdraw_vested_coins( +pub fn try_withdraw_vested_coins( amount: Coin, env: Env, info: MessageInfo, @@ -63,7 +63,7 @@ fn try_withdraw_vested_coins( ) -> Result { let address = info.sender; if let Some(account) = get_account(deps.storage, &address) { - let spendable_coins = account.spendable_coins(None, &env, deps.storage); + let spendable_coins = account.spendable_coins(None, &env, deps.storage)?; if amount.amount < spendable_coins.amount { if let Some(balance) = get_account_balance(deps.storage, &address) { let new_balance = balance.u128().saturating_sub(amount.amount.u128()); @@ -194,15 +194,14 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result to_binary(&try_get_start_time(vesting_account_address, env, deps)?), + } => to_binary(&try_get_start_time(vesting_account_address, deps)?), QueryMsg::GetEndTime { vesting_account_address, - } => to_binary(&try_get_end_time(vesting_account_address, env, deps)?), + } => to_binary(&try_get_end_time(vesting_account_address, deps)?), QueryMsg::GetOriginalVesting { vesting_account_address, } => to_binary(&try_get_original_vesting( vesting_account_address, - env, deps, )?), QueryMsg::GetDelegatedFree { @@ -228,7 +227,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result, env: Env, @@ -236,13 +235,13 @@ fn try_get_locked_coins( ) -> Result { let address = deps.api.addr_validate(&vesting_account_address)?; if let Some(account) = get_account(deps.storage, &address) { - Ok(account.locked_coins(block_time, &env, deps.storage)) + Ok(account.locked_coins(block_time, &env, deps.storage)?) } else { Err(ContractError::NoAccountForAddress(vesting_account_address)) } } -fn try_get_spendable_coins( +pub fn try_get_spendable_coins( vesting_account_address: String, block_time: Option, env: Env, @@ -250,13 +249,13 @@ fn try_get_spendable_coins( ) -> Result { let address = deps.api.addr_validate(&vesting_account_address)?; if let Some(account) = get_account(deps.storage, &address) { - Ok(account.spendable_coins(block_time, &env, deps.storage)) + Ok(account.spendable_coins(block_time, &env, deps.storage)?) } else { Err(ContractError::NoAccountForAddress(vesting_account_address)) } } -fn try_get_vested_coins( +pub fn try_get_vested_coins( vesting_account_address: String, block_time: Option, env: Env, @@ -264,13 +263,13 @@ fn try_get_vested_coins( ) -> Result { let address = deps.api.addr_validate(&vesting_account_address)?; if let Some(account) = get_account(deps.storage, &address) { - Ok(account.get_vested_coins(block_time, &env)) + Ok(account.get_vested_coins(block_time, &env)?) } else { Err(ContractError::NoAccountForAddress(vesting_account_address)) } } -fn try_get_vesting_coins( +pub fn try_get_vesting_coins( vesting_account_address: String, block_time: Option, env: Env, @@ -278,15 +277,14 @@ fn try_get_vesting_coins( ) -> Result { let address = deps.api.addr_validate(&vesting_account_address)?; if let Some(account) = get_account(deps.storage, &address) { - Ok(account.get_vesting_coins(block_time, &env)) + Ok(account.get_vesting_coins(block_time, &env)?) } else { Err(ContractError::NoAccountForAddress(vesting_account_address)) } } -fn try_get_start_time( +pub fn try_get_start_time( vesting_account_address: String, - env: Env, deps: Deps, ) -> Result { let address = deps.api.addr_validate(&vesting_account_address)?; @@ -297,9 +295,8 @@ fn try_get_start_time( } } -fn try_get_end_time( +pub fn try_get_end_time( vesting_account_address: String, - env: Env, deps: Deps, ) -> Result { let address = deps.api.addr_validate(&vesting_account_address)?; @@ -310,9 +307,8 @@ fn try_get_end_time( } } -fn try_get_original_vesting( +pub fn try_get_original_vesting( vesting_account_address: String, - env: Env, deps: Deps, ) -> Result { let address = deps.api.addr_validate(&vesting_account_address)?; @@ -323,7 +319,7 @@ fn try_get_original_vesting( } } -fn try_get_delegated_free( +pub fn try_get_delegated_free( block_time: Option, vesting_account_address: String, env: Env, @@ -337,7 +333,7 @@ fn try_get_delegated_free( } } -fn try_get_delegated_vesting( +pub fn try_get_delegated_vesting( block_time: Option, vesting_account_address: String, env: Env, diff --git a/contracts/vesting/src/errors.rs b/contracts/vesting/src/errors.rs index 3c7e1904df..1c611c17dd 100644 --- a/contracts/vesting/src/errors.rs +++ b/contracts/vesting/src/errors.rs @@ -11,10 +11,14 @@ pub enum ContractError { NotAdmin(String), #[error("Balance not found for existing account ({0}), this is a bug")] NoBalanceForAddress(String), - #[error("Insufficient balance")] + #[error("Insufficient balance for address {0} -> {1}")] InsufficientBalance(String, u128), - #[error("Insufficient spendable balance")] + #[error("Insufficient spendable balance for address {0} -> {1}")] InsufficientSpendable(String, u128), - #[error("Only delegation owner can perform delegation actions, {0} is not the delegation owner")] + #[error( + "Only delegation owner can perform delegation actions, {0} is not the delegation owner" + )] NotDelegate(String), + #[error("Total vesting amount is inprobably low -> {0}, this is likely an error")] + ImprobableVestingAmount(u128), } diff --git a/contracts/vesting/src/lib.rs b/contracts/vesting/src/lib.rs index 759fc26df4..dd60bf28a5 100644 --- a/contracts/vesting/src/lib.rs +++ b/contracts/vesting/src/lib.rs @@ -1,6 +1,6 @@ -mod contract; +pub mod contract; mod errors; -mod messages; +pub mod messages; mod storage; mod support; mod vesting; diff --git a/contracts/vesting/src/messages.rs b/contracts/vesting/src/messages.rs index ad5303df1a..c734f70893 100644 --- a/contracts/vesting/src/messages.rs +++ b/contracts/vesting/src/messages.rs @@ -1,5 +1,4 @@ -use crate::vesting::{self, VestingPeriod}; -use cosmwasm_std::{Addr, Coin, Timestamp}; +use cosmwasm_std::{Coin, Timestamp}; use mixnet_contract::IdentityKey; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/contracts/vesting/src/storage.rs b/contracts/vesting/src/storage.rs index 2172fc5a45..b9d454a32d 100644 --- a/contracts/vesting/src/storage.rs +++ b/contracts/vesting/src/storage.rs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 use cosmwasm_std::{Addr, StdResult, Storage, Uint128}; use cosmwasm_storage::{bucket, bucket_read, Bucket, ReadonlyBucket}; -use mixnet_contract::IdentityKey; -use std::collections::HashMap; use crate::{ errors::ContractError, diff --git a/contracts/vesting/src/vesting.rs b/contracts/vesting/src/vesting.rs index 9760fe41e6..d8496392bc 100644 --- a/contracts/vesting/src/vesting.rs +++ b/contracts/vesting/src/vesting.rs @@ -5,7 +5,7 @@ use crate::storage::{ set_account_delegations, }; use config::defaults::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DENOM}; -use cosmwasm_std::{wasm_execute, Addr, Coin, Env, QuerierWrapper, Storage, Timestamp, Uint128}; +use cosmwasm_std::{wasm_execute, Addr, Coin, Env, Storage, Timestamp, Uint128}; use mixnet_contract::ExecuteMsg as MixnetExecuteMsg; use mixnet_contract::IdentityKey; use schemars::JsonSchema; @@ -18,8 +18,12 @@ pub trait VestingAccount { // To get spendable coins of a vesting account, first the total balance must // be retrieved and the locked tokens can be subtracted from the total balance. // Note, the spendable balance can be negative. - fn locked_coins(&self, block_time: Option, env: &Env, storage: &dyn Storage) - -> Coin; + fn locked_coins( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result; // Calculates the total spendable balance that can be sent to other accounts. fn spendable_coins( @@ -27,10 +31,18 @@ pub trait VestingAccount { block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Coin; + ) -> Result; - fn get_vested_coins(&self, block_time: Option, env: &Env) -> Coin; - fn get_vesting_coins(&self, block_time: Option, env: &Env) -> Coin; + fn get_vested_coins( + &self, + block_time: Option, + env: &Env, + ) -> Result; + fn get_vesting_coins( + &self, + block_time: Option, + env: &Env, + ) -> Result; fn get_start_time(&self) -> Timestamp; fn get_end_time(&self) -> Timestamp; @@ -135,15 +147,20 @@ impl PeriodicVestingAccount { self.address.clone() } - pub fn tokens_per_period(&self) -> u128 { - // Remainder tokens will be lumped into the last period. - self.coin.amount.u128() / NUM_VESTING_PERIODS as u128 + pub fn tokens_per_period(&self) -> Result { + let amount = self.coin.amount.u128(); + if amount < NUM_VESTING_PERIODS as u128 { + Err(ContractError::ImprobableVestingAmount(amount)) + } else { + // Remainder tokens will be lumped into the last period. + Ok(amount / NUM_VESTING_PERIODS as u128) + } } - fn get_next_vesting_period(&self, block_time: Timestamp) -> usize { + fn get_current_vesting_period(&self, block_time: Timestamp) -> usize { // Returns the index of the next vesting period. Unless the current time is somehow in the past or vesting has not started yet. // In case vesting is over it will always return NUM_VESTING_PERIODS. - match self + let period = match self .periods .iter() .map(|period| period.start_time) @@ -152,12 +169,10 @@ impl PeriodicVestingAccount { { Ok(u) => u, Err(u) => u, - } - } + }; - fn get_current_vesting_period(&self, block_time: Timestamp) -> usize { - if self.get_next_vesting_period(block_time) > 0 { - self.get_next_vesting_period(block_time) - 1 + if period > 0 { + period - 1 } else { 0 } @@ -308,11 +323,11 @@ impl VestingAccount for PeriodicVestingAccount { block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Coin { + ) -> Result { // Returns 0 in case of underflow. - Coin { + Ok(Coin { amount: Uint128( - self.get_vesting_coins(block_time, env) + self.get_vesting_coins(block_time, env)? .amount .u128() .saturating_sub( @@ -322,7 +337,7 @@ impl VestingAccount for PeriodicVestingAccount { ), ), denom: DENOM.to_string(), - } + }) } fn spendable_coins( @@ -330,46 +345,56 @@ impl VestingAccount for PeriodicVestingAccount { block_time: Option, env: &Env, storage: &dyn Storage, - ) -> Coin { - Coin { + ) -> Result { + Ok(Coin { amount: Uint128( self.get_balance(storage) .u128() - .saturating_sub(self.locked_coins(block_time, env, storage).amount.u128()), + .saturating_sub(self.locked_coins(block_time, env, storage)?.amount.u128()), ), denom: DENOM.to_string(), - } + }) } - fn get_vested_coins(&self, block_time: Option, env: &Env) -> Coin { + fn get_vested_coins( + &self, + block_time: Option, + env: &Env, + ) -> Result { let block_time = block_time.unwrap_or(env.block.time); let period = self.get_current_vesting_period(block_time); - match period { + let amount = match period { // We're in the first period, or the vesting has not started yet. 0 => Coin { amount: Uint128(0), denom: DENOM.to_string(), }, + // We always have 8 vesting periods, so periods 1-7 are special 1..=7 => Coin { - amount: Uint128(self.tokens_per_period() * period as u128), + amount: Uint128(self.tokens_per_period()? * period as u128), denom: DENOM.to_string(), }, _ => Coin { amount: self.coin.amount, denom: DENOM.to_string(), }, - } + }; + Ok(amount) } - fn get_vesting_coins(&self, block_time: Option, env: &Env) -> Coin { - Coin { + fn get_vesting_coins( + &self, + block_time: Option, + env: &Env, + ) -> Result { + Ok(Coin { amount: Uint128( self.get_original_vesting().amount.u128() - - self.get_vested_coins(block_time, env).amount.u128(), + - self.get_vested_coins(block_time, env)?.amount.u128(), ), denom: DENOM.to_string(), - } + }) } fn get_start_time(&self) -> Timestamp { @@ -411,16 +436,14 @@ fn ge(x: u64, y: u64) -> bool { x >= y } -pub fn populate_vesting_periods(start_time: u64, n: u64) -> Vec { - let mut periods = Vec::new(); - // There are eight 3 month periods in two years +pub fn populate_vesting_periods(start_time: u64, n: usize) -> Vec { + let mut periods = Vec::with_capacity(n as usize); for i in 0..n { let period = VestingPeriod { - start_time: start_time + i * VESTING_PERIOD, + start_time: start_time + i as u64 * VESTING_PERIOD, }; periods.push(period); } - periods.shrink_to_fit(); periods } @@ -466,17 +489,13 @@ mod tests { assert_eq!(account.periods.len(), 8); let current_period = account.get_current_vesting_period(Timestamp::from_seconds(0)); - let next_period = account.get_next_vesting_period(Timestamp::from_seconds(0)); assert_eq!(0, current_period); - assert_eq!(0, next_period); let block_time = Timestamp::from_seconds(account.start_time.seconds() + VESTING_PERIOD + 1); let current_period = account.get_current_vesting_period(block_time); - let next_period = account.get_next_vesting_period(block_time); assert_eq!(current_period, 1); - assert_eq!(next_period, 2); - let vested_coins = account.get_vested_coins(Some(block_time), &env); - let vesting_coins = account.get_vesting_coins(Some(block_time), &env); + let vested_coins = account.get_vested_coins(Some(block_time), &env).unwrap(); + let vesting_coins = account.get_vesting_coins(Some(block_time), &env).unwrap(); assert_eq!( vested_coins.amount, Uint128(account.get_original_vesting().amount.u128() / NUM_VESTING_PERIODS as u128) @@ -492,11 +511,9 @@ mod tests { let block_time = Timestamp::from_seconds(account.start_time.seconds() + 5 * VESTING_PERIOD + 1); let current_period = account.get_current_vesting_period(block_time); - let next_period = account.get_next_vesting_period(block_time); assert_eq!(current_period, 5); - assert_eq!(next_period, 6); - let vested_coins = account.get_vested_coins(Some(block_time), &env); - let vesting_coins = account.get_vesting_coins(Some(block_time), &env); + let vested_coins = account.get_vested_coins(Some(block_time), &env).unwrap(); + let vesting_coins = account.get_vesting_coins(Some(block_time), &env).unwrap(); assert_eq!( vested_coins.amount, Uint128(5 * account.get_original_vesting().amount.u128() / NUM_VESTING_PERIODS as u128)