Initial analysis
This commit is contained in:
Generated
+105
@@ -2628,6 +2628,29 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8"
|
||||
dependencies = [
|
||||
"indoc-impl",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc-impl"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unindent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "infer"
|
||||
version = "0.4.0"
|
||||
@@ -3790,6 +3813,25 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
|
||||
dependencies = [
|
||||
"paste-impl",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste-impl"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.8.0"
|
||||
@@ -4230,6 +4272,54 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35100f9347670a566a67aa623369293703322bb9db77d99d7df7313b575ae0c8"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"indoc",
|
||||
"libc",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
"pyo3-build-config",
|
||||
"pyo3-macros",
|
||||
"unindent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d12961738cacbd7f91b7c43bc25cfeeaa2698ad07a04b3be0aa88b950865738f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0bc5215d704824dfddddc03f93cb572e1155c68b6761c37005e1c288808ea8"
|
||||
dependencies = [
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71623fc593224afaab918aa3afcaf86ed2f43d34f6afde7f3922608f253240df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-build-config",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
@@ -6289,6 +6379,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokenomics-py"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"mixnet-contract",
|
||||
"pyo3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.12.0"
|
||||
@@ -6619,6 +6718,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
||||
|
||||
[[package]]
|
||||
name = "unindent"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
|
||||
@@ -50,6 +50,7 @@ members = [
|
||||
"mixnode",
|
||||
"service-providers/network-requester",
|
||||
"validator-api",
|
||||
"tokenomics-py"
|
||||
]
|
||||
|
||||
default-members = [
|
||||
|
||||
@@ -182,6 +182,20 @@ impl<C> Client<C> {
|
||||
Ok(self.nymd.get_state_params().await?)
|
||||
}
|
||||
|
||||
pub async fn get_total_mix_stake(&self) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_total_mix_stake().await?.u128())
|
||||
}
|
||||
|
||||
pub async fn get_total_gateway_stake(&self) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_total_gateway_stake().await?.u128())
|
||||
}
|
||||
|
||||
// basically handles paging for us
|
||||
pub async fn get_all_nymd_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
|
||||
where
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
|
||||
use cosmwasm_std::Coin;
|
||||
use cosmwasm_std::{Coin, Uint128};
|
||||
use mixnet_contract::{
|
||||
Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey,
|
||||
LayerDistribution, MixNode, MixOwnershipResponse, PagedAllDelegationsResponse,
|
||||
@@ -212,6 +213,26 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_total_mix_stake(&self) -> Result<Uint128, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetTotalMixStake {};
|
||||
self.client
|
||||
.query_contract_smart(self.contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_total_gateway_stake(&self) -> Result<Uint128, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetTotalGatewayStake {};
|
||||
self.client
|
||||
.query_contract_smart(self.contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Checks whether there is a bonded mixnode associated with the provided client's address
|
||||
pub async fn owns_mixnode(&self, address: &AccountId) -> Result<bool, NymdError>
|
||||
where
|
||||
|
||||
@@ -59,6 +59,10 @@ impl GatewayBond {
|
||||
pub fn gateway(&self) -> &Gateway {
|
||||
&self.gateway
|
||||
}
|
||||
|
||||
pub fn total_delegation(&self) -> Coin {
|
||||
self.total_delegation.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for GatewayBond {
|
||||
|
||||
@@ -79,6 +79,10 @@ impl MixNodeBond {
|
||||
pub fn mix_node(&self) -> &MixNode {
|
||||
&self.mix_node
|
||||
}
|
||||
|
||||
pub fn total_delegation(&self) -> Coin {
|
||||
self.total_delegation.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for MixNodeBond {
|
||||
|
||||
@@ -107,6 +107,8 @@ pub enum QueryMsg {
|
||||
address: Addr,
|
||||
},
|
||||
LayerDistribution {},
|
||||
GetTotalMixStake {},
|
||||
GetTotalGatewayStake {},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
|
||||
@@ -207,13 +207,17 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
|
||||
gateway_identity,
|
||||
address,
|
||||
)?),
|
||||
QueryMsg::GetTotalMixStake {} => to_binary(&queries::query_total_mix_stake(deps)),
|
||||
QueryMsg::GetTotalGatewayStake {} => to_binary(&queries::query_total_gt_stake(deps)),
|
||||
};
|
||||
|
||||
Ok(query_res?)
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
todo!("Calculate initial total mix and gateway stake after initial deployment");
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
|
||||
@@ -89,4 +89,7 @@ pub enum ContractError {
|
||||
identity: IdentityKey,
|
||||
address: Addr,
|
||||
},
|
||||
|
||||
#[error("Overflow error!")]
|
||||
Overflow(#[from] cosmwasm_std::OverflowError),
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ use crate::storage::{
|
||||
all_gateway_delegations_read, all_mix_delegations_read, gateway_delegations_read,
|
||||
gateways_owners_read, gateways_read, mix_delegations_read, mixnodes_owners_read, mixnodes_read,
|
||||
read_layer_distribution, read_state_params, reverse_gateway_delegations_read,
|
||||
reverse_mix_delegations_read,
|
||||
reverse_mix_delegations_read, total_gateway_stake_value, total_mix_stake_value,
|
||||
};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{coin, Addr, Deps, Order, StdResult};
|
||||
use cosmwasm_std::{coin, Addr, Deps, Order, StdResult, Uint128};
|
||||
use mixnet_contract::{
|
||||
Delegation, GatewayBond, GatewayOwnershipResponse, IdentityKey, LayerDistribution, MixNodeBond,
|
||||
MixOwnershipResponse, PagedAllDelegationsResponse, PagedGatewayDelegationsResponse,
|
||||
@@ -93,6 +93,14 @@ pub(crate) fn query_layer_distribution(deps: Deps) -> LayerDistribution {
|
||||
read_layer_distribution(deps.storage)
|
||||
}
|
||||
|
||||
pub(crate) fn query_total_mix_stake(deps: Deps) -> Uint128 {
|
||||
total_mix_stake_value(deps.storage)
|
||||
}
|
||||
|
||||
pub(crate) fn query_total_gt_stake(deps: Deps) -> Uint128 {
|
||||
total_gateway_stake_value(deps.storage)
|
||||
}
|
||||
|
||||
/// Adds a 0 byte to terminate the `start_after` value given. This allows CosmWasm
|
||||
/// to get the succeeding key as the start of the next page.
|
||||
// S works for both `String` and `Addr` and that's what we wanted
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::queries;
|
||||
use crate::state::State;
|
||||
use crate::transactions::MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
use crate::{error::ContractError, queries};
|
||||
use cosmwasm_std::{Decimal, Order, StdResult, Storage, Uint128};
|
||||
use cosmwasm_storage::{
|
||||
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
|
||||
@@ -24,6 +24,9 @@ use serde::Serialize;
|
||||
// singletons
|
||||
const CONFIG_KEY: &[u8] = b"config";
|
||||
const LAYER_DISTRIBUTION_KEY: &[u8] = b"layers";
|
||||
// Keeps total amount of stake towards mixnodes and gateways. Removing a bond removes all its delegations from the total, the reverse is true for adding a bond.
|
||||
const TOTAL_MIX_STAKE_KEY: &[u8] = b"total_mn";
|
||||
const TOTAL_GATEWAY_STAKE_KEY: &[u8] = b"total_gt";
|
||||
|
||||
// buckets
|
||||
const PREFIX_MIXNODES: &[u8] = b"mn";
|
||||
@@ -48,6 +51,72 @@ pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton<State> {
|
||||
singleton_read(storage, CONFIG_KEY)
|
||||
}
|
||||
|
||||
fn total_mix_stake(storage: &dyn Storage) -> ReadonlySingleton<Uint128> {
|
||||
singleton_read(storage, TOTAL_MIX_STAKE_KEY)
|
||||
}
|
||||
|
||||
pub fn mut_total_mix_stake(storage: &mut dyn Storage) -> Singleton<Uint128> {
|
||||
singleton(storage, TOTAL_MIX_STAKE_KEY)
|
||||
}
|
||||
|
||||
fn total_gateway_stake(storage: &dyn Storage) -> ReadonlySingleton<Uint128> {
|
||||
singleton_read(storage, TOTAL_GATEWAY_STAKE_KEY)
|
||||
}
|
||||
|
||||
pub fn mut_total_gateway_stake(storage: &mut dyn Storage) -> Singleton<Uint128> {
|
||||
singleton(storage, TOTAL_GATEWAY_STAKE_KEY)
|
||||
}
|
||||
|
||||
pub fn total_mix_stake_value(storage: &dyn Storage) -> Uint128 {
|
||||
match total_mix_stake(storage).load() {
|
||||
Ok(value) => value,
|
||||
Err(_e) => Uint128(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn total_gateway_stake_value(storage: &dyn Storage) -> Uint128 {
|
||||
match total_gateway_stake(storage).load() {
|
||||
Ok(value) => value,
|
||||
Err(_e) => Uint128(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn incr_total_mix_stake(
|
||||
amount: Uint128,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
let stake = total_mix_stake_value(storage).checked_add(amount)?;
|
||||
mut_total_mix_stake(storage).save(&stake)?;
|
||||
Ok(stake)
|
||||
}
|
||||
|
||||
pub fn decr_total_mix_stake(
|
||||
amount: Uint128,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
let stake = total_mix_stake_value(storage).checked_sub(amount)?;
|
||||
mut_total_mix_stake(storage).save(&stake)?;
|
||||
Ok(stake)
|
||||
}
|
||||
|
||||
pub fn incr_total_gateway_stake(
|
||||
amount: Uint128,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
let stake = total_gateway_stake_value(storage).checked_add(amount)?;
|
||||
mut_total_gateway_stake(storage).save(&stake)?;
|
||||
Ok(stake)
|
||||
}
|
||||
|
||||
pub fn decr_total_gateway_stake(
|
||||
amount: Uint128,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
let stake = total_gateway_stake_value(storage).checked_sub(amount)?;
|
||||
mut_total_gateway_stake(storage).save(&stake)?;
|
||||
Ok(stake)
|
||||
}
|
||||
|
||||
pub(crate) fn read_state_params(storage: &dyn Storage) -> StateParams {
|
||||
// note: In any other case, I wouldn't have attempted to unwrap this result, but in here
|
||||
// if we fail to load the stored state we would already be in the undefined behaviour land,
|
||||
|
||||
@@ -111,6 +111,8 @@ pub(crate) fn try_add_mixnode(
|
||||
mixnodes(deps.storage).save(identity.as_bytes(), &bond)?;
|
||||
mixnodes_owners(deps.storage).save(sender_bytes, identity)?;
|
||||
increment_layer_count(deps.storage, bond.layer)?;
|
||||
incr_total_mix_stake(bond.bond_amount().amount, deps.storage)?;
|
||||
incr_total_mix_stake(bond.total_delegation().amount, deps.storage)?;
|
||||
|
||||
let attributes = vec![attr("overwritten", was_present)];
|
||||
Ok(Response {
|
||||
@@ -150,6 +152,9 @@ pub(crate) fn try_remove_mixnode(
|
||||
// decrement layer count
|
||||
decrement_layer_count(deps.storage, mixnode_bond.layer)?;
|
||||
|
||||
decr_total_mix_stake(mixnode_bond.bond_amount().amount, deps.storage)?;
|
||||
decr_total_mix_stake(mixnode_bond.total_delegation().amount, deps.storage)?;
|
||||
|
||||
// log our actions
|
||||
let attributes = vec![attr("action", "unbond"), attr("mixnode_bond", mixnode_bond)];
|
||||
|
||||
@@ -243,6 +248,9 @@ pub(crate) fn try_add_gateway(
|
||||
gateways_owners(deps.storage).save(sender_bytes, identity)?;
|
||||
increment_layer_count(deps.storage, Layer::Gateway)?;
|
||||
|
||||
incr_total_gateway_stake(bond.bond_amount().amount, deps.storage)?;
|
||||
incr_total_gateway_stake(bond.total_delegation().amount, deps.storage)?;
|
||||
|
||||
let attributes = vec![attr("overwritten", was_present)];
|
||||
Ok(Response {
|
||||
submessages: Vec::new(),
|
||||
@@ -281,6 +289,9 @@ pub(crate) fn try_remove_gateway(
|
||||
// decrement layer count
|
||||
decrement_layer_count(deps.storage, Layer::Gateway)?;
|
||||
|
||||
decr_total_gateway_stake(gateway_bond.bond_amount().amount, deps.storage)?;
|
||||
decr_total_gateway_stake(gateway_bond.total_delegation().amount, deps.storage)?;
|
||||
|
||||
// log our actions
|
||||
let attributes = vec![
|
||||
attr("action", "unbond"),
|
||||
@@ -561,8 +572,10 @@ pub(crate) fn try_delegate_to_mixnode(
|
||||
}
|
||||
};
|
||||
|
||||
let amount = info.funds[0].amount;
|
||||
|
||||
// update total_delegation of this node
|
||||
current_bond.total_delegation.amount += info.funds[0].amount;
|
||||
current_bond.total_delegation.amount += amount;
|
||||
mixnodes_bucket.save(mix_identity.as_bytes(), ¤t_bond)?;
|
||||
|
||||
let mut delegation_bucket = mix_delegations(deps.storage, &mix_identity);
|
||||
@@ -570,8 +583,8 @@ pub(crate) fn try_delegate_to_mixnode(
|
||||
|
||||
// write the delegation
|
||||
let new_amount = match delegation_bucket.may_load(sender_bytes)? {
|
||||
Some(existing_delegation) => existing_delegation.amount + info.funds[0].amount,
|
||||
None => info.funds[0].amount,
|
||||
Some(existing_delegation) => existing_delegation.amount + amount,
|
||||
None => amount,
|
||||
};
|
||||
// the block height is reset, if it existed
|
||||
let new_delegation = RawDelegationData::new(new_amount, env.block.height);
|
||||
@@ -579,6 +592,8 @@ pub(crate) fn try_delegate_to_mixnode(
|
||||
|
||||
reverse_mix_delegations(deps.storage, &info.sender).save(mix_identity.as_bytes(), &())?;
|
||||
|
||||
incr_total_mix_stake(amount, deps.storage)?;
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
@@ -614,6 +629,8 @@ pub(crate) fn try_remove_delegation_from_mixnode(
|
||||
.checked_sub(delegation.amount)
|
||||
.unwrap();
|
||||
mixnodes_bucket.save(mix_identity.as_bytes(), &existing_bond)?;
|
||||
// We should only decr total if a node is bonded, node unbonding remove all delegations from the total
|
||||
decr_total_mix_stake(delegation.amount, deps.storage)?;
|
||||
}
|
||||
|
||||
Ok(Response {
|
||||
@@ -669,6 +686,8 @@ pub(crate) fn try_delegate_to_gateway(
|
||||
reverse_gateway_delegations(deps.storage, &info.sender)
|
||||
.save(gateway_identity.as_bytes(), &())?;
|
||||
|
||||
incr_total_gateway_stake(info.funds[0].amount, deps.storage)?;
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
@@ -707,6 +726,8 @@ pub(crate) fn try_remove_delegation_from_gateway(
|
||||
.checked_sub(delegation.amount)
|
||||
.unwrap();
|
||||
gateways_bucket.save(gateway_identity.as_bytes(), &existing_bond)?;
|
||||
|
||||
decr_total_gateway_stake(delegation.amount, deps.storage)?;
|
||||
}
|
||||
|
||||
Ok(Response {
|
||||
@@ -3494,7 +3515,7 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delegation_is_not_removed_if_node_unbonded() {
|
||||
fn delegation_is_not_removed_if_gateway_unbonded() {
|
||||
let mut deps = helpers::init_contract();
|
||||
|
||||
let gateway_owner = "bob";
|
||||
@@ -4001,4 +4022,122 @@ pub mod tests {
|
||||
assert_eq!(bob_node.mix_node.identity_key, "bob");
|
||||
assert_eq!(bob_node.layer, Layer::Two);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mix_stake_accounting() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let mut test_bond = INITIAL_MIXNODE_BOND;
|
||||
try_add_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("alice", &good_mixnode_bond()),
|
||||
MixNode {
|
||||
identity_key: "alice".to_string(),
|
||||
..helpers::mix_node_fixture()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(total_mix_stake_value(&deps.storage), test_bond);
|
||||
|
||||
let delegation = coin(100, DENOM);
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("bob", &vec![delegation.clone()]),
|
||||
"alice".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test_bond += Uint128(100);
|
||||
assert_eq!(total_mix_stake_value(&deps.storage), test_bond);
|
||||
|
||||
try_remove_mixnode(deps.as_mut(), mock_info("alice", &[])).unwrap();
|
||||
|
||||
test_bond = Uint128(0);
|
||||
assert_eq!(total_mix_stake_value(&deps.storage), test_bond);
|
||||
|
||||
try_add_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("alice", &good_mixnode_bond()),
|
||||
MixNode {
|
||||
identity_key: "alice".to_string(),
|
||||
..helpers::mix_node_fixture()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test_bond = Uint128(100) + INITIAL_MIXNODE_BOND;
|
||||
assert_eq!(total_mix_stake_value(&deps.storage), test_bond);
|
||||
|
||||
try_remove_delegation_from_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_info("bob", &[]),
|
||||
"alice".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test_bond = test_bond.checked_sub(Uint128(100)).unwrap();
|
||||
assert_eq!(total_mix_stake_value(&deps.storage), test_bond);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_stake_accounting() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let mut test_bond = INITIAL_GATEWAY_BOND;
|
||||
try_add_gateway(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("alice", &good_gateway_bond()),
|
||||
Gateway {
|
||||
identity_key: "alice".to_string(),
|
||||
..helpers::gateway_fixture()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(total_gateway_stake_value(&deps.storage), test_bond);
|
||||
|
||||
let delegation = coin(100, DENOM);
|
||||
try_delegate_to_gateway(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("bob", &vec![delegation.clone()]),
|
||||
"alice".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test_bond += Uint128(100);
|
||||
assert_eq!(total_gateway_stake_value(&deps.storage), test_bond);
|
||||
|
||||
try_remove_gateway(deps.as_mut(), mock_info("alice", &[])).unwrap();
|
||||
|
||||
test_bond = Uint128(0);
|
||||
assert_eq!(total_gateway_stake_value(&deps.storage), test_bond);
|
||||
|
||||
try_add_gateway(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("alice", &good_gateway_bond()),
|
||||
Gateway {
|
||||
identity_key: "alice".to_string(),
|
||||
..helpers::gateway_fixture()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test_bond = Uint128(100) + INITIAL_GATEWAY_BOND;
|
||||
assert_eq!(total_gateway_stake_value(&deps.storage), test_bond);
|
||||
|
||||
try_remove_delegation_from_gateway(
|
||||
deps.as_mut(),
|
||||
mock_info("bob", &[]),
|
||||
"alice".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test_bond = test_bond.checked_sub(Uint128(100)).unwrap();
|
||||
assert_eq!(total_gateway_stake_value(&deps.storage), test_bond);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "tokenomics-py"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
name = "tokenomics_py"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
mixnet-contract = {path = "../common/mixnet-contract"}
|
||||
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256" }
|
||||
|
||||
[dependencies.pyo3]
|
||||
version = "0.14.5"
|
||||
features = ["extension-module"]
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
use cosmwasm_std::Uint128;
|
||||
use mixnet_contract::MixNodeBond;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
const ACTIVE_MIX_SET_SIZE: u32 = 1000;
|
||||
|
||||
#[pymodule]
|
||||
fn tokenomics_py(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(compute_mix_rewards_py, m)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// assign_rewards_to_nodes, Econ_results:344
|
||||
#[pyfunction]
|
||||
fn compute_mix_rewards_py(
|
||||
pledged: f64,
|
||||
delegated: f64,
|
||||
total_stake: f64, // total stake delegated across the network, ideally persisted in the blockchain
|
||||
performance: f64, // uptime ?
|
||||
income_global_mix: f64, // inflation pool
|
||||
omega_k: f64, // work_share * number of noes, where workshare is considered uniform 1/k, so it follows this is k in the uniform case
|
||||
alpha: f64, // sybil resistance param - externally defined constant
|
||||
k: f64, // number of desired mixnodes - externally defined constant
|
||||
) -> f64 {
|
||||
// set_lambda_sigma_mixnet, Network_econ:308
|
||||
let lambda = (pledged / total_stake).min(1. / k);
|
||||
let sigma = ((pledged + delegated) / total_stake).min(1. / k);
|
||||
|
||||
performance * income_global_mix * (sigma * omega_k + alpha * lambda) / (1. + alpha)
|
||||
}
|
||||
|
||||
fn compute_mix_rewards(mix: &MixNodeBond) {
|
||||
let k = ACTIVE_MIX_SET_SIZE as f64;
|
||||
let one_over_k = 1. / k;
|
||||
let one_over_k_uint = (one_over_k * 1_000_000.) as u128;
|
||||
// Assume uniform node work distribution for simplicity
|
||||
let work_share = one_over_k;
|
||||
let omega_k = work_share * k;
|
||||
// TODO: Use Coin struct from the Tauri wallet, this must be in the Minor denom it will be much easier then
|
||||
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// Compute total stake across the entire network, open question is how to handle orphaned delegations
|
||||
// Compute performance as uptime, uptime can be obtained from the validator API, might be useful to introduce some sugar functions. Validator API is also not available from the contract most likely
|
||||
// Compute workshare, as either 1/k or node uptime / total uptime, probably best to assume 1/k for now
|
||||
// k is the active set size
|
||||
// Where should the inflation pool size be set
|
||||
Vendored
+31
-5
@@ -37,6 +37,9 @@ struct ValidatorCacheInner {
|
||||
|
||||
current_mixnode_active_set_size: AtomicUsize,
|
||||
current_gateway_active_set_size: AtomicUsize,
|
||||
|
||||
total_mix_stake: RwLock<Cache<u128>>,
|
||||
total_gateway_stake: RwLock<Cache<u128>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Clone)]
|
||||
@@ -76,21 +79,30 @@ impl<C> ValidatorCacheRefresher<C> {
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let (mixnodes, gateways) = tokio::try_join!(
|
||||
let (mixnodes, gateways, total_mix_stake, total_gt_state) = tokio::try_join!(
|
||||
self.nymd_client.get_mixnodes(),
|
||||
self.nymd_client.get_gateways()
|
||||
self.nymd_client.get_gateways(),
|
||||
self.nymd_client.get_total_mix_stake(),
|
||||
self.nymd_client.get_total_gateway_stake()
|
||||
)?;
|
||||
|
||||
let state_params = self.nymd_client.get_state_params().await?;
|
||||
|
||||
info!(
|
||||
"Updating validator cache. There are {} mixnodes and {} gateways",
|
||||
"Updating validator cache. There are {} mixnodes and {} gateways, total_mix_stake is {}",
|
||||
mixnodes.len(),
|
||||
gateways.len()
|
||||
gateways.len(),
|
||||
total_mix_stake
|
||||
);
|
||||
|
||||
self.cache
|
||||
.update_cache(mixnodes, gateways, state_params)
|
||||
.update_cache(
|
||||
mixnodes,
|
||||
gateways,
|
||||
total_mix_stake,
|
||||
total_gt_state,
|
||||
state_params,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
@@ -177,6 +189,8 @@ impl ValidatorCache {
|
||||
&self,
|
||||
mut mixnodes: Vec<MixNodeBond>,
|
||||
mut gateways: Vec<GatewayBond>,
|
||||
total_mix_stake: u128,
|
||||
total_gt_stake: u128,
|
||||
state: StateParams,
|
||||
) {
|
||||
// if our data is valid, it means the active sets are available,
|
||||
@@ -218,6 +232,16 @@ impl ValidatorCache {
|
||||
|
||||
self.inner.mixnodes.write().await.set(mixnodes);
|
||||
self.inner.gateways.write().await.set(gateways);
|
||||
self.inner
|
||||
.total_mix_stake
|
||||
.write()
|
||||
.await
|
||||
.set(total_mix_stake);
|
||||
self.inner
|
||||
.total_gateway_stake
|
||||
.write()
|
||||
.await
|
||||
.set(total_gt_stake);
|
||||
}
|
||||
|
||||
pub async fn mixnodes(&self) -> Cache<Vec<MixNodeBond>> {
|
||||
@@ -303,6 +327,8 @@ impl ValidatorCacheInner {
|
||||
active_gateways_available: AtomicBool::new(false),
|
||||
current_mixnode_active_set_size: Default::default(),
|
||||
current_gateway_active_set_size: Default::default(),
|
||||
total_mix_stake: Default::default(),
|
||||
total_gateway_stake: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,20 @@ impl<C> Client<C> {
|
||||
Ok(time)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_total_mix_stake(&self) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.0.read().await.get_total_mix_stake().await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_total_gateway_stake(&self) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.0.read().await.get_total_gateway_stake().await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
|
||||
@@ -54,6 +54,8 @@ pub(crate) struct MixnodeToReward {
|
||||
|
||||
/// Total number of individual addresses that have delegated to this particular node
|
||||
pub(crate) total_delegations: usize,
|
||||
/// Node absolute uptime over total active set uptime
|
||||
pub(crate) performance: Option<f64>
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -285,6 +287,7 @@ impl Rewarder {
|
||||
uptime: self
|
||||
.calculate_absolute_uptime(mix.last_day_ipv4, mix.last_day_ipv6),
|
||||
total_delegations,
|
||||
performance: None
|
||||
})
|
||||
})
|
||||
.filter(|node| node.uptime.u8() > 0)
|
||||
|
||||
Reference in New Issue
Block a user