Implement some more
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
use crate::errors::ContractError;
|
||||
use crate::messages::{ExecuteMsg, InitMsg, QueryMsg};
|
||||
use crate::vesting::VestingPeriod;
|
||||
use crate::storage::{get_account, set_account};
|
||||
use crate::vesting::{DelegationAccount, VestingAccount, VestingPeriod};
|
||||
use cosmwasm_std::{
|
||||
entry_point, to_binary, Addr, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
|
||||
entry_point, to_binary, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
|
||||
Timestamp,
|
||||
};
|
||||
use mixnet_contract::IdentityKey;
|
||||
|
||||
pub const NUM_VESTING_PERIODS: u64 = 8;
|
||||
pub const VESTING_PERIOD: u64 = 3 * 30 * 86400;
|
||||
pub const ADMIN_ADDRESS: &str = "admin";
|
||||
|
||||
/// Instantiate the contract.
|
||||
///
|
||||
@@ -17,9 +19,9 @@ pub const VESTING_PERIOD: u64 = 3 * 30 * 86400;
|
||||
/// `msg` is the contract initialization message, sort of like a constructor call.
|
||||
#[entry_point]
|
||||
pub fn instantiate(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
_deps: DepsMut,
|
||||
_env: Env,
|
||||
_info: MessageInfo,
|
||||
_msg: InitMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
Ok(Response::default())
|
||||
@@ -34,44 +36,70 @@ pub fn execute(
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::DelegateToMixnode { mix_identity } => {
|
||||
try_delegate_to_mixnode(mix_identity, env, deps)
|
||||
}
|
||||
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
|
||||
try_undelegate_from_mixnode(mix_identity, env, deps)
|
||||
}
|
||||
ExecuteMsg::DelegateToMixnode {
|
||||
mix_identity,
|
||||
delegate_addr,
|
||||
amount,
|
||||
} => try_delegate_to_mixnode(mix_identity, delegate_addr, amount, info, env, deps),
|
||||
ExecuteMsg::UndelegateFromMixnode {
|
||||
mix_identity,
|
||||
delegate_addr,
|
||||
} => try_undelegate_from_mixnode(mix_identity, delegate_addr, info, deps),
|
||||
ExecuteMsg::CreatePeriodicVestingAccount {
|
||||
address,
|
||||
coin,
|
||||
start_time,
|
||||
periods,
|
||||
} => try_create_periodic_vesting_account(address, coin, start_time, env, deps),
|
||||
} => try_create_periodic_vesting_account(address, coin, start_time, info, env, deps),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_delegate_to_mixnode(
|
||||
mix_identity: IdentityKey,
|
||||
delegate_addr: String,
|
||||
amount: Coin,
|
||||
info: MessageInfo,
|
||||
env: Env,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
unimplemented!()
|
||||
if info.sender != ADMIN_ADDRESS {
|
||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||
}
|
||||
let address = deps.api.addr_validate(&delegate_addr)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
account.try_delegate_to_mixnode(mix_identity, amount, env, deps)?;
|
||||
}
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
fn try_undelegate_from_mixnode(
|
||||
mix_identity: IdentityKey,
|
||||
env: Env,
|
||||
delegate_addr: String,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
unimplemented!()
|
||||
if info.sender != ADMIN_ADDRESS {
|
||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||
}
|
||||
let address = deps.api.addr_validate(&delegate_addr)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
account.try_undelegate_from_mixnode(mix_identity, deps)?;
|
||||
}
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
fn try_create_periodic_vesting_account(
|
||||
address: Addr,
|
||||
address: String,
|
||||
coin: Coin,
|
||||
start_time: Option<u64>,
|
||||
info: MessageInfo,
|
||||
env: Env,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
let mut deps = deps;
|
||||
if info.sender != ADMIN_ADDRESS {
|
||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||
}
|
||||
let address = deps.api.addr_validate(&address)?;
|
||||
let start_time = start_time.unwrap_or_else(|| env.block.time.seconds());
|
||||
let mut periods = Vec::new();
|
||||
// There are eight 3 month periods in two years
|
||||
@@ -86,87 +114,200 @@ fn try_create_periodic_vesting_account(
|
||||
coin,
|
||||
Timestamp::from_seconds(start_time),
|
||||
periods,
|
||||
);
|
||||
unimplemented!()
|
||||
&mut deps,
|
||||
)?;
|
||||
set_account(deps.storage, account)?;
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
|
||||
let query_res = match msg {
|
||||
QueryMsg::LockedCoins { block_time } => {
|
||||
to_binary(&try_get_locked_coins(block_time, env, deps)?)
|
||||
}
|
||||
QueryMsg::SpendableCoins { block_time } => {
|
||||
to_binary(&try_get_spendable_coins(block_time, env, deps)?)
|
||||
}
|
||||
QueryMsg::GetVestedCoins { block_time } => {
|
||||
to_binary(&try_get_vested_coins(block_time, env, deps)?)
|
||||
}
|
||||
QueryMsg::GetVestingCoins { block_time } => {
|
||||
to_binary(&try_get_vesting_coins(block_time, env, deps)?)
|
||||
}
|
||||
QueryMsg::GetStartTime {} => to_binary(&try_get_start_time(env, deps)?),
|
||||
QueryMsg::GetEndTime {} => to_binary(&try_get_end_time(env, deps)?),
|
||||
QueryMsg::GetOriginalVesting {} => to_binary(&try_get_original_vesting(env, deps)?),
|
||||
QueryMsg::GetDelegatedFree {} => to_binary(&try_get_delegated_free(env, deps)?),
|
||||
QueryMsg::GetDelegatedVesting {} => to_binary(&try_get_delegated_vesting(env, deps)?),
|
||||
QueryMsg::LockedCoins {
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
} => to_binary(&try_get_locked_coins(
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
env,
|
||||
deps,
|
||||
)?),
|
||||
QueryMsg::SpendableCoins {
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
} => to_binary(&try_get_spendable_coins(
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
env,
|
||||
deps,
|
||||
)?),
|
||||
QueryMsg::GetVestedCoins {
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
} => to_binary(&try_get_vested_coins(
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
env,
|
||||
deps,
|
||||
)?),
|
||||
QueryMsg::GetVestingCoins {
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
} => to_binary(&try_get_vesting_coins(
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
env,
|
||||
deps,
|
||||
)?),
|
||||
QueryMsg::GetStartTime {
|
||||
vesting_account_address,
|
||||
} => to_binary(&try_get_start_time(vesting_account_address, env, deps)?),
|
||||
QueryMsg::GetEndTime {
|
||||
vesting_account_address,
|
||||
} => to_binary(&try_get_end_time(vesting_account_address, env, deps)?),
|
||||
QueryMsg::GetOriginalVesting {
|
||||
vesting_account_address,
|
||||
} => to_binary(&try_get_original_vesting(
|
||||
vesting_account_address,
|
||||
env,
|
||||
deps,
|
||||
)?),
|
||||
QueryMsg::GetDelegatedFree {
|
||||
vesting_account_address,
|
||||
} => to_binary(&try_get_delegated_free(vesting_account_address, env, deps)?),
|
||||
QueryMsg::GetDelegatedVesting {
|
||||
vesting_account_address,
|
||||
} => to_binary(&try_get_delegated_vesting(
|
||||
vesting_account_address,
|
||||
env,
|
||||
deps,
|
||||
)?),
|
||||
};
|
||||
|
||||
Ok(query_res?)
|
||||
}
|
||||
|
||||
fn try_get_locked_coins(
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
env: Env,
|
||||
deps: Deps,
|
||||
) -> Result<Vec<Coin>, ContractError> {
|
||||
) -> Result<Coin, ContractError> {
|
||||
let block_time = block_time.unwrap_or(env.block.time);
|
||||
unimplemented!()
|
||||
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))
|
||||
} else {
|
||||
Err(ContractError::NoSuchAccount(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_get_spendable_coins(
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
env: Env,
|
||||
deps: Deps,
|
||||
) -> Result<Vec<Coin>, ContractError> {
|
||||
) -> Result<Coin, ContractError> {
|
||||
let block_time = block_time.unwrap_or(env.block.time);
|
||||
unimplemented!()
|
||||
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))
|
||||
} else {
|
||||
Err(ContractError::NoSuchAccount(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_get_vested_coins(
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
env: Env,
|
||||
deps: Deps,
|
||||
) -> Result<Vec<Coin>, ContractError> {
|
||||
) -> Result<Coin, ContractError> {
|
||||
let block_time = block_time.unwrap_or(env.block.time);
|
||||
unimplemented!()
|
||||
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))
|
||||
} else {
|
||||
Err(ContractError::NoSuchAccount(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_get_vesting_coins(
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
env: Env,
|
||||
deps: Deps,
|
||||
) -> Result<Vec<Coin>, ContractError> {
|
||||
) -> Result<Coin, ContractError> {
|
||||
let block_time = block_time.unwrap_or(env.block.time);
|
||||
unimplemented!()
|
||||
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))
|
||||
} else {
|
||||
Err(ContractError::NoSuchAccount(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_get_start_time(env: Env, deps: Deps) -> Result<Option<Timestamp>, ContractError> {
|
||||
unimplemented!()
|
||||
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)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.get_start_time())
|
||||
} else {
|
||||
Err(ContractError::NoSuchAccount(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_get_end_time(env: Env, deps: Deps) -> Result<Option<Timestamp>, ContractError> {
|
||||
unimplemented!()
|
||||
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)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.get_end_time())
|
||||
} else {
|
||||
Err(ContractError::NoSuchAccount(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_get_original_vesting(env: Env, deps: Deps) -> Result<Vec<Coin>, ContractError> {
|
||||
unimplemented!()
|
||||
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)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.get_original_vesting())
|
||||
} else {
|
||||
Err(ContractError::NoSuchAccount(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_get_delegated_free(env: Env, deps: Deps) -> Result<Vec<Coin>, ContractError> {
|
||||
unimplemented!()
|
||||
fn try_get_delegated_free(
|
||||
vesting_account_address: String,
|
||||
env: Env,
|
||||
deps: Deps,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let address = deps.api.addr_validate(&vesting_account_address)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.get_delegated_free(env, deps))
|
||||
} else {
|
||||
Err(ContractError::NoSuchAccount(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_get_delegated_vesting(env: Env, deps: Deps) -> Result<Vec<Coin>, ContractError> {
|
||||
unimplemented!()
|
||||
fn try_get_delegated_vesting(
|
||||
vesting_account_address: String,
|
||||
env: Env,
|
||||
deps: Deps,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let address = deps.api.addr_validate(&vesting_account_address)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.get_delegated_vesting(env, deps))
|
||||
} else {
|
||||
Err(ContractError::NoSuchAccount(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,8 @@ use thiserror::Error;
|
||||
pub enum ContractError {
|
||||
#[error("{0}")]
|
||||
Std(#[from] StdError),
|
||||
#[error("Account does not exist - {0}")]
|
||||
NoSuchAccount(String),
|
||||
#[error("Only admin can perform this action, {0} is not admin")]
|
||||
NotAdmin(String),
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::vesting::VestingPeriod;
|
||||
use crate::vesting::{self, VestingPeriod};
|
||||
use cosmwasm_std::{Addr, Coin, Timestamp};
|
||||
use mixnet_contract::IdentityKey;
|
||||
use schemars::JsonSchema;
|
||||
@@ -8,30 +8,56 @@ use serde::{Deserialize, Serialize};
|
||||
pub struct InitMsg {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
DelegateToMixnode {
|
||||
mix_identity: IdentityKey,
|
||||
delegate_addr: String,
|
||||
amount: Coin,
|
||||
},
|
||||
UndelegateFromMixnode {
|
||||
mix_identity: IdentityKey,
|
||||
delegate_addr: String,
|
||||
},
|
||||
CreatePeriodicVestingAccount {
|
||||
address: Addr,
|
||||
address: String,
|
||||
coin: Coin,
|
||||
start_time: Option<u64>,
|
||||
periods: Option<Vec<VestingPeriod>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum QueryMsg {
|
||||
LockedCoins { block_time: Option<Timestamp> },
|
||||
SpendableCoins { block_time: Option<Timestamp> },
|
||||
GetVestedCoins { block_time: Option<Timestamp> },
|
||||
GetVestingCoins { block_time: Option<Timestamp> },
|
||||
GetStartTime,
|
||||
GetEndTime,
|
||||
GetOriginalVesting,
|
||||
GetDelegatedFree,
|
||||
GetDelegatedVesting,
|
||||
LockedCoins {
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
},
|
||||
SpendableCoins {
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
},
|
||||
GetVestedCoins {
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
},
|
||||
GetVestingCoins {
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
},
|
||||
GetStartTime {
|
||||
vesting_account_address: String,
|
||||
},
|
||||
GetEndTime {
|
||||
vesting_account_address: String,
|
||||
},
|
||||
GetOriginalVesting {
|
||||
vesting_account_address: String,
|
||||
},
|
||||
GetDelegatedFree {
|
||||
vesting_account_address: String,
|
||||
},
|
||||
GetDelegatedVesting {
|
||||
vesting_account_address: String,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,51 +1,65 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use cosmwasm_std::{Order, StdResult, Storage, Uint128};
|
||||
use cosmwasm_storage::{
|
||||
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
|
||||
Singleton,
|
||||
};
|
||||
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::vesting::{DelegationData, PeriodicVestingAccount};
|
||||
use crate::{
|
||||
errors::ContractError,
|
||||
vesting::{DelegationData, PeriodicVestingAccount},
|
||||
};
|
||||
// storage prefixes
|
||||
// all of them must be unique and presumably not be a prefix of a different one
|
||||
// keeping them as short as possible is also desirable as they are part of each stored key
|
||||
// it's not as important for singletons, but is a nice optimisation for buckets
|
||||
|
||||
// singletons
|
||||
|
||||
// buckets
|
||||
const PREFIX_ACCOUNTS: &[u8] = b"ac";
|
||||
const PREFIX_ACCOUNT_DELEGATIONS: &[u8] = b"ad";
|
||||
const PREFIX_ACCOUNT_BALANCE: &[u8] = b"ab";
|
||||
// Contract-level stuff
|
||||
|
||||
pub fn accounts_mut(storage: &mut dyn Storage) -> Bucket<PeriodicVestingAccount> {
|
||||
fn accounts_mut(storage: &mut dyn Storage) -> Bucket<PeriodicVestingAccount> {
|
||||
bucket(storage, PREFIX_ACCOUNTS)
|
||||
}
|
||||
|
||||
pub fn accounts(storage: &dyn Storage) -> ReadonlyBucket<PeriodicVestingAccount> {
|
||||
fn accounts(storage: &dyn Storage) -> ReadonlyBucket<PeriodicVestingAccount> {
|
||||
bucket_read(storage, PREFIX_ACCOUNTS)
|
||||
}
|
||||
|
||||
pub fn account_delegations_mut(
|
||||
fn account_delegations_mut(
|
||||
storage: &mut dyn Storage,
|
||||
) -> Bucket<HashMap<IdentityKey, Vec<DelegationData>>> {
|
||||
bucket(storage, PREFIX_ACCOUNT_DELEGATIONS)
|
||||
}
|
||||
|
||||
pub fn account_delegations(
|
||||
fn account_delegations(
|
||||
storage: &dyn Storage,
|
||||
) -> ReadonlyBucket<HashMap<IdentityKey, Vec<DelegationData>>> {
|
||||
bucket_read(storage, PREFIX_ACCOUNT_DELEGATIONS)
|
||||
}
|
||||
|
||||
pub fn get_account(storage: &dyn Storage, address: &str) -> Option<PeriodicVestingAccount> {
|
||||
fn account_balance(storage: &dyn Storage) -> ReadonlyBucket<Uint128> {
|
||||
bucket_read(storage, PREFIX_ACCOUNT_BALANCE)
|
||||
}
|
||||
|
||||
fn account_balance_mut(storage: &mut dyn Storage) -> Bucket<Uint128> {
|
||||
bucket(storage, PREFIX_ACCOUNT_BALANCE)
|
||||
}
|
||||
|
||||
pub fn get_account(storage: &dyn Storage, address: &Addr) -> Option<PeriodicVestingAccount> {
|
||||
// Due to using may_load this should be safe to unwrap
|
||||
accounts(storage).may_load(address.as_bytes()).unwrap()
|
||||
}
|
||||
|
||||
pub fn set_account(
|
||||
storage: &mut dyn Storage,
|
||||
account: PeriodicVestingAccount,
|
||||
) -> Result<(), ContractError> {
|
||||
Ok(accounts_mut(storage).save(account.address().as_bytes(), &account)?)
|
||||
}
|
||||
|
||||
pub fn get_account_delegations(
|
||||
storage: &dyn Storage,
|
||||
address: &str,
|
||||
@@ -58,12 +72,26 @@ pub fn get_account_delegations(
|
||||
|
||||
pub fn set_account_delegations(
|
||||
storage: &mut dyn Storage,
|
||||
address: &str,
|
||||
address: &Addr,
|
||||
delegations: HashMap<IdentityKey, Vec<DelegationData>>,
|
||||
) -> StdResult<()> {
|
||||
account_delegations_mut(storage).save(address.as_bytes(), &delegations)
|
||||
}
|
||||
|
||||
pub fn get_account_balance(storage: &dyn Storage, address: &Addr) -> Option<Uint128> {
|
||||
// Due to using may_load this should be safe to unwrap
|
||||
account_balance(storage)
|
||||
.may_load(address.as_bytes())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn set_account_balance(
|
||||
storage: &mut dyn Storage,
|
||||
address: &Addr,
|
||||
balance: Uint128,
|
||||
) -> StdResult<()> {
|
||||
account_balance_mut(storage).save(address.as_bytes(), &balance)
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
use crate::contract::{NUM_VESTING_PERIODS, VESTING_PERIOD};
|
||||
use crate::errors::ContractError;
|
||||
use crate::storage::{get_account_delegations, set_account_delegations};
|
||||
use crate::storage::{
|
||||
get_account_balance, get_account_delegations, set_account_balance, set_account_delegations,
|
||||
};
|
||||
use config::defaults::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DENOM};
|
||||
use cosmwasm_std::{Addr, Coin, DepsMut, Env, Timestamp, Uint128};
|
||||
use cosmwasm_std::{Addr, Coin, Deps, DepsMut, Env, Timestamp, Uint128};
|
||||
use mixnet_contract::ExecuteMsg as MixnetExecuteMsg;
|
||||
use mixnet_contract::IdentityKey;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
trait VestingAccount {
|
||||
pub trait VestingAccount {
|
||||
// locked_coins returns the set of coins that are not spendable (i.e. locked),
|
||||
// defined as the vesting coins that are not delegated.
|
||||
//
|
||||
// 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: Timestamp, env: Env, deps: DepsMut) -> Coin;
|
||||
fn locked_coins(&self, block_time: Timestamp, env: Env, deps: Deps) -> Coin;
|
||||
|
||||
// Calculates the total spendable balance that can be sent to other accounts.
|
||||
fn spendable_coins(&self, block_time: Timestamp, env: Env, deps: DepsMut) -> Coin;
|
||||
fn spendable_coins(&self, block_time: Timestamp, env: Env, deps: Deps) -> Coin;
|
||||
|
||||
fn get_vested_coins(&self, block_time: Timestamp) -> Coin;
|
||||
fn get_vesting_coins(&self, block_time: Timestamp) -> Coin;
|
||||
@@ -28,17 +30,16 @@ trait VestingAccount {
|
||||
fn get_end_time(&self) -> Timestamp;
|
||||
|
||||
fn get_original_vesting(&self) -> Coin;
|
||||
fn get_delegated_free(&self, env: Env, deps: DepsMut) -> Coin;
|
||||
fn get_delegated_vesting(&self, env: Env, deps: DepsMut) -> Coin;
|
||||
fn get_delegated_free(&self, env: Env, deps: Deps) -> Coin;
|
||||
fn get_delegated_vesting(&self, env: Env, deps: Deps) -> Coin;
|
||||
}
|
||||
|
||||
trait DelegationAccount {
|
||||
// TODO: Add delegate on behalf messages in the mixnet contract.
|
||||
// As the vesting account is delegating on behalf of the token holders/vesters
|
||||
pub trait DelegationAccount {
|
||||
fn try_delegate_to_mixnode(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
env: Env,
|
||||
deps: DepsMut,
|
||||
) -> Result<(), ContractError>;
|
||||
|
||||
@@ -61,7 +62,11 @@ trait DelegationAccount {
|
||||
) -> Result<(), ContractError>;
|
||||
// track_undelegation performs internal vesting accounting necessary when a
|
||||
// vesting account performs an undelegation.
|
||||
fn track_undelegation(&self, mix_identity: IdentityKey, deps: DepsMut);
|
||||
fn track_undelegation(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
deps: DepsMut,
|
||||
) -> Result<(), ContractError>;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
@@ -95,14 +100,21 @@ impl PeriodicVestingAccount {
|
||||
coin: Coin,
|
||||
start_time: Timestamp,
|
||||
periods: Vec<VestingPeriod>,
|
||||
// TODO: Store coins into contract to keep track of current balance.
|
||||
) -> Self {
|
||||
PeriodicVestingAccount {
|
||||
deps: &mut DepsMut,
|
||||
) -> Result<Self, ContractError> {
|
||||
let amount = coin.amount;
|
||||
let account = PeriodicVestingAccount {
|
||||
address,
|
||||
start_time,
|
||||
periods,
|
||||
coin,
|
||||
}
|
||||
};
|
||||
set_account_balance(deps.storage, &account.address, amount)?;
|
||||
Ok(account)
|
||||
}
|
||||
|
||||
pub fn address(&self) -> Addr {
|
||||
self.address.clone()
|
||||
}
|
||||
|
||||
pub fn tokens_per_period(&self) -> u128 {
|
||||
@@ -129,12 +141,7 @@ impl PeriodicVestingAccount {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_delegated_with_op(
|
||||
&self,
|
||||
op: &dyn Fn(u64, u64) -> bool,
|
||||
env: Env,
|
||||
deps: DepsMut,
|
||||
) -> Coin {
|
||||
fn get_delegated_with_op(&self, op: &dyn Fn(u64, u64) -> bool, env: Env, deps: Deps) -> Coin {
|
||||
let period = self.get_current_vesting_period(env.block.time);
|
||||
if period == 0 {
|
||||
return Coin {
|
||||
@@ -170,8 +177,8 @@ impl PeriodicVestingAccount {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_balance(&self) -> Coin {
|
||||
unimplemented!()
|
||||
fn get_balance(&self, deps: Deps) -> Uint128 {
|
||||
get_account_balance(deps.storage, &self.address).unwrap_or(Uint128(0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,15 +187,17 @@ impl DelegationAccount for PeriodicVestingAccount {
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
coin: Coin,
|
||||
env: Env,
|
||||
deps: DepsMut,
|
||||
) -> Result<(), ContractError> {
|
||||
let querier = deps.querier;
|
||||
let msg = MixnetExecuteMsg::DelegateToMixnodeOnBehalf {
|
||||
mix_identity,
|
||||
mix_identity: mix_identity.clone(),
|
||||
delegate_addr: self.address.clone(),
|
||||
coin,
|
||||
coin: coin.clone(),
|
||||
};
|
||||
querier.query_wasm_smart(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg)?;
|
||||
self.track_delegation(env.block.time, mix_identity, coin, deps)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -199,10 +208,11 @@ impl DelegationAccount for PeriodicVestingAccount {
|
||||
) -> Result<(), ContractError> {
|
||||
let querier = deps.querier;
|
||||
let msg = MixnetExecuteMsg::UnDelegateFromMixnodeOnBehalf {
|
||||
mix_identity,
|
||||
mix_identity: mix_identity.clone(),
|
||||
delegate_addr: self.address.clone(),
|
||||
};
|
||||
querier.query_wasm_smart(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg)?;
|
||||
self.track_undelegation(mix_identity, deps)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -220,25 +230,36 @@ impl DelegationAccount for PeriodicVestingAccount {
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
let delegation = delegations.entry(mix_identity).or_insert_with(Vec::new);
|
||||
let delegation = delegations
|
||||
.entry(mix_identity)
|
||||
.or_insert_with(Vec::new);
|
||||
delegation.push(DelegationData {
|
||||
amount: delegation_amount.amount,
|
||||
block_time,
|
||||
});
|
||||
set_account_delegations(deps.storage, self.address.as_str(), delegations)?;
|
||||
set_account_delegations(deps.storage, &self.address, delegations)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn track_undelegation(&self, mix_identity: IdentityKey, deps: DepsMut) {
|
||||
fn track_undelegation(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
deps: DepsMut,
|
||||
) -> Result<(), ContractError> {
|
||||
// This has to exist in storage at this point.
|
||||
let mut delegations = get_account_delegations(deps.storage, self.address.as_str()).unwrap();
|
||||
// Since we're always removing the entire delegation we can just drop the key
|
||||
delegations.remove(&mix_identity);
|
||||
Ok(set_account_delegations(
|
||||
deps.storage,
|
||||
&self.address,
|
||||
delegations,
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl VestingAccount for PeriodicVestingAccount {
|
||||
fn locked_coins(&self, block_time: Timestamp, env: Env, deps: DepsMut) -> Coin {
|
||||
fn locked_coins(&self, block_time: Timestamp, env: Env, deps: Deps) -> Coin {
|
||||
// Returns 0 in case of underflow.
|
||||
Coin {
|
||||
amount: Uint128(
|
||||
@@ -251,11 +272,10 @@ impl VestingAccount for PeriodicVestingAccount {
|
||||
}
|
||||
}
|
||||
|
||||
fn spendable_coins(&self, block_time: Timestamp, env: Env, deps: DepsMut) -> Coin {
|
||||
fn spendable_coins(&self, block_time: Timestamp, env: Env, deps: Deps) -> Coin {
|
||||
Coin {
|
||||
amount: Uint128(
|
||||
self.get_balance()
|
||||
.amount
|
||||
self.get_balance(deps)
|
||||
.u128()
|
||||
.saturating_sub(self.locked_coins(block_time, env, deps).amount.u128()),
|
||||
),
|
||||
@@ -305,11 +325,11 @@ impl VestingAccount for PeriodicVestingAccount {
|
||||
self.coin.clone()
|
||||
}
|
||||
|
||||
fn get_delegated_free(&self, env: Env, deps: DepsMut) -> Coin {
|
||||
fn get_delegated_free(&self, env: Env, deps: Deps) -> Coin {
|
||||
self.get_delegated_with_op(<, env, deps)
|
||||
}
|
||||
|
||||
fn get_delegated_vesting(&self, env: Env, deps: DepsMut) -> Coin {
|
||||
fn get_delegated_vesting(&self, env: Env, deps: Deps) -> Coin {
|
||||
self.get_delegated_with_op(&ge, env, deps)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user