This commit is contained in:
Drazen Urch
2021-11-18 20:46:08 +01:00
parent abf97e7be8
commit d052fb80ca
6 changed files with 92 additions and 78 deletions
+18 -22
View File
@@ -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<Response, ContractError> {
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<QueryResponse, Contr
)?),
QueryMsg::GetStartTime {
vesting_account_address,
} => 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<QueryResponse, Contr
Ok(query_res?)
}
fn try_get_locked_coins(
pub fn try_get_locked_coins(
vesting_account_address: String,
block_time: Option<Timestamp>,
env: Env,
@@ -236,13 +235,13 @@ fn try_get_locked_coins(
) -> Result<Coin, ContractError> {
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<Timestamp>,
env: Env,
@@ -250,13 +249,13 @@ fn try_get_spendable_coins(
) -> Result<Coin, ContractError> {
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<Timestamp>,
env: Env,
@@ -264,13 +263,13 @@ fn try_get_vested_coins(
) -> Result<Coin, ContractError> {
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<Timestamp>,
env: Env,
@@ -278,15 +277,14 @@ fn try_get_vesting_coins(
) -> Result<Coin, ContractError> {
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<Timestamp, ContractError> {
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<Timestamp, ContractError> {
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<Coin, ContractError> {
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<Timestamp>,
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<Timestamp>,
vesting_account_address: String,
env: Env,
+7 -3
View File
@@ -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),
}
+2 -2
View File
@@ -1,6 +1,6 @@
mod contract;
pub mod contract;
mod errors;
mod messages;
pub mod messages;
mod storage;
mod support;
mod vesting;
+1 -2
View File
@@ -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};
-2
View File
@@ -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,
+64 -47
View File
@@ -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<Timestamp>, env: &Env, storage: &dyn Storage)
-> Coin;
fn locked_coins(
&self,
block_time: Option<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError>;
// 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<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Coin;
) -> Result<Coin, ContractError>;
fn get_vested_coins(&self, block_time: Option<Timestamp>, env: &Env) -> Coin;
fn get_vesting_coins(&self, block_time: Option<Timestamp>, env: &Env) -> Coin;
fn get_vested_coins(
&self,
block_time: Option<Timestamp>,
env: &Env,
) -> Result<Coin, ContractError>;
fn get_vesting_coins(
&self,
block_time: Option<Timestamp>,
env: &Env,
) -> Result<Coin, ContractError>;
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<u128, ContractError> {
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<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Coin {
) -> Result<Coin, ContractError> {
// 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<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Coin {
Coin {
) -> Result<Coin, ContractError> {
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<Timestamp>, env: &Env) -> Coin {
fn get_vested_coins(
&self,
block_time: Option<Timestamp>,
env: &Env,
) -> Result<Coin, ContractError> {
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<Timestamp>, env: &Env) -> Coin {
Coin {
fn get_vesting_coins(
&self,
block_time: Option<Timestamp>,
env: &Env,
) -> Result<Coin, ContractError> {
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<VestingPeriod> {
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<VestingPeriod> {
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)