Files
nym/contracts/mixnet/src/contract.rs
T
Jędrzej Stuczyński 2bdee705b7 Feature/total delegation field (#660)
* Changed bond Vec<Coin> into a Coin

* Introduced total_delegation field to bond models

* Restoring total old delegation

* Updating total delegation on new delegations

* Updating total delegation on removal

* Keeping track of delegation increase during rewarding

* Updating total delegation during rewarding + additional response attributes

* Removed irrelevant tests

* Fixed storage-related tests

* Added additional test assertions for delegation increase

* Added additional node rewarding test assertions

* Tests for correct reward calculation + gateway rewarding early termination

* Added delegation field to parsed node type

* Updated typescript bond types

* Moved `OLD_DELEGATIONS_CHUNK_SIZE` to file-wide namespace so that it could be used in tests

* Tests for finding old node delegations

* ibid.

* Issue#657

* Additional test assertions regardingn total delegation

* Missed test field after merge

* ibid

* Cleaning up storage related imports
2021-06-29 11:29:51 +01:00

213 lines
7.5 KiB
Rust

use crate::helpers::calculate_epoch_reward_rate;
use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
use crate::state::{State, StateParams};
use crate::storage::{config, layer_distribution};
use crate::{error::ContractError, queries, transactions};
use cosmwasm_std::{
entry_point, to_binary, Addr, Decimal, Deps, DepsMut, Env, MessageInfo, QueryResponse,
Response, Uint128,
};
pub const INITIAL_DEFAULT_EPOCH_LENGTH: u32 = 2;
/// Constant specifying minimum of coin required to bond a gateway
pub const INITIAL_GATEWAY_BOND: Uint128 = Uint128(100_000000);
/// Constant specifying minimum of coin required to bond a mixnode
pub const INITIAL_MIXNODE_BOND: Uint128 = Uint128(100_000000);
// percentage annual increase. Given starting value of x, we expect to have 1.1x at the end of the year
pub const INITIAL_MIXNODE_BOND_REWARD_RATE: u64 = 110;
pub const INITIAL_GATEWAY_BOND_REWARD_RATE: u64 = 110;
pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100;
const NETWORK_MONITOR_ADDRESS: &str = "hal1v9qauwdq5terag6uvfsdytcs2d0sdmfdq6e83g";
/// Constant specifying denomination of the coin used for bonding
pub const DENOM: &str = "uhal";
fn default_initial_state(owner: Addr) -> State {
let mixnode_bond_reward_rate = Decimal::percent(INITIAL_MIXNODE_BOND_REWARD_RATE);
let gateway_bond_reward_rate = Decimal::percent(INITIAL_GATEWAY_BOND_REWARD_RATE);
State {
owner,
network_monitor_address: Addr::unchecked(NETWORK_MONITOR_ADDRESS), // we trust our hardcoded value
params: StateParams {
epoch_length: INITIAL_DEFAULT_EPOCH_LENGTH,
minimum_mixnode_bond: INITIAL_MIXNODE_BOND,
minimum_gateway_bond: INITIAL_GATEWAY_BOND,
mixnode_bond_reward_rate,
gateway_bond_reward_rate,
mixnode_active_set_size: INITIAL_MIXNODE_ACTIVE_SET_SIZE,
},
mixnode_epoch_bond_reward: calculate_epoch_reward_rate(
INITIAL_DEFAULT_EPOCH_LENGTH,
mixnode_bond_reward_rate,
),
gateway_epoch_bond_reward: calculate_epoch_reward_rate(
INITIAL_DEFAULT_EPOCH_LENGTH,
gateway_bond_reward_rate,
),
}
}
/// Instantiate the contract.
///
/// `deps` contains Storage, API and Querier
/// `env` contains block, message and contract info
/// `msg` is the contract initialization message, sort of like a constructor call.
#[entry_point]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
_msg: InstantiateMsg,
) -> Result<Response, ContractError> {
let state = default_initial_state(info.sender);
config(deps.storage).save(&state)?;
layer_distribution(deps.storage).save(&Default::default())?;
Ok(Response::default())
}
/// Handle an incoming message
#[entry_point]
pub fn execute(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::BondMixnode { mix_node } => transactions::try_add_mixnode(deps, info, mix_node),
ExecuteMsg::UnbondMixnode {} => transactions::try_remove_mixnode(deps, info),
ExecuteMsg::BondGateway { gateway } => transactions::try_add_gateway(deps, 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)
}
ExecuteMsg::RewardGateway { identity, uptime } => {
transactions::try_reward_gateway(deps, info, identity, uptime)
}
ExecuteMsg::DelegateToMixnode { mix_identity } => {
transactions::try_delegate_to_mixnode(deps, info, mix_identity)
}
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
transactions::try_remove_delegation_from_mixnode(deps, info, mix_identity)
}
ExecuteMsg::DelegateToGateway { gateway_identity } => {
transactions::try_delegate_to_gateway(deps, info, gateway_identity)
}
ExecuteMsg::UndelegateFromGateway { gateway_identity } => {
transactions::try_remove_delegation_from_gateway(deps, info, gateway_identity)
}
}
}
#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg {
QueryMsg::GetMixNodes { start_after, limit } => {
to_binary(&queries::query_mixnodes_paged(deps, start_after, limit)?)
}
QueryMsg::GetGateways { limit, start_after } => {
to_binary(&queries::query_gateways_paged(deps, start_after, limit)?)
}
QueryMsg::OwnsMixnode { address } => {
to_binary(&queries::query_owns_mixnode(deps, address)?)
}
QueryMsg::OwnsGateway { address } => {
to_binary(&queries::query_owns_gateway(deps, address)?)
}
QueryMsg::StateParams {} => to_binary(&queries::query_state_params(deps)),
QueryMsg::LayerDistribution {} => to_binary(&queries::query_layer_distribution(deps)),
QueryMsg::GetMixDelegations {
mix_identity,
start_after,
limit,
} => to_binary(&queries::query_mixnode_delegations_paged(
deps,
mix_identity,
start_after,
limit,
)?),
QueryMsg::GetMixDelegation {
mix_identity,
address,
} => to_binary(&queries::query_mixnode_delegation(
deps,
mix_identity,
address,
)?),
QueryMsg::GetGatewayDelegations {
gateway_identity,
start_after,
limit,
} => to_binary(&queries::query_gateway_delegations_paged(
deps,
gateway_identity,
start_after,
limit,
)?),
QueryMsg::GetGatewayDelegation {
gateway_identity,
address,
} => to_binary(&queries::query_gateway_delegation(
deps,
gateway_identity,
address,
)?),
};
Ok(query_res?)
}
#[entry_point]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
Ok(Default::default())
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::support::tests::helpers::*;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, from_binary};
use mixnet_contract::PagedMixnodeResponse;
#[test]
fn initialize_contract() {
let mut deps = mock_dependencies(&[]);
let env = mock_env();
let msg = InstantiateMsg {};
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
assert_eq!(0, res.messages.len());
// mix_node_bonds should be empty after initialization
let res = query(
deps.as_ref(),
env.clone(),
QueryMsg::GetMixNodes {
start_after: None,
limit: Option::from(2),
},
)
.unwrap();
let page: PagedMixnodeResponse = from_binary(&res).unwrap();
assert_eq!(0, page.nodes.len()); // there are no mixnodes in the list when it's just been initialized
// Contract balance should match what we initialized it as
assert_eq!(
coins(0, DENOM),
query_contract_balance(env.contract.address, deps)
);
}
}