Feature/bond blockstamp (#760)
* Add block_height to MixNode/GatewayBond * Reward based on blockstamp of bonded node or of delegation * Add specific tests * Add migration code * Apply doc nit
This commit is contained in:
committed by
GitHub
parent
1074449f91
commit
e00e77db15
@@ -7,6 +7,8 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::current_block_height;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct Gateway {
|
||||
pub host: String,
|
||||
@@ -24,15 +26,18 @@ pub struct GatewayBond {
|
||||
pub bond_amount: Coin,
|
||||
pub total_delegation: Coin,
|
||||
pub owner: Addr,
|
||||
#[serde(default = "current_block_height")]
|
||||
pub block_height: u64,
|
||||
pub gateway: Gateway,
|
||||
}
|
||||
|
||||
impl GatewayBond {
|
||||
pub fn new(bond_amount: Coin, owner: Addr, gateway: Gateway) -> Self {
|
||||
pub fn new(bond_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
|
||||
GatewayBond {
|
||||
total_delegation: coin(0, &bond_amount.denom),
|
||||
bond_amount,
|
||||
owner,
|
||||
block_height,
|
||||
gateway,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,3 +16,10 @@ pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayRe
|
||||
pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
|
||||
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
pub use types::{IdentityKey, IdentityKeyRef, LayerDistribution, SphinxKey, StateParams};
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
pub static CURRENT_BLOCK_HEIGHT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
pub fn current_block_height() -> u64 {
|
||||
CURRENT_BLOCK_HEIGHT.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::current_block_height;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct MixNode {
|
||||
pub host: String,
|
||||
@@ -35,16 +37,25 @@ pub struct MixNodeBond {
|
||||
pub total_delegation: Coin,
|
||||
pub owner: Addr,
|
||||
pub layer: Layer,
|
||||
#[serde(default = "current_block_height")]
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
}
|
||||
|
||||
impl MixNodeBond {
|
||||
pub fn new(bond_amount: Coin, owner: Addr, layer: Layer, mix_node: MixNode) -> Self {
|
||||
pub fn new(
|
||||
bond_amount: Coin,
|
||||
owner: Addr,
|
||||
layer: Layer,
|
||||
block_height: u64,
|
||||
mix_node: MixNode,
|
||||
) -> Self {
|
||||
MixNodeBond {
|
||||
total_delegation: coin(0, &bond_amount.denom),
|
||||
bond_amount,
|
||||
owner,
|
||||
layer,
|
||||
block_height,
|
||||
mix_node,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,18 +94,22 @@ pub fn execute(
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::BondMixnode { mix_node } => transactions::try_add_mixnode(deps, info, mix_node),
|
||||
ExecuteMsg::BondMixnode { mix_node } => {
|
||||
transactions::try_add_mixnode(deps, env, info, mix_node)
|
||||
}
|
||||
ExecuteMsg::UnbondMixnode {} => transactions::try_remove_mixnode(deps, info),
|
||||
ExecuteMsg::BondGateway { gateway } => transactions::try_add_gateway(deps, info, gateway),
|
||||
ExecuteMsg::BondGateway { gateway } => {
|
||||
transactions::try_add_gateway(deps, env, info, gateway)
|
||||
}
|
||||
ExecuteMsg::UnbondGateway {} => transactions::try_remove_gateway(deps, info),
|
||||
ExecuteMsg::UpdateStateParams(params) => {
|
||||
transactions::try_update_state_params(deps, info, params)
|
||||
}
|
||||
ExecuteMsg::RewardMixnode { identity, uptime } => {
|
||||
transactions::try_reward_mixnode(deps, info, identity, uptime)
|
||||
transactions::try_reward_mixnode(deps, env, info, identity, uptime)
|
||||
}
|
||||
ExecuteMsg::RewardGateway { identity, uptime } => {
|
||||
transactions::try_reward_gateway(deps, info, identity, uptime)
|
||||
transactions::try_reward_gateway(deps, env, info, identity, uptime)
|
||||
}
|
||||
ExecuteMsg::DelegateToMixnode { mix_identity } => {
|
||||
transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
|
||||
@@ -201,7 +205,33 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
pub fn migrate(deps: DepsMut, env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
use crate::storage::{gateways, gateways_read, mixnodes, mixnodes_read};
|
||||
use cosmwasm_std::{Order, StdResult};
|
||||
use mixnet_contract::CURRENT_BLOCK_HEIGHT;
|
||||
use mixnet_contract::{GatewayBond, MixNodeBond};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
CURRENT_BLOCK_HEIGHT.store(env.block.height, Ordering::Relaxed);
|
||||
|
||||
let bonds = mixnodes_read(deps.storage)
|
||||
.range(None, None, Order::Ascending)
|
||||
.map(|res| res.map(|item| item.1))
|
||||
.collect::<StdResult<Vec<MixNodeBond>>>()?;
|
||||
|
||||
for bond in bonds {
|
||||
mixnodes(deps.storage).save(bond.identity().as_bytes(), &bond)?;
|
||||
}
|
||||
|
||||
let bonds = gateways_read(deps.storage)
|
||||
.range(None, None, Order::Ascending)
|
||||
.map(|res| res.map(|item| item.1))
|
||||
.collect::<StdResult<Vec<GatewayBond>>>()?;
|
||||
|
||||
for bond in bonds {
|
||||
gateways(deps.storage).save(bond.identity().as_bytes(), &bond)?;
|
||||
}
|
||||
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ mod tests {
|
||||
good_gateway_bond, good_mixnode_bond, raw_delegation_fixture,
|
||||
};
|
||||
use crate::transactions;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::{Addr, Storage};
|
||||
use mixnet_contract::{Gateway, MixNode, RawDelegationData};
|
||||
|
||||
@@ -546,8 +546,13 @@ mod tests {
|
||||
identity_key: "bobsnode".into(),
|
||||
..helpers::mix_node_fixture()
|
||||
};
|
||||
transactions::try_add_mixnode(deps.as_mut(), mock_info("bob", &good_mixnode_bond()), node)
|
||||
.unwrap();
|
||||
transactions::try_add_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("bob", &good_mixnode_bond()),
|
||||
node,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let res = query_owns_mixnode(deps.as_ref(), Addr::unchecked("fred")).unwrap();
|
||||
assert!(!res.has_node);
|
||||
@@ -557,8 +562,13 @@ mod tests {
|
||||
identity_key: "fredsnode".into(),
|
||||
..helpers::mix_node_fixture()
|
||||
};
|
||||
transactions::try_add_mixnode(deps.as_mut(), mock_info("fred", &good_mixnode_bond()), node)
|
||||
.unwrap();
|
||||
transactions::try_add_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("fred", &good_mixnode_bond()),
|
||||
node,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let res = query_owns_mixnode(deps.as_ref(), Addr::unchecked("fred")).unwrap();
|
||||
assert!(res.has_node);
|
||||
@@ -583,8 +593,13 @@ mod tests {
|
||||
identity_key: "bobsnode".into(),
|
||||
..helpers::gateway_fixture()
|
||||
};
|
||||
transactions::try_add_gateway(deps.as_mut(), mock_info("bob", &good_gateway_bond()), node)
|
||||
.unwrap();
|
||||
transactions::try_add_gateway(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("bob", &good_gateway_bond()),
|
||||
node,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let res = query_owns_gateway(deps.as_ref(), Addr::unchecked("fred")).unwrap();
|
||||
assert!(!res.has_gateway);
|
||||
@@ -594,8 +609,13 @@ mod tests {
|
||||
identity_key: "fredsnode".into(),
|
||||
..helpers::gateway_fixture()
|
||||
};
|
||||
transactions::try_add_gateway(deps.as_mut(), mock_info("fred", &good_gateway_bond()), node)
|
||||
.unwrap();
|
||||
transactions::try_add_gateway(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("fred", &good_gateway_bond()),
|
||||
node,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let res = query_owns_gateway(deps.as_ref(), Addr::unchecked("fred")).unwrap();
|
||||
assert!(res.has_gateway);
|
||||
|
||||
+190
-26
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::queries;
|
||||
use crate::state::State;
|
||||
use crate::transactions::MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
use cosmwasm_std::{Decimal, Order, StdResult, Storage, Uint128};
|
||||
use cosmwasm_storage::{
|
||||
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
|
||||
@@ -164,6 +165,7 @@ pub(crate) fn increase_mix_delegated_stakes(
|
||||
storage: &mut dyn Storage,
|
||||
mix_identity: IdentityKeyRef,
|
||||
scaled_reward_rate: Decimal,
|
||||
reward_blockstamp: u64,
|
||||
) -> StdResult<Uint128> {
|
||||
let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize;
|
||||
|
||||
@@ -193,11 +195,15 @@ pub(crate) fn increase_mix_delegated_stakes(
|
||||
);
|
||||
|
||||
// and for each of them increase the stake proportionally to the reward
|
||||
// if at least `MINIMUM_BLOCK_AGE_FOR_REWARDING` blocks have been created
|
||||
// since they delegated
|
||||
for (delegator_address, mut delegation) in delegations_chunk.into_iter() {
|
||||
let reward = delegation.amount * scaled_reward_rate;
|
||||
delegation.amount += reward;
|
||||
total_rewarded += reward;
|
||||
mix_delegations(storage, mix_identity).save(&delegator_address, &delegation)?;
|
||||
if delegation.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= reward_blockstamp {
|
||||
let reward = delegation.amount * scaled_reward_rate;
|
||||
delegation.amount += reward;
|
||||
total_rewarded += reward;
|
||||
mix_delegations(storage, mix_identity).save(&delegator_address, &delegation)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +214,7 @@ pub(crate) fn increase_gateway_delegated_stakes(
|
||||
storage: &mut dyn Storage,
|
||||
gateway_identity: IdentityKeyRef,
|
||||
scaled_reward_rate: Decimal,
|
||||
reward_blockstamp: u64,
|
||||
) -> StdResult<Uint128> {
|
||||
let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize;
|
||||
|
||||
@@ -237,11 +244,16 @@ pub(crate) fn increase_gateway_delegated_stakes(
|
||||
);
|
||||
|
||||
// and for each of them increase the stake proportionally to the reward
|
||||
// if at least `MINIMUM_BLOCK_AGE_FOR_REWARDING` blocks have been created
|
||||
// since they delegated
|
||||
for (delegator_address, mut delegation) in delegations_chunk.into_iter() {
|
||||
let reward = delegation.amount * scaled_reward_rate;
|
||||
delegation.amount += reward;
|
||||
total_rewarded += reward;
|
||||
gateway_delegations(storage, gateway_identity).save(&delegator_address, &delegation)?;
|
||||
if delegation.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= reward_blockstamp {
|
||||
let reward = delegation.amount * scaled_reward_rate;
|
||||
delegation.amount += reward;
|
||||
total_rewarded += reward;
|
||||
gateway_delegations(storage, gateway_identity)
|
||||
.save(&delegator_address, &delegation)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,6 +447,7 @@ mod tests {
|
||||
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()
|
||||
@@ -468,6 +481,7 @@ mod tests {
|
||||
bond_amount: coin(bond_value, DENOM),
|
||||
total_delegation: coin(0, DENOM),
|
||||
owner: node_owner.clone(),
|
||||
block_height: 12_345,
|
||||
gateway: Gateway {
|
||||
identity_key: node_identity.clone(),
|
||||
..gateway_fixture()
|
||||
@@ -498,9 +512,13 @@ mod tests {
|
||||
// 0.001
|
||||
let reward = Decimal::from_ratio(1u128, 1000u128);
|
||||
|
||||
let total_increase =
|
||||
increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward)
|
||||
.unwrap();
|
||||
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());
|
||||
@@ -518,6 +536,7 @@ mod tests {
|
||||
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);
|
||||
@@ -526,13 +545,17 @@ mod tests {
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), 42),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let total_increase =
|
||||
increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward)
|
||||
.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);
|
||||
|
||||
@@ -545,10 +568,67 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[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);
|
||||
@@ -556,13 +636,20 @@ mod tests {
|
||||
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(), &raw_delegation_fixture(1000))
|
||||
.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)
|
||||
.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);
|
||||
|
||||
@@ -581,6 +668,7 @@ mod tests {
|
||||
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);
|
||||
@@ -588,13 +676,20 @@ mod tests {
|
||||
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(), &raw_delegation_fixture(1000))
|
||||
.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)
|
||||
.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),
|
||||
@@ -696,6 +791,7 @@ mod tests {
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
42,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -715,6 +811,7 @@ mod tests {
|
||||
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);
|
||||
@@ -723,7 +820,7 @@ mod tests {
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), 42),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -731,6 +828,7 @@ mod tests {
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -745,10 +843,67 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[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");
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let total_increase = increase_gateway_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),
|
||||
gateway_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
let total_increase = increase_gateway_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),
|
||||
gateway_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);
|
||||
@@ -756,7 +911,10 @@ mod tests {
|
||||
for i in 0..100 {
|
||||
let delegator_address = Addr::unchecked(format!("address{}", i));
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(delegator_address.as_bytes(), &raw_delegation_fixture(1000))
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -764,6 +922,7 @@ mod tests {
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -784,6 +943,7 @@ mod tests {
|
||||
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);
|
||||
@@ -791,7 +951,10 @@ mod tests {
|
||||
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
|
||||
let delegator_address = Addr::unchecked(format!("address{}", i));
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(delegator_address.as_bytes(), &raw_delegation_fixture(1000))
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -799,6 +962,7 @@ mod tests {
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ pub mod helpers {
|
||||
let key = format!("{}mixnode", sender);
|
||||
try_add_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
info,
|
||||
MixNode {
|
||||
identity_key: key.clone(),
|
||||
@@ -67,6 +68,7 @@ pub mod helpers {
|
||||
let key = format!("{}gateway", sender);
|
||||
try_add_gateway(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
info,
|
||||
Gateway {
|
||||
identity_key: key.clone(),
|
||||
@@ -129,6 +131,7 @@ pub mod helpers {
|
||||
coin(50, DENOM),
|
||||
Addr::unchecked("foo"),
|
||||
Layer::One,
|
||||
12_345,
|
||||
mix_node,
|
||||
)
|
||||
}
|
||||
@@ -156,7 +159,7 @@ pub mod helpers {
|
||||
identity_key: "identity".to_string(),
|
||||
version: "0.10.0".to_string(),
|
||||
};
|
||||
GatewayBond::new(coin(50, DENOM), Addr::unchecked("foo"), gateway)
|
||||
GatewayBond::new(coin(50, DENOM), Addr::unchecked("foo"), 12_345, gateway)
|
||||
}
|
||||
|
||||
pub fn raw_delegation_fixture(amount: u128) -> RawDelegationData {
|
||||
|
||||
@@ -16,6 +16,7 @@ use mixnet_contract::{
|
||||
};
|
||||
|
||||
const OLD_DELEGATIONS_CHUNK_SIZE: usize = 500;
|
||||
pub(crate) const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 17280;
|
||||
|
||||
// Looks for the total amount of delegations towards a particular node.
|
||||
// This function is used only in very specific circumstances:
|
||||
@@ -87,6 +88,7 @@ fn validate_mixnode_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), Con
|
||||
|
||||
pub(crate) fn try_add_mixnode(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_node: MixNode,
|
||||
) -> Result<Response, ContractError> {
|
||||
@@ -126,7 +128,13 @@ pub(crate) fn try_add_mixnode(
|
||||
let layer_distribution = queries::query_layer_distribution(deps.as_ref());
|
||||
let layer = layer_distribution.choose_with_fewest();
|
||||
|
||||
let mut bond = MixNodeBond::new(info.funds[0].clone(), info.sender.clone(), layer, mix_node);
|
||||
let mut bond = MixNodeBond::new(
|
||||
info.funds[0].clone(),
|
||||
info.sender.clone(),
|
||||
layer,
|
||||
env.block.height,
|
||||
mix_node,
|
||||
);
|
||||
|
||||
// this might potentially require more gas if a significant number of delegations was there
|
||||
let delegations_bucket = mix_delegations_read(deps.storage, &bond.mix_node.identity_key);
|
||||
@@ -216,6 +224,7 @@ fn validate_gateway_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), Con
|
||||
|
||||
pub(crate) fn try_add_gateway(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
gateway: Gateway,
|
||||
) -> Result<Response, ContractError> {
|
||||
@@ -252,7 +261,12 @@ pub(crate) fn try_add_gateway(
|
||||
let minimum_bond = read_state_params(deps.storage).minimum_gateway_bond;
|
||||
validate_gateway_bond(&info.funds, minimum_bond)?;
|
||||
|
||||
let mut bond = GatewayBond::new(info.funds[0].clone(), info.sender.clone(), gateway);
|
||||
let mut bond = GatewayBond::new(
|
||||
info.funds[0].clone(),
|
||||
info.sender.clone(),
|
||||
env.block.height,
|
||||
gateway,
|
||||
);
|
||||
|
||||
// this might potentially require more gas if a significant number of delegations was there
|
||||
let delegations_bucket = gateway_delegations_read(deps.storage, &bond.gateway.identity_key);
|
||||
@@ -391,6 +405,7 @@ pub(crate) fn try_update_state_params(
|
||||
|
||||
pub(crate) fn try_reward_mixnode(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_identity: IdentityKey,
|
||||
uptime: u32,
|
||||
@@ -431,14 +446,22 @@ pub(crate) fn try_reward_mixnode(
|
||||
let bond_scaled_reward_rate = scale_reward_by_uptime(bond_reward_rate, uptime)?;
|
||||
let delegation_scaled_reward_rate = scale_reward_by_uptime(delegation_reward_rate, uptime)?;
|
||||
|
||||
let node_reward = current_bond.bond_amount.amount * bond_scaled_reward_rate;
|
||||
let total_delegation_reward =
|
||||
increase_mix_delegated_stakes(deps.storage, &mix_identity, delegation_scaled_reward_rate)?;
|
||||
let mut node_reward = Uint128(0);
|
||||
let total_delegation_reward = increase_mix_delegated_stakes(
|
||||
deps.storage,
|
||||
&mix_identity,
|
||||
delegation_scaled_reward_rate,
|
||||
env.block.height,
|
||||
)?;
|
||||
|
||||
// update current bond with the reward given to the node and the delegators
|
||||
current_bond.bond_amount.amount += node_reward;
|
||||
current_bond.total_delegation.amount += total_delegation_reward;
|
||||
mixnodes(deps.storage).save(mix_identity.as_bytes(), ¤t_bond)?;
|
||||
// if it has been bonded for long enough
|
||||
if current_bond.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= env.block.height {
|
||||
node_reward = current_bond.bond_amount.amount * bond_scaled_reward_rate;
|
||||
current_bond.bond_amount.amount += node_reward;
|
||||
current_bond.total_delegation.amount += total_delegation_reward;
|
||||
mixnodes(deps.storage).save(mix_identity.as_bytes(), ¤t_bond)?;
|
||||
}
|
||||
|
||||
Ok(Response {
|
||||
submessages: vec![],
|
||||
@@ -453,6 +476,7 @@ pub(crate) fn try_reward_mixnode(
|
||||
|
||||
pub(crate) fn try_reward_gateway(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
gateway_identity: IdentityKey,
|
||||
uptime: u32,
|
||||
@@ -493,17 +517,22 @@ pub(crate) fn try_reward_gateway(
|
||||
let scaled_bond_reward_rate = scale_reward_by_uptime(bond_reward_rate, uptime)?;
|
||||
let scaled_delegation_reward_rate = scale_reward_by_uptime(delegation_reward_rate, uptime)?;
|
||||
|
||||
let node_reward = current_bond.bond_amount.amount * scaled_bond_reward_rate;
|
||||
let mut node_reward = Uint128(0);
|
||||
let total_delegation_reward = increase_gateway_delegated_stakes(
|
||||
deps.storage,
|
||||
&gateway_identity,
|
||||
scaled_delegation_reward_rate,
|
||||
env.block.height,
|
||||
)?;
|
||||
|
||||
// update current bond with the reward given to the node and the delegators
|
||||
current_bond.bond_amount.amount += node_reward;
|
||||
current_bond.total_delegation.amount += total_delegation_reward;
|
||||
gateways(deps.storage).save(gateway_identity.as_bytes(), ¤t_bond)?;
|
||||
// if it has been bonded for long enough
|
||||
if current_bond.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= env.block.height {
|
||||
node_reward = current_bond.bond_amount.amount * scaled_bond_reward_rate;
|
||||
current_bond.bond_amount.amount += node_reward;
|
||||
current_bond.total_delegation.amount += total_delegation_reward;
|
||||
gateways(deps.storage).save(gateway_identity.as_bytes(), ¤t_bond)?;
|
||||
}
|
||||
|
||||
Ok(Response {
|
||||
submessages: vec![],
|
||||
@@ -1082,6 +1111,7 @@ pub mod tests {
|
||||
let info = mock_info("fred", &good_mixnode_bond());
|
||||
try_add_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
info,
|
||||
MixNode {
|
||||
identity_key: "fredsmixnode".to_string(),
|
||||
@@ -1481,6 +1511,7 @@ pub mod tests {
|
||||
let info = mock_info("fred", &good_gateway_bond());
|
||||
try_add_gateway(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
info,
|
||||
Gateway {
|
||||
identity_key: "fredsgateway".into(),
|
||||
@@ -1773,6 +1804,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn rewarding_mixnode() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let mut env = mock_env();
|
||||
let current_state = config(deps.as_mut().storage).load().unwrap();
|
||||
let network_monitor_address = current_state.network_monitor_address;
|
||||
|
||||
@@ -1781,12 +1813,13 @@ pub mod tests {
|
||||
|
||||
// errors out if executed by somebody else than network monitor
|
||||
let info = mock_info("not-the-monitor", &[]);
|
||||
let res = try_reward_mixnode(deps.as_mut(), info, node_identity.clone(), 100);
|
||||
let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100);
|
||||
assert_eq!(res, Err(ContractError::Unauthorized));
|
||||
|
||||
// returns bond not found attribute if the target owner hasn't bonded any mixnodes
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_mixnode(deps.as_mut(), info, node_identity.clone(), 100).unwrap();
|
||||
let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100)
|
||||
.unwrap();
|
||||
assert_eq!(vec![attr("result", "bond not found")], res.attributes);
|
||||
|
||||
let initial_bond = 100_000000;
|
||||
@@ -1796,6 +1829,7 @@ pub mod tests {
|
||||
total_delegation: coin(initial_delegation, DENOM),
|
||||
owner: node_owner.clone(),
|
||||
layer: Layer::One,
|
||||
block_height: env.block.height,
|
||||
mix_node: MixNode {
|
||||
identity_key: node_identity.clone(),
|
||||
..mix_node_fixture()
|
||||
@@ -1807,9 +1841,14 @@ pub mod tests {
|
||||
.unwrap();
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save(b"delegator", &raw_delegation_fixture(initial_delegation))
|
||||
.save(
|
||||
b"delegator",
|
||||
&RawDelegationData::new(initial_delegation.into(), env.block.height),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
env.block.height += 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
|
||||
let bond_reward_rate = read_mixnode_epoch_bond_reward_rate(deps.as_ref().storage);
|
||||
let delegation_reward_rate =
|
||||
read_mixnode_epoch_delegation_reward_rate(deps.as_ref().storage);
|
||||
@@ -1822,7 +1861,8 @@ pub mod tests {
|
||||
let expected_delegation = expected_delegation_reward + Uint128(initial_delegation);
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_mixnode(deps.as_mut(), info, node_identity.clone(), 100).unwrap();
|
||||
let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
@@ -1850,7 +1890,136 @@ pub mod tests {
|
||||
let expected_delegation = expected_delegation_reward + expected_delegation;
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_mixnode(deps.as_mut(), info, node_identity.clone(), 20).unwrap();
|
||||
let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 20)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
read_mixnode_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
expected_delegation,
|
||||
read_mixnode_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
attr("bond increase", expected_bond_reward),
|
||||
attr("total delegation increase", expected_delegation_reward),
|
||||
],
|
||||
res.attributes
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewarding_mixnode_blockstamp_based() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let mut env = mock_env();
|
||||
let current_state = config(deps.as_mut().storage).load().unwrap();
|
||||
let network_monitor_address = current_state.network_monitor_address;
|
||||
|
||||
let node_owner: Addr = Addr::unchecked("node-owner");
|
||||
let node_identity: IdentityKey = "nodeidentity".into();
|
||||
|
||||
let initial_bond = 100_000000;
|
||||
let initial_delegation = 200_000000;
|
||||
let mixnode_bond = MixNodeBond {
|
||||
bond_amount: coin(initial_bond, DENOM),
|
||||
total_delegation: coin(initial_delegation, DENOM),
|
||||
owner: node_owner.clone(),
|
||||
layer: Layer::One,
|
||||
block_height: env.block.height,
|
||||
mix_node: MixNode {
|
||||
identity_key: node_identity.clone(),
|
||||
..mix_node_fixture()
|
||||
},
|
||||
};
|
||||
|
||||
mixnodes(deps.as_mut().storage)
|
||||
.save(node_identity.as_bytes(), &mixnode_bond)
|
||||
.unwrap();
|
||||
|
||||
// delegation happens later, but not later enough
|
||||
env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING - 1;
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
b"delegator",
|
||||
&RawDelegationData::new(initial_delegation.into(), env.block.height),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let bond_reward_rate = read_mixnode_epoch_bond_reward_rate(deps.as_ref().storage);
|
||||
let delegation_reward_rate =
|
||||
read_mixnode_epoch_delegation_reward_rate(deps.as_ref().storage);
|
||||
let scaled_bond_reward = scale_reward_by_uptime(bond_reward_rate, 100).unwrap();
|
||||
let scaled_delegation_reward = scale_reward_by_uptime(delegation_reward_rate, 100).unwrap();
|
||||
|
||||
// no reward is due
|
||||
let expected_bond_reward = Uint128(0);
|
||||
let expected_delegation_reward = Uint128(0);
|
||||
let expected_bond = expected_bond_reward + Uint128(initial_bond);
|
||||
let expected_delegation = expected_delegation_reward + Uint128(initial_delegation);
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
read_mixnode_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
expected_delegation,
|
||||
read_mixnode_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
attr("bond increase", expected_bond_reward),
|
||||
attr("total delegation increase", expected_delegation_reward),
|
||||
],
|
||||
res.attributes
|
||||
);
|
||||
|
||||
// reward can happen now, but only for bonded node
|
||||
env.block.height += 1;
|
||||
let expected_bond_reward = expected_bond * scaled_bond_reward;
|
||||
let expected_delegation_reward = Uint128(0);
|
||||
let expected_bond = expected_bond_reward + expected_bond;
|
||||
let expected_delegation = expected_delegation_reward + expected_delegation;
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
read_mixnode_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
expected_delegation,
|
||||
read_mixnode_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
attr("bond increase", expected_bond_reward),
|
||||
attr("total delegation increase", expected_delegation_reward),
|
||||
],
|
||||
res.attributes
|
||||
);
|
||||
|
||||
// reward happens now, both for node owner and delegators
|
||||
env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING - 1;
|
||||
let expected_bond_reward = expected_bond * scaled_bond_reward;
|
||||
let expected_delegation_reward = expected_delegation * scaled_delegation_reward;
|
||||
let expected_bond = expected_bond_reward + expected_bond;
|
||||
let expected_delegation = expected_delegation_reward + expected_delegation;
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
@@ -1873,6 +2042,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn rewarding_gateway() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let mut env = mock_env();
|
||||
let current_state = config(deps.as_mut().storage).load().unwrap();
|
||||
let network_monitor_address = current_state.network_monitor_address;
|
||||
|
||||
@@ -1881,12 +2051,13 @@ pub mod tests {
|
||||
|
||||
// errors out if executed by somebody else than network monitor
|
||||
let info = mock_info("not-the-monitor", &[]);
|
||||
let res = try_reward_gateway(deps.as_mut(), info, node_identity.clone(), 100);
|
||||
let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100);
|
||||
assert_eq!(res, Err(ContractError::Unauthorized));
|
||||
|
||||
// returns bond not found attribute if the target owner hasn't bonded any gateways
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_gateway(deps.as_mut(), info, node_identity.clone(), 100).unwrap();
|
||||
let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100)
|
||||
.unwrap();
|
||||
assert_eq!(vec![attr("result", "bond not found")], res.attributes);
|
||||
|
||||
let initial_bond = 100_000000;
|
||||
@@ -1895,6 +2066,7 @@ pub mod tests {
|
||||
bond_amount: coin(initial_bond, DENOM),
|
||||
total_delegation: coin(initial_delegation, DENOM),
|
||||
owner: node_owner.clone(),
|
||||
block_height: env.block.height,
|
||||
gateway: Gateway {
|
||||
identity_key: node_identity.clone(),
|
||||
..gateway_fixture()
|
||||
@@ -1909,6 +2081,8 @@ pub mod tests {
|
||||
.save(b"delegator", &raw_delegation_fixture(initial_delegation))
|
||||
.unwrap();
|
||||
|
||||
env.block.height += 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
|
||||
let bond_reward_rate = read_gateway_epoch_bond_reward_rate(deps.as_ref().storage);
|
||||
let delegation_reward_rate =
|
||||
read_gateway_epoch_delegation_reward_rate(deps.as_ref().storage);
|
||||
@@ -1921,7 +2095,8 @@ pub mod tests {
|
||||
let expected_delegation = expected_delegation_reward + Uint128(initial_delegation);
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_gateway(deps.as_mut(), info, node_identity.clone(), 100).unwrap();
|
||||
let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
@@ -1950,7 +2125,8 @@ pub mod tests {
|
||||
let expected_delegation = expected_delegation_reward + expected_delegation;
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_gateway(deps.as_mut(), info, node_identity.clone(), 20).unwrap();
|
||||
let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 20)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
@@ -1971,6 +2147,133 @@ pub mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewarding_gateway_blockstamp_based() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let mut env = mock_env();
|
||||
let current_state = config(deps.as_mut().storage).load().unwrap();
|
||||
let network_monitor_address = current_state.network_monitor_address;
|
||||
|
||||
let node_owner: Addr = Addr::unchecked("node-owner");
|
||||
let node_identity: IdentityKey = "nodeidentity".into();
|
||||
|
||||
let initial_bond = 100_000000;
|
||||
let initial_delegation = 200_000000;
|
||||
let gateway_bond = GatewayBond {
|
||||
bond_amount: coin(initial_bond, DENOM),
|
||||
total_delegation: coin(initial_delegation, DENOM),
|
||||
owner: node_owner.clone(),
|
||||
block_height: env.block.height,
|
||||
gateway: Gateway {
|
||||
identity_key: node_identity.clone(),
|
||||
..gateway_fixture()
|
||||
},
|
||||
};
|
||||
|
||||
gateways(deps.as_mut().storage)
|
||||
.save(node_identity.as_bytes(), &gateway_bond)
|
||||
.unwrap();
|
||||
|
||||
// delegation happens later, but not later enough
|
||||
env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING - 1;
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
b"delegator",
|
||||
&RawDelegationData::new(initial_delegation.into(), env.block.height),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let bond_reward_rate = read_gateway_epoch_bond_reward_rate(deps.as_ref().storage);
|
||||
let delegation_reward_rate =
|
||||
read_gateway_epoch_delegation_reward_rate(deps.as_ref().storage);
|
||||
let scaled_bond_reward = scale_reward_by_uptime(bond_reward_rate, 100).unwrap();
|
||||
let scaled_delegation_reward = scale_reward_by_uptime(delegation_reward_rate, 100).unwrap();
|
||||
|
||||
// no reward is due
|
||||
let expected_bond_reward = Uint128(0);
|
||||
let expected_delegation_reward = Uint128(0);
|
||||
let expected_bond = expected_bond_reward + Uint128(initial_bond);
|
||||
let expected_delegation = expected_delegation_reward + Uint128(initial_delegation);
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
read_gateway_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
expected_delegation,
|
||||
read_gateway_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
attr("bond increase", expected_bond_reward),
|
||||
attr("total delegation increase", expected_delegation_reward),
|
||||
],
|
||||
res.attributes
|
||||
);
|
||||
|
||||
// reward can happen now, but only for bonded node
|
||||
env.block.height += 1;
|
||||
let expected_bond_reward = expected_bond * scaled_bond_reward;
|
||||
let expected_delegation_reward = Uint128(0);
|
||||
let expected_bond = expected_bond_reward + expected_bond;
|
||||
let expected_delegation = expected_delegation_reward + expected_delegation;
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
read_gateway_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
expected_delegation,
|
||||
read_gateway_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
attr("bond increase", expected_bond_reward),
|
||||
attr("total delegation increase", expected_delegation_reward),
|
||||
],
|
||||
res.attributes
|
||||
);
|
||||
|
||||
// reward happens now, both for node owner and delegators
|
||||
env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING - 1;
|
||||
let expected_bond_reward = expected_bond * scaled_bond_reward;
|
||||
let expected_delegation_reward = expected_delegation * scaled_delegation_reward;
|
||||
let expected_bond = expected_bond_reward + expected_bond;
|
||||
let expected_delegation = expected_delegation_reward + expected_delegation;
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
read_gateway_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
expected_delegation,
|
||||
read_gateway_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
attr("bond increase", expected_bond_reward),
|
||||
attr("total delegation increase", expected_delegation_reward),
|
||||
],
|
||||
res.attributes
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod delegation_stake_validation {
|
||||
use super::*;
|
||||
@@ -2623,6 +2926,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn delegators_on_mix_node_reward_rate() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let mut env = mock_env();
|
||||
let current_state = config(deps.as_mut().storage).load().unwrap();
|
||||
let network_monitor_address = current_state.network_monitor_address;
|
||||
|
||||
@@ -2635,15 +2939,26 @@ pub mod tests {
|
||||
let identity = add_mixnode(node_owner, good_mixnode_bond(), &mut deps);
|
||||
|
||||
mix_delegations(&mut deps.storage, &identity)
|
||||
.save(b"delegator1", &raw_delegation_fixture(initial_delegation1))
|
||||
.save(
|
||||
b"delegator1",
|
||||
&RawDelegationData::new(initial_delegation1.into(), env.block.height),
|
||||
)
|
||||
.unwrap();
|
||||
mix_delegations(&mut deps.storage, &identity)
|
||||
.save(b"delegator2", &raw_delegation_fixture(initial_delegation2))
|
||||
.save(
|
||||
b"delegator2",
|
||||
&RawDelegationData::new(initial_delegation2.into(), env.block.height),
|
||||
)
|
||||
.unwrap();
|
||||
mix_delegations(&mut deps.storage, &identity)
|
||||
.save(b"delegator3", &raw_delegation_fixture(initial_delegation3))
|
||||
.save(
|
||||
b"delegator3",
|
||||
&RawDelegationData::new(initial_delegation3.into(), env.block.height),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
env.block.height += 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
|
||||
let bond_reward = read_mixnode_epoch_bond_reward_rate(deps.as_ref().storage);
|
||||
let delegation_reward = read_mixnode_epoch_delegation_reward_rate(deps.as_ref().storage);
|
||||
|
||||
@@ -2660,7 +2975,8 @@ pub mod tests {
|
||||
let expected_delegation3 = expected_delegation3_reward + Uint128(initial_delegation3);
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_mixnode(deps.as_mut(), info, identity.clone(), 100).unwrap();
|
||||
let res =
|
||||
try_reward_mixnode(deps.as_mut(), env.clone(), info, identity.clone(), 100).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
@@ -2719,7 +3035,8 @@ pub mod tests {
|
||||
let expected_delegation3 = expected_delegation3_reward + expected_delegation3;
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_mixnode(deps.as_mut(), info, identity.clone(), 20).unwrap();
|
||||
let res =
|
||||
try_reward_mixnode(deps.as_mut(), env.clone(), info, identity.clone(), 20).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
@@ -2765,7 +3082,8 @@ pub mod tests {
|
||||
|
||||
// if the node was 0% up, nobody will get any rewards
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_mixnode(deps.as_mut(), info, identity.clone(), 0).unwrap();
|
||||
let res =
|
||||
try_reward_mixnode(deps.as_mut(), env.clone(), info, identity.clone(), 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
@@ -3411,6 +3729,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn delegators_on_gateway_reward_rate() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let mut env = mock_env();
|
||||
let current_state = config(deps.as_mut().storage).load().unwrap();
|
||||
let network_monitor_address = current_state.network_monitor_address;
|
||||
|
||||
@@ -3432,6 +3751,8 @@ pub mod tests {
|
||||
.save(b"delegator3", &raw_delegation_fixture(initial_delegation3))
|
||||
.unwrap();
|
||||
|
||||
env.block.height += 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
|
||||
let bond_reward = read_gateway_epoch_bond_reward_rate(deps.as_ref().storage);
|
||||
let delegation_reward = read_gateway_epoch_delegation_reward_rate(deps.as_ref().storage);
|
||||
|
||||
@@ -3448,7 +3769,8 @@ pub mod tests {
|
||||
let expected_delegation3 = expected_delegation3_reward + Uint128(initial_delegation3);
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_gateway(deps.as_mut(), info, identity.clone(), 100).unwrap();
|
||||
let res =
|
||||
try_reward_gateway(deps.as_mut(), env.clone(), info, identity.clone(), 100).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
@@ -3507,7 +3829,8 @@ pub mod tests {
|
||||
let expected_delegation3 = expected_delegation3_reward + expected_delegation3;
|
||||
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_gateway(deps.as_mut(), info, identity.clone(), 20).unwrap();
|
||||
let res =
|
||||
try_reward_gateway(deps.as_mut(), env.clone(), info, identity.clone(), 20).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
@@ -3553,7 +3876,8 @@ pub mod tests {
|
||||
|
||||
// if the node was 0% up, nobody will get any rewards
|
||||
let info = mock_info(network_monitor_address.as_ref(), &[]);
|
||||
let res = try_reward_gateway(deps.as_mut(), info, identity.clone(), 0).unwrap();
|
||||
let res =
|
||||
try_reward_gateway(deps.as_mut(), env.clone(), info, identity.clone(), 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_bond,
|
||||
@@ -3651,6 +3975,7 @@ pub mod tests {
|
||||
for owner in ["alice", "bob"] {
|
||||
try_add_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info(owner, &good_mixnode_bond()),
|
||||
MixNode {
|
||||
identity_key: owner.to_string(),
|
||||
|
||||
Reference in New Issue
Block a user