Storing delegations using block timestamp as opposed to block height (#1544)
* Storing delegations using block timestamp as opposed to block height * Updated changelog
This commit is contained in:
committed by
GitHub
parent
ef7961f58e
commit
39ee215005
Generated
+2
-2
@@ -4989,9 +4989,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.79"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
|
||||
dependencies = [
|
||||
"itoa 1.0.1",
|
||||
"ryu",
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## Unreleased
|
||||
|
||||
### Fixed
|
||||
|
||||
- vesting-contract: the contract now correctly stores delegations with their timestamp as opposed to using block height ([#1544])
|
||||
|
||||
[#1544]: https://github.com/nymtech/nym/pull/1544
|
||||
|
||||
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
|
||||
|
||||
### Added
|
||||
|
||||
@@ -5,7 +5,7 @@ use cw_storage_plus::{Item, Map};
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
use vesting_contract_common::PledgeData;
|
||||
|
||||
type BlockHeight = u64;
|
||||
pub(crate) type BlockTimestampSecs = u64;
|
||||
|
||||
pub const KEY: Item<'_, u32> = Item::new("key");
|
||||
const ACCOUNTS: Map<'_, String, Account> = Map::new("acc");
|
||||
@@ -14,7 +14,7 @@ const BALANCES: Map<'_, u32, Uint128> = Map::new("blc");
|
||||
const WITHDRAWNS: Map<'_, u32, Uint128> = Map::new("wthd");
|
||||
const BOND_PLEDGES: Map<'_, u32, PledgeData> = Map::new("bnd");
|
||||
const GATEWAY_PLEDGES: Map<'_, u32, PledgeData> = Map::new("gtw");
|
||||
pub const DELEGATIONS: Map<'_, (u32, IdentityKey, BlockHeight), Uint128> = Map::new("dlg");
|
||||
pub const DELEGATIONS: Map<'_, (u32, IdentityKey, BlockTimestampSecs), Uint128> = Map::new("dlg");
|
||||
pub const ADMIN: Item<'_, String> = Item::new("adm");
|
||||
pub const MIXNET_CONTRACT_ADDRESS: Item<'_, String> = Item::new("mix");
|
||||
pub const MIX_DENOM: Item<'_, String> = Item::new("den");
|
||||
@@ -35,7 +35,7 @@ pub fn update_locked_pledge_cap(
|
||||
}
|
||||
|
||||
pub fn save_delegation(
|
||||
key: (u32, IdentityKey, BlockHeight),
|
||||
key: (u32, IdentityKey, BlockTimestampSecs),
|
||||
amount: Uint128,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
@@ -44,7 +44,7 @@ pub fn save_delegation(
|
||||
}
|
||||
|
||||
pub fn remove_delegation(
|
||||
key: (u32, IdentityKey, BlockHeight),
|
||||
key: (u32, IdentityKey, BlockTimestampSecs),
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
DELEGATIONS.remove(storage, key);
|
||||
|
||||
@@ -88,7 +88,7 @@ impl DelegatingAccount for Account {
|
||||
vec![coin.clone()],
|
||||
)?;
|
||||
self.track_delegation(
|
||||
env.block.height,
|
||||
env.block.time.seconds(),
|
||||
mix_identity,
|
||||
current_balance,
|
||||
coin,
|
||||
@@ -129,14 +129,14 @@ impl DelegatingAccount for Account {
|
||||
|
||||
fn track_delegation(
|
||||
&self,
|
||||
block_height: u64,
|
||||
block_timestamp_secs: u64,
|
||||
mix_identity: IdentityKey,
|
||||
current_balance: Uint128,
|
||||
delegation: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
save_delegation(
|
||||
(self.storage_key(), mix_identity, block_height),
|
||||
(self.storage_key(), mix_identity, block_timestamp_secs),
|
||||
delegation.amount,
|
||||
storage,
|
||||
)?;
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::errors::ContractError;
|
||||
use crate::storage::{
|
||||
load_balance, load_bond_pledge, load_gateway_pledge, load_withdrawn, remove_bond_pledge,
|
||||
remove_delegation, remove_gateway_pledge, save_account, save_balance, save_bond_pledge,
|
||||
save_gateway_pledge, save_withdrawn, DELEGATIONS, KEY,
|
||||
save_gateway_pledge, save_withdrawn, BlockTimestampSecs, DELEGATIONS, KEY,
|
||||
};
|
||||
use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128};
|
||||
use cw_storage_plus::Bound;
|
||||
@@ -261,4 +261,19 @@ impl Account {
|
||||
.filter_map(|x| x.ok())
|
||||
.fold(Uint128::zero(), |acc, (_key, val)| acc + val))
|
||||
}
|
||||
|
||||
pub fn total_delegations_at_timestamp(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
start_time: BlockTimestampSecs,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
Ok(DELEGATIONS
|
||||
.sub_prefix(self.storage_key())
|
||||
.range(storage, None, None, Order::Ascending)
|
||||
.filter_map(|x| x.ok())
|
||||
.filter(|((_mix, block_time), _amount)| *block_time <= start_time)
|
||||
.fold(Uint128::zero(), |acc, ((_mix, _block_time), amount)| {
|
||||
acc + amount
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::errors::ContractError;
|
||||
use crate::storage::{delete_account, save_account, DELEGATIONS, MIX_DENOM};
|
||||
use crate::storage::{delete_account, save_account, MIX_DENOM};
|
||||
use crate::traits::VestingAccount;
|
||||
use cosmwasm_std::{Addr, Coin, Env, Order, Storage, Timestamp, Uint128};
|
||||
use cosmwasm_std::{Addr, Coin, Env, Storage, Timestamp, Uint128};
|
||||
use vesting_contract_common::{OriginalVestingResponse, Period};
|
||||
|
||||
use super::Account;
|
||||
@@ -16,13 +16,6 @@ impl VestingAccount for Account {
|
||||
+ self.get_pledged_vesting(None, env, storage)?.amount)
|
||||
}
|
||||
|
||||
fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
let current_balance = self.load_balance(storage)?;
|
||||
let new_balance = current_balance + amount.amount;
|
||||
self.save_balance(new_balance, storage)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn locked_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -141,14 +134,7 @@ impl VestingAccount for Account {
|
||||
Period::In(idx) => self.periods[idx as usize].start_time,
|
||||
};
|
||||
|
||||
let coin = DELEGATIONS
|
||||
.sub_prefix(self.storage_key())
|
||||
.range(storage, None, None, Order::Ascending)
|
||||
.filter_map(|x| x.ok())
|
||||
.filter(|((_mix, block_time), _amount)| *block_time < start_time)
|
||||
.fold(Uint128::zero(), |acc, ((_mix, _block_time), amount)| {
|
||||
acc + amount
|
||||
});
|
||||
let coin = self.total_delegations_at_timestamp(storage, start_time)?;
|
||||
|
||||
let amount = Uint128::new(coin.u128().min(max_available.u128()));
|
||||
|
||||
@@ -158,6 +144,7 @@ impl VestingAccount for Account {
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: why do we allow querying for block times in the past? - just use env.block.time all the time
|
||||
fn get_delegated_vesting(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -166,9 +153,18 @@ impl VestingAccount for Account {
|
||||
) -> Result<Coin, ContractError> {
|
||||
let block_time = block_time.unwrap_or(env.block.time);
|
||||
let delegated_free = self.get_delegated_free(Some(block_time), env, storage)?;
|
||||
let total_delegations = self.total_delegations(storage)?;
|
||||
|
||||
let amount = total_delegations - delegated_free.amount;
|
||||
let period = self.get_current_vesting_period(block_time);
|
||||
let start_time = match period {
|
||||
Period::Before => 0,
|
||||
Period::After => u64::MAX,
|
||||
Period::In(idx) => self.periods[idx as usize].start_time,
|
||||
};
|
||||
|
||||
let delegations_before_start_time =
|
||||
self.total_delegations_at_timestamp(storage, start_time)?;
|
||||
|
||||
let amount = delegations_before_start_time - delegated_free.amount;
|
||||
|
||||
Ok(Coin {
|
||||
amount,
|
||||
@@ -261,4 +257,11 @@ impl VestingAccount for Account {
|
||||
save_account(self, storage)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
let current_balance = self.load_balance(storage)?;
|
||||
let new_balance = current_balance + amount.amount;
|
||||
self.save_balance(new_balance, storage)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +44,11 @@ mod tests {
|
||||
use crate::traits::DelegatingAccount;
|
||||
use crate::traits::VestingAccount;
|
||||
use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount};
|
||||
use crate::vesting::{populate_vesting_periods, Account};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::{coins, Addr, Coin, Timestamp, Uint128};
|
||||
use mixnet_contract_common::{Gateway, MixNode};
|
||||
use vesting_contract_common::messages::ExecuteMsg;
|
||||
use vesting_contract_common::messages::{ExecuteMsg, VestingSpecification};
|
||||
use vesting_contract_common::Period;
|
||||
|
||||
#[test]
|
||||
@@ -757,4 +758,171 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(Uint128::zero(), bonded_vesting.amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delegated_free() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
|
||||
let vesting_period_length_secs = 3600;
|
||||
|
||||
let account_creation_timestamp = 1650000000;
|
||||
let account_creation_blockheight = 12345;
|
||||
|
||||
// this value is completely arbitrary, I just wanted to keep consistent
|
||||
// (and make sure that if block timestamp increases so does the block height)
|
||||
let blocks_per_period = 100;
|
||||
|
||||
env.block.height = account_creation_blockheight;
|
||||
env.block.time = Timestamp::from_seconds(account_creation_timestamp);
|
||||
|
||||
// lets define some helper timestamps
|
||||
|
||||
// our account is set to be created after 2 vesting periods already passed
|
||||
let vesting_start_blockheight = account_creation_blockheight - 2 * blocks_per_period;
|
||||
let vesting_start_timestamp = account_creation_timestamp - 2 * vesting_period_length_secs;
|
||||
|
||||
let vesting_period2_start_blockheight = vesting_start_blockheight + blocks_per_period;
|
||||
let vesting_period2_start_timestamp = vesting_start_timestamp + vesting_period_length_secs;
|
||||
|
||||
// this vesting period is currently in progress!
|
||||
let vesting_period3_start_blockheight =
|
||||
vesting_period2_start_blockheight + blocks_per_period;
|
||||
let vesting_period3_start_timestamp =
|
||||
vesting_period2_start_timestamp + vesting_period_length_secs;
|
||||
|
||||
// and this one is in the future! (in relation to account creation)
|
||||
let vesting_period4_start_blockheight =
|
||||
vesting_period3_start_blockheight + blocks_per_period;
|
||||
let vesting_period4_start_timestamp =
|
||||
vesting_period3_start_timestamp + vesting_period_length_secs;
|
||||
|
||||
// lets create our vesting account
|
||||
let periods = populate_vesting_periods(
|
||||
vesting_start_timestamp,
|
||||
VestingSpecification::new(None, Some(vesting_period_length_secs), None),
|
||||
);
|
||||
|
||||
let vesting_account = Account::new(
|
||||
Addr::unchecked("owner"),
|
||||
Some(Addr::unchecked("staking")),
|
||||
Coin {
|
||||
amount: Uint128::new(1_000_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
Timestamp::from_seconds(account_creation_timestamp),
|
||||
periods,
|
||||
deps.as_mut().storage,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// time for some delegations
|
||||
|
||||
let mix_identity = "alice".to_string();
|
||||
|
||||
let delegation = Coin {
|
||||
amount: Uint128::new(90_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
};
|
||||
|
||||
// delegate explicitly at the time the account was created
|
||||
// (i.e. after 2 vesting periods already elapsed)
|
||||
env.block.height = account_creation_blockheight;
|
||||
env.block.time = Timestamp::from_seconds(account_creation_timestamp);
|
||||
let ok = vesting_account.try_delegate_to_mixnode(
|
||||
mix_identity.clone(),
|
||||
delegation.clone(),
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(ok.is_ok());
|
||||
|
||||
let vested_coins = vesting_account
|
||||
.get_vested_coins(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
let vesting_coins = vesting_account
|
||||
.get_vesting_coins(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000));
|
||||
assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000));
|
||||
let delegated_free = vesting_account
|
||||
.get_delegated_free(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
let delegated_vesting = vesting_account
|
||||
.get_delegated_vesting(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
|
||||
// all good so far
|
||||
assert_eq!(delegated_free.amount, Uint128::new(90_000_000_000));
|
||||
assert_eq!(delegated_vesting.amount, Uint128::zero());
|
||||
|
||||
// some time passes, and we're now into the next vesting period, more of our coins got unlocked!
|
||||
env.block.height = vesting_period4_start_blockheight;
|
||||
env.block.time = Timestamp::from_seconds(vesting_period4_start_timestamp);
|
||||
|
||||
let vested_coins = vesting_account
|
||||
.get_vested_coins(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
let vesting_coins = vesting_account
|
||||
.get_vesting_coins(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
assert_eq!(vested_coins.amount, Uint128::new(375_000_000_000));
|
||||
assert_eq!(vesting_coins.amount, Uint128::new(625_000_000_000));
|
||||
|
||||
// and nothing about our existing delegation changed
|
||||
let delegated_free = vesting_account
|
||||
.get_delegated_free(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
let delegated_vesting = vesting_account
|
||||
.get_delegated_vesting(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
assert_eq!(delegated_free.amount, Uint128::new(90_000_000_000));
|
||||
assert_eq!(delegated_vesting.amount, Uint128::zero());
|
||||
|
||||
// however, create a new delegation now in this brand new vesting period
|
||||
let delegation = Coin {
|
||||
amount: Uint128::new(50_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
};
|
||||
let ok = vesting_account.try_delegate_to_mixnode(
|
||||
mix_identity.clone(),
|
||||
delegation.clone(),
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(ok.is_ok());
|
||||
|
||||
// we're still good here, we have delegated in total 140M from our vested tokens!
|
||||
let delegated_free = vesting_account
|
||||
.get_delegated_free(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
let delegated_vesting = vesting_account
|
||||
.get_delegated_vesting(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
assert_eq!(delegated_free.amount, Uint128::new(140_000_000_000));
|
||||
assert_eq!(delegated_vesting.amount, Uint128::zero());
|
||||
|
||||
// but let's ask now a different question:
|
||||
// how many vested tokens have I had delegated during vesting period3? (i.e. after account creation)
|
||||
let delegated_free = vesting_account
|
||||
.get_delegated_free(
|
||||
Some(Timestamp::from_seconds(vesting_period3_start_timestamp)),
|
||||
&env,
|
||||
&deps.storage,
|
||||
)
|
||||
.unwrap();
|
||||
let delegated_vesting = vesting_account
|
||||
.get_delegated_vesting(
|
||||
Some(Timestamp::from_seconds(vesting_period3_start_timestamp)),
|
||||
&env,
|
||||
&deps.storage,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// returns 90M as the 50M delegation didn't exist at this point of time
|
||||
assert_eq!(delegated_free.amount, Uint128::new(90_000_000_000));
|
||||
|
||||
// the 50M delegation wasn't a thing here for VESTING tokens either
|
||||
assert_eq!(delegated_vesting.amount, Uint128::zero());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user