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:
Bogdan-Ștefan Neacşu
2021-09-08 15:07:24 +02:00
committed by GitHub
parent 1074449f91
commit e00e77db15
8 changed files with 638 additions and 73 deletions
+6 -1
View File
@@ -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,
}
}
+7
View File
@@ -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)
}
+12 -1
View File
@@ -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,
}
}
+35 -5
View File
@@ -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())
}
+29 -9
View File
@@ -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
View File
@@ -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();
+4 -1
View File
@@ -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 {
+355 -30
View File
@@ -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(), &current_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(), &current_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(), &current_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(), &current_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(),