Stubbing away

This commit is contained in:
Drazen Urch
2021-11-16 15:51:34 +01:00
committed by Drazen Urch
parent 0ae4e7bdfb
commit 7a5bee30ce
8 changed files with 826 additions and 54 deletions
+10 -2
View File
@@ -1,10 +1,9 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::NodeRewardParams;
use crate::StateParams;
use crate::{Gateway, IdentityKey, MixNode};
use cosmwasm_std::Addr;
use cosmwasm_std::{Addr, Coin};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -59,6 +58,15 @@ pub enum ExecuteMsg {
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
},
DelegateToMixnodeOnBehalf {
mix_identity: IdentityKey,
delegate_addr: Addr,
coin: Coin,
},
UnDelegateFromMixnodeOnBehalf {
mix_identity: IdentityKey,
delegate_addr: Addr,
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+21
View File
@@ -141,6 +141,27 @@ pub fn execute(
ExecuteMsg::FinishMixnodeRewarding {
rewarding_interval_nonce,
} => transactions::try_finish_mixnode_rewarding(deps, info, rewarding_interval_nonce),
ExecuteMsg::DelegateToMixnodeOnBehalf {
mix_identity,
delegate_addr,
coin
} => transactions::try_delegate_to_mixnode_on_behalf(
deps,
env,
info,
mix_identity,
delegate_addr,
coin
),
ExecuteMsg::UnDelegateFromMixnodeOnBehalf {
mix_identity,
delegate_addr,
} => transactions::try_remove_delegation_from_mixnode_on_behalf(
deps,
info,
mix_identity,
delegate_addr,
),
}
}
+53 -7
View File
@@ -6,7 +6,8 @@ use crate::queries;
use crate::storage::*;
use config::defaults::DENOM;
use cosmwasm_std::{
attr, coins, BankMsg, Coin, Decimal, DepsMut, Env, MessageInfo, Response, StdResult, Uint128,
attr, coins, Addr, BankMsg, Coin, Decimal, DepsMut, Env, MessageInfo, Response, StdResult,
Uint128,
};
use cosmwasm_storage::ReadonlyBucket;
use mixnet_contract::mixnode::NodeRewardParams;
@@ -656,6 +657,34 @@ pub(crate) fn try_delegate_to_mixnode(
// check if the delegation contains any funds of the appropriate denomination
validate_delegation_stake(&info.funds)?;
let delegate_addr = info.sender.clone();
let amount = info.funds[0].amount;
_try_delegate_to_mixnode(deps, env, mix_identity, &delegate_addr, amount)
}
pub(crate) fn try_delegate_to_mixnode_on_behalf(
deps: DepsMut,
env: Env,
_info: MessageInfo,
mix_identity: IdentityKey,
delegate_addr: Addr,
coin: Coin,
) -> Result<Response, ContractError> {
// check if the delegation contains any funds of the appropriate denomination
validate_delegation_stake(&[coin.clone()])?;
let amount = coin.amount;
_try_delegate_to_mixnode(deps, env, mix_identity, &delegate_addr, amount)
}
fn _try_delegate_to_mixnode(
deps: DepsMut,
env: Env,
mix_identity: IdentityKey,
delegate_addr: &Addr,
amount: Uint128,
) -> Result<Response, ContractError> {
// check if the target node actually exists
let mut current_bond = match mixnodes_read(deps.storage).load(mix_identity.as_bytes()) {
Ok(bond) => bond,
@@ -666,14 +695,12 @@ pub(crate) fn try_delegate_to_mixnode(
}
};
let amount = info.funds[0].amount;
// update total_delegation of this node
current_bond.total_delegation.amount += info.funds[0].amount;
current_bond.total_delegation.amount += amount;
mixnodes(deps.storage).save(mix_identity.as_bytes(), &current_bond)?;
let mut delegation_bucket = mix_delegations(deps.storage, &mix_identity);
let sender_bytes = info.sender.as_bytes();
let sender_bytes = delegate_addr.as_bytes();
// write the delegation
let new_amount = match delegation_bucket.may_load(sender_bytes)? {
@@ -684,7 +711,7 @@ pub(crate) fn try_delegate_to_mixnode(
let new_delegation = RawDelegationData::new(new_amount, env.block.height);
delegation_bucket.save(sender_bytes, &new_delegation)?;
reverse_mix_delegations(deps.storage, &info.sender).save(mix_identity.as_bytes(), &())?;
reverse_mix_delegations(deps.storage, delegate_addr).save(mix_identity.as_bytes(), &())?;
Ok(Response::default())
}
@@ -693,9 +720,28 @@ pub(crate) fn try_remove_delegation_from_mixnode(
deps: DepsMut,
info: MessageInfo,
mix_identity: IdentityKey,
) -> Result<Response, ContractError> {
let sender = info.sender.clone();
_try_remove_delegation_from_mixnode(deps, info, mix_identity, &sender)
}
pub(crate) fn try_remove_delegation_from_mixnode_on_behalf(
deps: DepsMut,
info: MessageInfo,
mix_identity: IdentityKey,
delegate_addr: Addr,
) -> Result<Response, ContractError> {
_try_remove_delegation_from_mixnode(deps, info, mix_identity, &delegate_addr)
}
fn _try_remove_delegation_from_mixnode(
deps: DepsMut,
info: MessageInfo,
mix_identity: IdentityKey,
delegate_addr: &Addr,
) -> Result<Response, ContractError> {
let mut delegation_bucket = mix_delegations(deps.storage, &mix_identity);
let sender_bytes = info.sender.as_bytes();
let sender_bytes = delegate_addr.as_bytes();
match delegation_bucket.may_load(sender_bytes)? {
Some(delegation) => {
// remove delegation from the buckets
+25 -11
View File
@@ -6,6 +6,10 @@ use cosmwasm_std::{
Timestamp,
};
use mixnet_contract::IdentityKey;
pub const NUM_VESTING_PERIODS: u64 = 8;
pub const VESTING_PERIOD: u64 = 3 * 30 * 86400;
/// Instantiate the contract.
///
/// `deps` contains Storage, API and Querier
@@ -38,10 +42,10 @@ pub fn execute(
}
ExecuteMsg::CreatePeriodicVestingAccount {
address,
coins,
coin,
start_time,
periods,
} => try_create_periodic_vesting_account(address, coins, start_time, periods, env, deps),
} => try_create_periodic_vesting_account(address, coin, start_time, env, deps),
}
}
@@ -63,15 +67,26 @@ fn try_undelegate_from_mixnode(
fn try_create_periodic_vesting_account(
address: Addr,
coins: Vec<Coin>,
coin: Coin,
start_time: Option<u64>,
periods: Option<Vec<VestingPeriod>>,
env: Env,
deps: DepsMut,
) -> Result<Response, ContractError> {
let start_time = start_time.unwrap_or_else(|| env.block.time.seconds());
let periods = periods.unwrap_or_else(|| vec![VestingPeriod::default(); 8]);
let account = crate::vesting::PeriodicVestingAccount::new(address, coins, start_time, periods);
let mut periods = Vec::new();
// There are eight 3 month periods in two years
for i in 0..(NUM_VESTING_PERIODS - 1) {
let period = VestingPeriod {
start_time: start_time + i * VESTING_PERIOD,
};
periods.push(period);
}
let account = crate::vesting::PeriodicVestingAccount::new(
address,
coin,
Timestamp::from_seconds(start_time),
periods,
);
unimplemented!()
}
@@ -105,8 +120,7 @@ fn try_get_locked_coins(
env: Env,
deps: Deps,
) -> Result<Vec<Coin>, ContractError> {
let block_time = block_time.unwrap_or_else(|| env.block.time);
let block_time = block_time.unwrap_or(env.block.time);
unimplemented!()
}
@@ -115,7 +129,7 @@ fn try_get_spendable_coins(
env: Env,
deps: Deps,
) -> Result<Vec<Coin>, ContractError> {
let block_time = block_time.unwrap_or_else(|| env.block.time);
let block_time = block_time.unwrap_or(env.block.time);
unimplemented!()
}
@@ -124,7 +138,7 @@ fn try_get_vested_coins(
env: Env,
deps: Deps,
) -> Result<Vec<Coin>, ContractError> {
let block_time = block_time.unwrap_or_else(|| env.block.time);
let block_time = block_time.unwrap_or(env.block.time);
unimplemented!()
}
@@ -133,7 +147,7 @@ fn try_get_vesting_coins(
env: Env,
deps: Deps,
) -> Result<Vec<Coin>, ContractError> {
let block_time = block_time.unwrap_or_else(|| env.block.time);
let block_time = block_time.unwrap_or(env.block.time);
unimplemented!()
}
+1
View File
@@ -2,3 +2,4 @@ mod contract;
mod errors;
mod messages;
mod vesting;
mod storage;
+2 -2
View File
@@ -1,6 +1,6 @@
use crate::vesting::VestingPeriod;
use cosmwasm_std::{Addr, Coin, Timestamp};
use mixnet_contract::IdentityKey;
use mixnet_contract::{IdentityKey};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -17,7 +17,7 @@ pub enum ExecuteMsg {
},
CreatePeriodicVestingAccount {
address: Addr,
coins: Vec<Coin>,
coin: Coin,
start_time: Option<u64>,
periods: Option<Vec<VestingPeriod>>,
},
+437
View File
@@ -0,0 +1,437 @@
// 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 mixnet_contract::IdentityKey;
use std::collections::HashMap;
use crate::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";
// Contract-level stuff
pub fn accounts_mut(storage: &mut dyn Storage) -> Bucket<PeriodicVestingAccount> {
bucket(storage, PREFIX_ACCOUNTS)
}
pub fn accounts(storage: &dyn Storage) -> ReadonlyBucket<PeriodicVestingAccount> {
bucket_read(storage, PREFIX_ACCOUNTS)
}
pub fn account_delegations_mut(
storage: &mut dyn Storage,
) -> Bucket<HashMap<IdentityKey, Vec<DelegationData>>> {
bucket(storage, PREFIX_ACCOUNT_DELEGATIONS)
}
pub 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> {
// Due to using may_load this should be safe to unwrap
accounts(storage).may_load(address.as_bytes()).unwrap()
}
pub fn get_account_delegations(
storage: &dyn Storage,
address: &str,
) -> Option<HashMap<IdentityKey, Vec<DelegationData>>> {
// Due to using may_load this should be safe to unwrap
account_delegations(storage)
.may_load(address.as_bytes())
.unwrap()
}
pub fn set_account_delegations(
storage: &mut dyn Storage,
address: &str,
delegations: HashMap<IdentityKey, Vec<DelegationData>>,
) -> StdResult<()> {
account_delegations_mut(storage).save(address.as_bytes(), &delegations)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::helpers::identity_and_owner_to_bytes;
use crate::support::tests::helpers::{
gateway_bond_fixture, gateway_fixture, mix_node_fixture, mixnode_bond_fixture,
raw_delegation_fixture,
};
use config::defaults::DENOM;
use cosmwasm_std::testing::{mock_dependencies, MockStorage};
use cosmwasm_std::{coin, Addr, Uint128};
use mixnet_contract::{Gateway, MixNode};
#[test]
fn mixnode_single_read_retrieval() {
let mut storage = MockStorage::new();
let bond1 = mixnode_bond_fixture();
let bond2 = mixnode_bond_fixture();
mixnodes(&mut storage).save(b"bond1", &bond1).unwrap();
mixnodes(&mut storage).save(b"bond2", &bond2).unwrap();
let res1 = mixnodes_read(&storage).load(b"bond1").unwrap();
let res2 = mixnodes_read(&storage).load(b"bond2").unwrap();
assert_eq!(bond1, res1);
assert_eq!(bond2, res2);
}
#[test]
fn reading_mixnode_bond() {
let mut storage = MockStorage::new();
let node_owner: Addr = Addr::unchecked("node-owner");
let node_identity: IdentityKey = "nodeidentity".into();
// produces an error if target mixnode doesn't exist
let res = read_mixnode_bond(&storage, node_owner.as_bytes());
assert!(res.is_err());
// returns appropriate value otherwise
let bond_value = 1000;
let mixnode_bond = MixNodeBond {
bond_amount: coin(bond_value, DENOM),
total_delegation: coin(0, DENOM),
owner: node_owner.clone(),
layer: Layer::One,
block_height: 12_345,
mix_node: MixNode {
identity_key: node_identity.clone(),
..mix_node_fixture()
},
profit_margin_percent: Some(10),
};
mixnodes(&mut storage)
.save(node_identity.as_bytes(), &mixnode_bond)
.unwrap();
assert_eq!(
Uint128(bond_value),
read_mixnode_bond(&storage, node_identity.as_bytes()).unwrap()
);
}
#[test]
fn all_mixnode_delegations_read_retrieval() {
let mut deps = mock_dependencies(&[]);
let node_identity1: IdentityKey = "foo1".into();
let delegation_owner1 = Addr::unchecked("bar1");
let node_identity2: IdentityKey = "foo2".into();
let delegation_owner2 = Addr::unchecked("bar2");
let raw_delegation1 = RawDelegationData::new(1u128.into(), 1000);
let raw_delegation2 = RawDelegationData::new(2u128.into(), 2000);
mix_delegations(&mut deps.storage, &node_identity1)
.save(delegation_owner1.as_bytes(), &raw_delegation1)
.unwrap();
mix_delegations(&mut deps.storage, &node_identity2)
.save(delegation_owner2.as_bytes(), &raw_delegation2)
.unwrap();
let res1 = all_mix_delegations_read::<RawDelegationData>(&deps.storage)
.load(&*identity_and_owner_to_bytes(
&node_identity1,
&delegation_owner1,
))
.unwrap();
let res2 = all_mix_delegations_read::<RawDelegationData>(&deps.storage)
.load(&*identity_and_owner_to_bytes(
&node_identity2,
&delegation_owner2,
))
.unwrap();
assert_eq!(raw_delegation1, res1);
assert_eq!(raw_delegation2, res2);
}
#[cfg(test)]
mod increasing_mix_delegated_stakes {
use super::*;
use crate::queries::query_mixnode_delegations_paged;
use cosmwasm_std::testing::mock_dependencies;
#[test]
fn when_there_are_no_delegations() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
42,
)
.unwrap();
// there was no increase
assert!(total_increase.is_zero());
// there are no 'new' delegations magically added
assert!(
query_mixnode_delegations_paged(deps.as_ref(), node_identity, None, None)
.unwrap()
.delegations
.is_empty()
)
}
#[test]
fn when_there_is_a_single_delegation() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
let delegator_address = Addr::unchecked("bob");
mix_delegations(&mut deps.storage, &node_identity)
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
assert_eq!(Uint128(1), total_increase);
// amount is incremented, block height remains the same
assert_eq!(
RawDelegationData::new(1001u128.into(), 42),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
)
}
#[test]
fn when_there_is_a_single_delegation_depending_on_blockstamp() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
let delegator_address = Addr::unchecked("bob");
mix_delegations(&mut deps.storage, &node_identity)
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING - 1,
)
.unwrap();
// there was no increase
assert!(total_increase.is_zero());
// amount is not incremented
assert_eq!(
RawDelegationData::new(1000u128.into(), delegation_blockstamp),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
);
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
// there is an increase now, that the lock period has passed
assert_eq!(Uint128(1), total_increase);
// amount is incremented
assert_eq!(
RawDelegationData::new(1001u128.into(), delegation_blockstamp),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
)
}
#[test]
fn when_there_are_multiple_delegations() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
for i in 0..100 {
let delegator_address = Addr::unchecked(format!("address{}", i));
mix_delegations(&mut deps.storage, &node_identity)
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
}
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
assert_eq!(Uint128(100), total_increase);
for i in 0..100 {
let delegator_address = Addr::unchecked(format!("address{}", i));
assert_eq!(
raw_delegation_fixture(1001),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
)
}
}
#[test]
fn when_there_are_more_delegations_than_page_size() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
let delegator_address = Addr::unchecked(format!("address{}", i));
mix_delegations(&mut deps.storage, &node_identity)
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
}
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
assert_eq!(
Uint128(queries::DELEGATION_PAGE_MAX_LIMIT as u128 * 10),
total_increase
);
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
let delegator_address = Addr::unchecked(format!("address{}", i));
assert_eq!(
raw_delegation_fixture(1001),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
)
}
}
}
#[cfg(test)]
mod reverse_mix_delegations {
use super::*;
use crate::support::tests::helpers;
#[test]
fn reverse_mix_delegation_exists() {
let mut deps = helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
let delegation_owner = Addr::unchecked("bar");
reverse_mix_delegations(&mut deps.storage, &delegation_owner)
.save(node_identity.as_bytes(), &())
.unwrap();
assert!(
reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner)
.may_load(node_identity.as_bytes())
.unwrap()
.is_some(),
);
}
#[test]
fn reverse_mix_delegation_returns_none_if_delegation_doesnt_exist() {
let mut deps = helpers::init_contract();
let node_identity1: IdentityKey = "foo1".into();
let node_identity2: IdentityKey = "foo2".into();
let delegation_owner1 = Addr::unchecked("bar");
let delegation_owner2 = Addr::unchecked("bar2");
assert!(
reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1)
.may_load(node_identity1.as_bytes())
.unwrap()
.is_none()
);
// add delegation for a different node
reverse_mix_delegations(&mut deps.storage, &delegation_owner1)
.save(node_identity2.as_bytes(), &())
.unwrap();
assert!(
reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1)
.may_load(node_identity1.as_bytes())
.unwrap()
.is_none()
);
// add delegation from a different owner
reverse_mix_delegations(&mut deps.storage, &delegation_owner2)
.save(node_identity1.as_bytes(), &())
.unwrap();
assert!(
reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1)
.may_load(node_identity1.as_bytes())
.unwrap()
.is_none()
);
}
}
}
+277 -32
View File
@@ -1,7 +1,13 @@
use cosmwasm_std::{Addr, Coin, DepsMut, Env, Timestamp};
use crate::contract::{NUM_VESTING_PERIODS, VESTING_PERIOD};
use crate::errors::ContractError;
use crate::storage::{get_account_delegations, set_account_delegations};
use config::defaults::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DENOM};
use cosmwasm_std::{Addr, Coin, 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 {
// locked_coins returns the set of coins that are not spendable (i.e. locked),
@@ -10,10 +16,37 @@ 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, deps: DepsMut) -> Vec<Coin>;
fn locked_coins(&self, block_time: Timestamp, env: Env, deps: DepsMut) -> Coin;
// Calculates the total spendable balance that can be sent to other accounts.
fn spendable_coins(&self, block_time: Option<Timestamp>, env: Env, deps: DepsMut) -> Vec<Coin>;
fn spendable_coins(&self, block_time: Timestamp, env: Env, deps: DepsMut) -> Coin;
fn get_vested_coins(&self, block_time: Timestamp) -> Coin;
fn get_vesting_coins(&self, block_time: Timestamp) -> Coin;
fn get_start_time(&self) -> Timestamp;
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;
}
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
fn try_delegate_to_mixnode(
&self,
mix_identity: IdentityKey,
amount: Coin,
deps: DepsMut,
) -> Result<(), ContractError>;
fn try_undelegate_from_mixnode(
&self,
mix_identity: IdentityKey,
deps: DepsMut,
) -> Result<(), ContractError>;
// track_delegation performs internal vesting accounting necessary when
// delegating from a vesting account. It accepts the current block time, the
@@ -22,57 +55,269 @@ trait VestingAccount {
fn track_delegation(
&self,
block_time: Timestamp,
delegation_amount: Vec<Coin>,
original_vesting_balance: Vec<Coin>,
env: Env,
mix_identity: IdentityKey,
delegation_amount: Coin,
deps: DepsMut,
);
) -> Result<(), ContractError>;
// track_undelegation performs internal vesting accounting necessary when a
// vesting account performs an undelegation.
fn track_undelegation(&self, delegation_amount: Vec<Coin>, env: Env, deps: DepsMut);
fn get_vested_coins(&self, block_time: Timestamp, env: Env, deps: DepsMut) -> Vec<Coin>;
fn get_vesting_coins(&self, block_time: Timestamp, env: Env, deps: DepsMut) -> Vec<Coin>;
fn get_start_time(&self, env: Env, deps: DepsMut) -> Timestamp;
fn get_end_time(&self, env: Env, deps: DepsMut) -> Timestamp;
fn get_original_vesting(&self, env: Env, deps: DepsMut) -> Vec<Coin>;
fn get_delegated_free(&self, env: Env, deps: DepsMut) -> Vec<Coin>;
fn get_delegated_vesting(&self, env: Env, deps: DepsMut) -> Vec<Coin>;
}
trait DelegationAccount {
fn try_delegate_to_mixnode(mix_identity: IdentityKey, amount: Vec<Coin>);
fn try_undelegate_from_mixnode(mix_identity: IdentityKey);
fn track_undelegation(&self, mix_identity: IdentityKey, deps: DepsMut);
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct VestingPeriod {
secs: u64,
pub start_time: u64,
}
impl VestingPeriod {
pub fn end_time(&self) -> Timestamp {
Timestamp::from_seconds(self.start_time + VESTING_PERIOD)
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct PeriodicVestingAccount {
address: Addr,
start_time: u64,
start_time: Timestamp,
periods: Vec<VestingPeriod>,
coin: Coin,
}
impl Default for VestingPeriod {
fn default() -> Self {
// 90 days
VestingPeriod { secs: 90 * 86400 }
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct DelegationData {
amount: Uint128,
block_time: Timestamp,
}
impl PeriodicVestingAccount {
pub fn new(
address: Addr,
coins: Vec<Coin>,
start_time: u64,
coin: Coin,
start_time: Timestamp,
periods: Vec<VestingPeriod>,
// TODO: Store coins into contract to keep track of current balance.
) -> Self {
PeriodicVestingAccount {
address,
start_time,
periods,
coin,
}
}
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
}
fn get_next_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.
self.periods
.iter()
.map(|period| period.start_time)
.collect::<Vec<u64>>()
.binary_search(&block_time.seconds())
.unwrap()
}
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
} else {
0
}
}
fn get_delegated_with_op(
&self,
op: &dyn Fn(u64, u64) -> bool,
env: Env,
deps: DepsMut,
) -> Coin {
let period = self.get_current_vesting_period(env.block.time);
if period == 0 {
return Coin {
amount: Uint128(0),
denom: DENOM.to_string(),
};
}
let end_time = if period >= NUM_VESTING_PERIODS as usize {
u64::MAX
} else {
self.periods[period].end_time().seconds()
};
if let Some(delegations) = get_account_delegations(deps.storage, self.address.as_str()) {
let mut delegated = Coin {
amount: Uint128(0),
denom: DENOM.to_string(),
};
for (_mix_identity, delegation_data) in delegations.iter() {
for delegation in delegation_data {
if op(delegation.block_time.seconds(), end_time) {
delegated.amount += delegation.amount;
}
}
}
delegated
} else {
Coin {
amount: Uint128(0),
denom: DENOM.to_string(),
}
}
}
fn get_balance(&self) -> Coin {
unimplemented!()
}
}
impl DelegationAccount for PeriodicVestingAccount {
fn try_delegate_to_mixnode(
&self,
mix_identity: IdentityKey,
coin: Coin,
deps: DepsMut,
) -> Result<(), ContractError> {
let querier = deps.querier;
let msg = MixnetExecuteMsg::DelegateToMixnodeOnBehalf {
mix_identity,
delegate_addr: self.address.clone(),
coin,
};
querier.query_wasm_smart(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg)?;
Ok(())
}
fn try_undelegate_from_mixnode(
&self,
mix_identity: IdentityKey,
deps: DepsMut,
) -> Result<(), ContractError> {
let querier = deps.querier;
let msg = MixnetExecuteMsg::UnDelegateFromMixnodeOnBehalf {
mix_identity,
delegate_addr: self.address.clone(),
};
querier.query_wasm_smart(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg)?;
Ok(())
}
fn track_delegation(
&self,
block_time: Timestamp,
mix_identity: IdentityKey,
delegation_amount: Coin,
deps: DepsMut,
) -> Result<(), ContractError> {
let mut delegations = if let Some(delegations) =
get_account_delegations(deps.storage, self.address.as_str())
{
delegations
} else {
HashMap::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)?;
Ok(())
}
fn track_undelegation(&self, mix_identity: IdentityKey, deps: DepsMut) {
// 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);
}
}
impl VestingAccount for PeriodicVestingAccount {
fn locked_coins(&self, block_time: Timestamp, env: Env, deps: DepsMut) -> Coin {
// Returns 0 in case of underflow.
Coin {
amount: Uint128(
self.get_vesting_coins(block_time)
.amount
.u128()
.saturating_sub(self.get_delegated_vesting(env, deps).amount.u128()),
),
denom: DENOM.to_string(),
}
}
fn spendable_coins(&self, block_time: Timestamp, env: Env, deps: DepsMut) -> Coin {
Coin {
amount: Uint128(
self.get_balance()
.amount
.u128()
.saturating_sub(self.locked_coins(block_time, env, deps).amount.u128()),
),
denom: DENOM.to_string(),
}
}
fn get_vested_coins(&self, block_time: Timestamp) -> Coin {
let period = self.get_current_vesting_period(block_time);
match period {
// We're in the first period, or the vesting has not started yet.
0 => Coin {
amount: Uint128(0),
denom: DENOM.to_string(),
},
1..=7 => Coin {
amount: Uint128(self.tokens_per_period() * period as u128),
denom: DENOM.to_string(),
},
_ => Coin {
amount: self.coin.amount,
denom: DENOM.to_string(),
},
}
}
fn get_vesting_coins(&self, block_time: Timestamp) -> Coin {
Coin {
amount: Uint128(
self.get_original_vesting().amount.u128()
- self.get_vested_coins(block_time).amount.u128(),
),
denom: DENOM.to_string(),
}
}
fn get_start_time(&self) -> Timestamp {
self.start_time
}
fn get_end_time(&self) -> Timestamp {
self.periods[(NUM_VESTING_PERIODS - 1) as usize].end_time()
}
fn get_original_vesting(&self) -> Coin {
self.coin.clone()
}
fn get_delegated_free(&self, env: Env, deps: DepsMut) -> Coin {
self.get_delegated_with_op(&lt, env, deps)
}
fn get_delegated_vesting(&self, env: Env, deps: DepsMut) -> Coin {
self.get_delegated_with_op(&ge, env, deps)
}
}
fn lt(x: u64, y: u64) -> bool {
x < y
}
fn ge(x: u64, y: u64) -> bool {
x >= y
}