Merge branch 'develop' into 327-nym-connect-colours

This commit is contained in:
Gala
2022-09-22 10:37:06 +02:00
362 changed files with 23688 additions and 18008 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
- name: Check out repository code
uses: actions/checkout@v2
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out repository code
+11
View File
@@ -2,6 +2,12 @@ name: Publish Nym binaries
on:
workflow_dispatch:
inputs:
add_tokio_unstable:
description: 'True to add RUSTFLAGS="--cfg tokio_unstable"'
required: true
default: false
type: boolean
release:
types: [created]
@@ -25,6 +31,11 @@ jobs:
with:
script: |
core.setFailed('Release tag did not start with nym-binaries-...')
- name: Sets env vars for tokio if set in manual dispatch inputs
run: |
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
- name: Install Rust stable
uses: actions-rs/toolchain@v1
+2
View File
@@ -9,6 +9,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- nym-cli: added CLI tool for interacting with the Nyx blockchain and Nym mixnet smart contracts ([#1577])
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611](https://github.com/nymtech/nym/pull/1611))
- common/ledger: new library for communicating with a Ledger device ([#1640])
### Changed
@@ -21,6 +22,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1577]: https://github.com/nymtech/nym/pull/1577
[#1585]: https://github.com/nymtech/nym/pull/1585
[#1591]: https://github.com/nymtech/nym/pull/1591
[#1640]: https://github.com/nymtech/nym/pull/1640
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
Generated
+683 -702
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -40,6 +40,7 @@ members = [
"common/crypto/dkg",
"common/execute",
"common/inclusion-probability",
"common/ledger",
"common/mixnode-common",
"common/network-defaults",
"common/nonexhaustive-delayqueue",
@@ -136,7 +136,7 @@ impl LoopCoverTrafficStream<OsRng> {
let cover_message = generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&*self.ack_key,
&self.ack_key,
&self.our_full_destination,
self.average_ack_delay,
self.average_packet_delay,
@@ -228,7 +228,7 @@ where
generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&*self.ack_key,
&self.ack_key,
&self.our_full_destination,
self.config.average_ack_delay,
self.config.average_packet_delay,
@@ -13,7 +13,7 @@ use std::time::Duration;
use task::ShutdownListener;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle;
use topology::{nym_topology_from_bonds, NymTopology};
use topology::{nym_topology_from_detailed, NymTopology};
use url::Url;
// I'm extremely curious why compiler NEVER complained about lack of Debug here before
@@ -266,8 +266,8 @@ impl TopologyRefresher {
};
let mixnodes_count = mixnodes.len();
let topology =
nym_topology_from_bonds(mixnodes, gateways).filter_system_version(&self.client_version);
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
if !self.check_layer_distribution(&topology, mixnodes_count) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used.");
+2 -2
View File
@@ -103,7 +103,7 @@ fn minor_0_12_upgrade(
Version::new(0, 12, 0)
};
print_start_upgrade(&config_version, &to_version);
print_start_upgrade(config_version, &to_version);
config
.get_base_mut()
@@ -111,7 +111,7 @@ fn minor_0_12_upgrade(
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&config_version, &to_version);
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
+2 -2
View File
@@ -102,7 +102,7 @@ fn minor_0_12_upgrade(
Version::new(0, 12, 0)
};
print_start_upgrade(&config_version, &to_version);
print_start_upgrade(config_version, &to_version);
config
.get_base_mut()
@@ -110,7 +110,7 @@ fn minor_0_12_upgrade(
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&config_version, &to_version);
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
+4 -4
View File
@@ -25,7 +25,7 @@ async function main() {
set_panic_hook();
// validator server we will use to get topology from
const validator = "https://validator.nymtech.net/api"; //"http://localhost:8081";
const validator = "https://validator.nymtech.net/api";
client = new NymClient(validator);
@@ -69,7 +69,7 @@ async function sendMessageTo() {
* @param {string} message
*/
function displaySend(message) {
let timestamp = new Date().toISOString().substr(11, 12);
let timestamp = new Date().toISOString().slice(11, 21);
let sendDiv = document.createElement("div")
let paragraph = document.createElement("p")
@@ -90,11 +90,11 @@ function displayReceived(message) {
const content = message.message
const replySurb = message.replySurb
let timestamp = new Date().toISOString().substr(11, 12);
let timestamp = new Date().toISOString().slice(11, 21);
let receivedDiv = document.createElement("div")
let paragraph = document.createElement("p")
paragraph.setAttribute('style', 'color: green')
let paragraphContent = document.createTextNode(timestamp + " received >>> " + content + ((replySurb != null) ? "Reply SURB was attached here (but we can't do anything with it yet" : " (NO REPLY-SURB AVAILABLE)"))
let paragraphContent = document.createTextNode(timestamp + " received >>> " + content)
paragraph.appendChild(paragraphContent)
receivedDiv.appendChild(paragraph)
document.getElementById("output").appendChild(receivedDiv)
+2 -2
View File
@@ -34,6 +34,6 @@
"webpack-dev-server": "^4.7.4"
},
"dependencies": {
"@nymproject/nym-client-wasm": "file:../pkg"
"@nymproject/nym-client-wasm": "1.0.0"
}
}
}
+2 -2
View File
@@ -11,7 +11,7 @@ use rand::rngs::OsRng;
use received_processor::ReceivedMessagesProcessor;
use std::sync::Arc;
use std::time::Duration;
use topology::{gateway, nym_topology_from_bonds, NymTopology};
use topology::{gateway, nym_topology_from_detailed, NymTopology};
use url::Url;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
@@ -265,7 +265,7 @@ impl NymClient {
Ok(gateways) => gateways,
};
let topology = nym_topology_from_bonds(mixnodes, gateways);
let topology = nym_topology_from_detailed(mixnodes, gateways);
let version = env!("CARGO_PKG_VERSION");
topology.filter_system_version(version)
}
@@ -186,7 +186,7 @@ impl Client {
address.into(),
receiver,
initial_connection_timeout,
&*current_reconnection_attempt,
&current_reconnection_attempt,
)
.await
});
+266 -295
View File
@@ -2,35 +2,33 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{validator_api, ValidatorClientError};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::NodeId;
use mixnet_contract_common::{GatewayBond, IdentityKeyRef};
use url::Url;
use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
};
#[cfg(feature = "nymd-client")]
use validator_api_requests::models::{MixNodeBondAnnotated, UptimeResponse};
use crate::nymd::traits::MixnetQueryClient;
#[cfg(feature = "nymd-client")]
use crate::nymd::{
self, error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
};
use crate::nymd::{self, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient};
#[cfg(feature = "nymd-client")]
use mixnet_contract_common::{
mixnode::DelegationEvent, ContractStateParams, Delegation, IdentityKey, Interval,
MixnetContractVersion, MixnodeRewardingStatusResponse, RewardedSetNodeStatus,
RewardedSetUpdateDetails,
mixnode::MixNodeBond,
pending_events::{PendingEpochEvent, PendingIntervalEvent},
Delegation, RewardedSetNodeStatus, UnbondedMixnode,
};
#[cfg(feature = "nymd-client")]
use network_defaults::NymNetworkDetails;
#[cfg(feature = "nymd-client")]
use std::collections::{HashMap, HashSet};
use validator_api_requests::models::MixNodeBondAnnotated;
#[cfg(feature = "nymd-client")]
#[must_use]
@@ -191,12 +189,9 @@ impl Client<QueryNymdClient> {
}
}
// nymd wrappers
#[cfg(feature = "nymd-client")]
impl<C> Client<C> {
pub fn change_validator_api(&mut self, new_endpoint: Url) {
self.validator_api.change_url(new_endpoint)
}
// use case: somebody initialised client without a contract in order to upload and initialise one
// and now they want to actually use it without making new client
pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmrs::AccountId) {
@@ -208,203 +203,22 @@ impl<C> Client<C> {
self.nymd.mixnet_contract_address().clone()
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
pub async fn get_cached_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes_detailed().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes().await?)
}
pub async fn get_cached_rewarded_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes_detailed().await?)
}
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_active_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes_detailed().await?)
}
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn get_contract_settings(&self) -> Result<ContractStateParams, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_contract_settings().await?)
}
pub async fn get_operator_rewards(&self, address: String) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_operator_rewards(address).await?.u128())
}
pub async fn get_delegator_rewards(
&self,
address: String,
mix_identity: IdentityKey,
proxy: Option<String>,
) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_delegator_rewards(address, mix_identity, proxy)
.await?
.u128())
}
pub async fn get_pending_delegation_events(
&self,
owner_address: String,
proxy_address: Option<String>,
) -> Result<Vec<DelegationEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_pending_delegation_events(owner_address, proxy_address)
.await?)
}
pub async fn get_current_epoch(&self) -> Result<Interval, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_epoch().await?)
}
pub async fn get_current_operator_cost(&self) -> Result<u64, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_operator_cost().await?)
}
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
where
C: CosmWasmClient + Sync,
{
self.nymd.get_mixnet_contract_version().await
}
pub async fn get_rewarding_status(
&self,
mix_identity: mixnet_contract_common::IdentityKey,
rewarding_interval_nonce: u32,
) -> Result<MixnodeRewardingStatusResponse, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_rewarding_status(mix_identity, rewarding_interval_nonce)
.await?)
}
pub async fn get_reward_pool(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_reward_pool().await?.u128())
}
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_circulating_supply().await?.u128())
}
pub async fn get_sybil_resistance_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_sybil_resistance_percent().await?)
}
pub async fn get_active_set_work_factor(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_active_set_work_factor().await?)
}
pub async fn get_epochs_in_interval(&self) -> Result<u64, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_epochs_in_interval().await?)
}
pub async fn get_interval_reward_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_interval_reward_percent().await?)
}
pub async fn get_current_rewarded_set_update_details(
&self,
) -> Result<RewardedSetUpdateDetails, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.query_current_rewarded_set_update_details()
.await?)
}
// basically handles paging for us
pub async fn get_all_nymd_rewarded_set_mixnode_identities(
pub async fn get_all_nymd_rewarded_set_mixnodes(
&self,
) -> Result<Vec<(IdentityKey, RewardedSetNodeStatus)>, ValidatorClientError>
) -> Result<Vec<(NodeId, RewardedSetNodeStatus)>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut identities = Vec::new();
let mut start_after = None;
let mut height = None;
loop {
let mut paged_response = self
.nymd
.get_rewarded_set_identities_paged(
start_after.take(),
self.rewarded_set_page_limit,
height,
)
.get_rewarded_set_paged(start_after.take(), self.rewarded_set_page_limit)
.await?;
identities.append(&mut paged_response.identities);
if height.is_none() {
// keep using the same height (the first query happened at the most recent height)
height = Some(paged_response.at_height)
}
identities.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
@@ -416,83 +230,122 @@ impl<C> Client<C> {
Ok(identities)
}
pub async fn get_nymd_rewarded_and_active_sets(
&self,
) -> Result<Vec<(MixNodeBond, RewardedSetNodeStatus)>, ValidatorClientError>
pub async fn get_all_nymd_mixnode_bonds(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let rewarded_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.collect::<HashMap<_, _>>();
Ok(all_mixnodes
.into_iter()
.filter_map(|node| {
rewarded_set_identities
.get(node.identity())
.map(|status| (node, *status))
})
.collect())
}
/// If you need both rewarded and the active set, consider using [Self::get_nymd_rewarded_and_active_sets] instead
pub async fn get_nymd_rewarded_set(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let rewarded_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.map(|(identity, _status)| identity)
.collect::<HashSet<_>>();
Ok(all_mixnodes
.into_iter()
.filter(|node| rewarded_set_identities.contains(node.identity()))
.collect())
}
/// If you need both rewarded and the active set, consider using [Self::get_nymd_rewarded_and_active_sets] instead
pub async fn get_nymd_active_set(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let active_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.filter_map(|(identity, status)| {
if status.is_active() {
Some(identity)
} else {
None
}
})
.collect::<HashSet<_>>();
Ok(all_mixnodes
.into_iter()
.filter(|node| active_set_identities.contains(node.identity()))
.collect())
}
pub async fn get_all_nymd_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_mixnodes_paged(start_after.take(), self.mixnode_page_limit)
.get_mixnode_bonds_paged(self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
}
pub async fn get_all_nymd_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_mixnodes_detailed_paged(self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
}
pub async fn get_all_nymd_unbonded_mixnodes(
&self,
) -> Result<Vec<(NodeId, UnbondedMixnode)>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_unbonded_paged(self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
}
pub async fn get_all_nymd_unbonded_mixnodes_by_owner(
&self,
owner: &cosmrs::AccountId,
) -> Result<Vec<(NodeId, UnbondedMixnode)>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_unbonded_by_owner_paged(owner, self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
}
pub async fn get_all_nymd_unbonded_mixnodes_by_identity(
&self,
identity_key: String,
) -> Result<Vec<(NodeId, UnbondedMixnode)>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_unbonded_by_identity_paged(
identity_key.clone(),
self.mixnode_page_limit,
start_after.take(),
)
.await?;
mixnodes.append(&mut paged_response.nodes);
@@ -508,7 +361,7 @@ impl<C> Client<C> {
pub async fn get_all_nymd_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut gateways = Vec::new();
let mut start_after = None;
@@ -531,18 +384,18 @@ impl<C> Client<C> {
pub async fn get_all_nymd_single_mixnode_delegations(
&self,
identity: IdentityKey,
mix_id: NodeId,
) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_mix_delegations_paged(
identity.clone(),
.get_mixnode_delegations_paged(
mix_id,
start_after.take(),
self.mixnode_delegations_page_limit,
)
@@ -564,7 +417,7 @@ impl<C> Client<C> {
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut delegations = Vec::new();
let mut start_after = None;
@@ -589,10 +442,128 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_mixnode_avg_uptimes(
pub async fn get_all_network_delegations(&self) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_all_network_delegations_paged(
start_after.take(),
self.mixnode_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegations);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(delegations)
}
pub async fn get_all_nymd_pending_epoch_events(
&self,
) -> Result<Vec<UptimeResponse>, ValidatorClientError> {
Ok(self.validator_api.get_mixnode_avg_uptimes().await?)
) -> Result<Vec<PendingEpochEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut events = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_pending_epoch_events_paged(start_after.take(), self.rewarded_set_page_limit)
.await?;
events.append(&mut paged_response.events);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(events)
}
pub async fn get_all_nymd_pending_interval_events(
&self,
) -> Result<Vec<PendingIntervalEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut events = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_pending_interval_events_paged(start_after.take(), self.rewarded_set_page_limit)
.await?;
events.append(&mut paged_response.events);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(events)
}
}
// validator-api wrappers
#[cfg(feature = "nymd-client")]
impl<C> Client<C> {
pub fn change_validator_api(&mut self, new_endpoint: Url) {
self.validator_api.change_url(new_endpoint)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
pub async fn get_cached_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes_detailed().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes().await?)
}
pub async fn get_cached_rewarded_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes_detailed().await?)
}
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_active_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes_detailed().await?)
}
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn blind_sign(
@@ -630,17 +601,17 @@ impl ApiClient {
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes().await?)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
@@ -652,7 +623,7 @@ impl ApiClient {
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorClientError> {
) -> Result<GatewayCoreStatusResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_gateway_core_status_count(identity, since)
@@ -661,39 +632,39 @@ impl ApiClient {
pub async fn get_mixnode_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorClientError> {
) -> Result<MixnodeCoreStatusResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_core_status_count(identity, since)
.get_mixnode_core_status_count(mix_id, since)
.await?)
}
pub async fn get_mixnode_status(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<MixnodeStatusResponse, ValidatorClientError> {
Ok(self.validator_api.get_mixnode_status(identity).await?)
Ok(self.validator_api.get_mixnode_status(mix_id).await?)
}
pub async fn get_mixnode_reward_estimation(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<RewardEstimationResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_reward_estimation(identity)
.get_mixnode_reward_estimation(mix_id)
.await?)
}
pub async fn get_mixnode_stake_saturation(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<StakeSaturationResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_stake_saturation(identity)
.get_mixnode_stake_saturation(mix_id)
.await?)
}
@@ -3,6 +3,7 @@ use crate::nymd::{Config as ClientConfig, NymdClient, QueryNymdClient};
use crate::ApiClient;
use network_defaults::all::Network;
use crate::nymd::traits::MixnetQueryClient;
use colored::Colorize;
use core::fmt;
use itertools::Itertools;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,387 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::error::NymdError;
use crate::nymd::NymdClient;
use async_trait::async_trait;
use cosmrs::AccountId;
use mixnet_contract_common::delegation::{MixNodeDelegationResponse, OwnerProxySubKey};
use mixnet_contract_common::mixnode::{
MixNodeDetails, MixnodeRewardingDetailsResponse, PagedMixnodesDetailsResponse,
PagedUnbondedMixnodesResponse, StakeSaturationResponse, UnbondedMixnodeResponse,
};
use mixnet_contract_common::reward_params::{Performance, RewardingParams};
use mixnet_contract_common::rewarding::{
EstimatedCurrentEpochRewardResponse, PendingRewardResponse,
};
use mixnet_contract_common::{
delegation, ContractBuildInformation, ContractState, ContractStateParams,
CurrentIntervalResponse, EpochEventId, GatewayBondResponse, GatewayOwnershipResponse,
IdentityKey, IntervalEventId, LayerDistribution, MixOwnershipResponse, MixnodeDetailsResponse,
NodeId, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse,
PagedMixNodeDelegationsResponse, PagedMixnodeBondsResponse, PagedRewardedSetResponse,
PendingEpochEventsResponse, PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg,
};
use serde::Deserialize;
#[async_trait]
pub trait MixnetQueryClient {
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>;
// state/sys-params-related
async fn get_mixnet_contract_version(&self) -> Result<ContractBuildInformation, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetContractVersion {})
.await
}
async fn get_rewarding_validator_address(&self) -> Result<AccountId, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardingValidatorAddress {})
.await
}
async fn get_mixnet_contract_settings(&self) -> Result<ContractStateParams, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetStateParams {})
.await
}
async fn get_mixnet_contract_state(&self) -> Result<ContractState, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetState {})
.await
}
async fn get_rewarding_parameters(&self) -> Result<RewardingParams, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardingParams {})
.await
}
async fn get_current_interval_details(&self) -> Result<CurrentIntervalResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetCurrentIntervalDetails {})
.await
}
async fn get_rewarded_set_paged(
&self,
start_after: Option<NodeId>,
limit: Option<u32>,
) -> Result<PagedRewardedSetResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardedSet { limit, start_after })
.await
}
// mixnode-related:
async fn get_mixnode_bonds_paged(
&self,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedMixnodeBondsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixNodeBonds { limit, start_after })
.await
}
async fn get_mixnodes_detailed_paged(
&self,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedMixnodesDetailsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixNodesDetailed { limit, start_after })
.await
}
async fn get_unbonded_paged(
&self,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodes { limit, start_after })
.await
}
async fn get_unbonded_by_owner_paged(
&self,
owner: &AccountId,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByOwner {
owner: owner.to_string(),
limit,
start_after,
})
.await
}
async fn get_unbonded_by_identity_paged(
&self,
identity_key: String,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByIdentityKey {
identity_key,
limit,
start_after,
})
.await
}
async fn get_owned_mixnode(
&self,
address: &AccountId,
) -> Result<MixOwnershipResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetOwnedMixnode {
address: address.to_string(),
})
.await
}
async fn get_mixnode_details(
&self,
mix_id: NodeId,
) -> Result<MixnodeDetailsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeDetails { mix_id })
.await
}
async fn get_mixnode_rewarding_details(
&self,
mix_id: NodeId,
) -> Result<MixnodeRewardingDetailsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeRewardingDetails { mix_id })
.await
}
async fn get_mixnode_stake_saturation(
&self,
mix_id: NodeId,
) -> Result<StakeSaturationResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetStakeSaturation { mix_id })
.await
}
async fn get_unbonded_mixnode_information(
&self,
mix_id: NodeId,
) -> Result<UnbondedMixnodeResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodeInformation { mix_id })
.await
}
async fn get_layer_distribution(&self) -> Result<LayerDistribution, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetLayerDistribution {})
.await
}
// gateway-related:
async fn get_gateways_paged(
&self,
start_after: Option<IdentityKey>,
limit: Option<u32>,
) -> Result<PagedGatewayResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetGateways { start_after, limit })
.await
}
/// Checks whether there is a bonded gateway associated with the provided identity key
async fn get_gateway_bond(
&self,
identity: IdentityKey,
) -> Result<GatewayBondResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetGatewayBond { identity })
.await
}
/// Checks whether there is a bonded gateway associated with the provided client's address
async fn get_owned_gateway(
&self,
address: &AccountId,
) -> Result<GatewayOwnershipResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetOwnedGateway {
address: address.to_string(),
})
.await
}
// delegation-related:
/// Gets list of all delegations towards particular mixnode on particular page.
async fn get_mixnode_delegations_paged(
&self,
mix_id: NodeId,
start_after: Option<String>,
limit: Option<u32>,
) -> Result<PagedMixNodeDelegationsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeDelegations {
mix_id,
start_after,
limit,
})
.await
}
/// Gets list of all the mixnodes to which a particular address delegated.
async fn get_delegator_delegations_paged(
&self,
delegator: String,
start_after: Option<(NodeId, OwnerProxySubKey)>,
limit: Option<u32>,
) -> Result<PagedDelegatorDelegationsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetDelegatorDelegations {
delegator,
start_after,
limit,
})
.await
}
/// Checks value of delegation of given client towards particular mixnode.
async fn get_delegation_details(
&self,
mix_id: NodeId,
delegator: &AccountId,
proxy: Option<String>,
) -> Result<MixNodeDelegationResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetDelegationDetails {
mix_id,
delegator: delegator.to_string(),
proxy,
})
.await
}
/// Gets all the delegations on the entire network
async fn get_all_network_delegations_paged(
&self,
start_after: Option<delegation::StorageKey>,
limit: Option<u32>,
) -> Result<PagedAllDelegationsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetAllDelegations { start_after, limit })
.await
}
// rewards related
async fn get_pending_operator_reward(
&self,
operator: &AccountId,
) -> Result<PendingRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingOperatorReward {
address: operator.to_string(),
})
.await
}
async fn get_pending_mixnode_operator_reward(
&self,
mix_id: NodeId,
) -> Result<PendingRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingMixNodeOperatorReward { mix_id })
.await
}
async fn get_pending_delegator_reward(
&self,
delegator: &AccountId,
mix_id: NodeId,
proxy: Option<String>,
) -> Result<PendingRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingDelegatorReward {
address: delegator.to_string(),
mix_id,
proxy,
})
.await
}
// given the provided performance, estimate the reward at the end of the current epoch
async fn get_estimated_current_epoch_operator_reward(
&self,
mix_id: NodeId,
estimated_performance: Performance,
) -> Result<EstimatedCurrentEpochRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetEstimatedCurrentEpochOperatorReward {
mix_id,
estimated_performance,
})
.await
}
// given the provided performance, estimate the reward at the end of the current epoch
async fn get_estimated_current_epoch_delegator_reward(
&self,
delegator: &AccountId,
mix_id: NodeId,
proxy: Option<String>,
estimated_performance: Performance,
) -> Result<EstimatedCurrentEpochRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetEstimatedCurrentEpochDelegatorReward {
address: delegator.to_string(),
mix_id,
proxy,
estimated_performance,
})
.await
}
// interval-related
async fn get_pending_epoch_events_paged(
&self,
start_after: Option<EpochEventId>,
limit: Option<u32>,
) -> Result<PendingEpochEventsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingEpochEvents { start_after, limit })
.await
}
async fn get_pending_interval_events_paged(
&self,
start_after: Option<IntervalEventId>,
limit: Option<u32>,
) -> Result<PendingIntervalEventsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingIntervalEvents { start_after, limit })
.await
}
async fn get_mixnode_details_by_identity(
&self,
mix_identity: IdentityKey,
) -> Result<Option<MixNodeDetails>, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity {
mix_identity,
})
.await
}
}
#[async_trait]
impl<C> MixnetQueryClient for NymdClient<C>
where
C: CosmWasmClient + Sync + Send,
{
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>,
{
self.client
.query_contract_smart(self.mixnet_contract_address(), &query)
.await
}
}
#[async_trait]
impl<C> MixnetQueryClient for crate::Client<C>
where
C: CosmWasmClient + Sync + Send,
{
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>,
{
self.nymd.query_mixnet_contract(query).await
}
}
@@ -0,0 +1,496 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::coin::Coin;
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{Fee, NymdClient, SigningCosmWasmClient};
use async_trait::async_trait;
use cosmrs::AccountId;
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::reward_params::{IntervalRewardingParamsUpdate, Performance};
use mixnet_contract_common::{
ContractStateParams, ExecuteMsg as MixnetExecuteMsg, Gateway, MixNode, NodeId,
};
#[async_trait]
pub trait MixnetSigningClient {
async fn execute_mixnet_contract(
&self,
fee: Option<Fee>,
msg: MixnetExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError>;
// state/sys-params-related
async fn update_rewarding_validator_address(
&self,
address: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateRewardingValidatorAddress {
address: address.to_string(),
},
vec![],
)
.await
}
async fn update_contract_state_params(
&self,
updated_parameters: ContractStateParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateContractStateParams { updated_parameters },
vec![],
)
.await
}
async fn update_active_set_size(
&self,
active_set_size: u32,
force_immediately: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateActiveSetSize {
active_set_size,
force_immediately,
},
vec![],
)
.await
}
async fn update_rewarding_parameters(
&self,
updated_params: IntervalRewardingParamsUpdate,
force_immediately: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateRewardingParams {
updated_params,
force_immediately,
},
vec![],
)
.await
}
async fn update_interval_config(
&self,
epochs_in_interval: u32,
epoch_duration_secs: u64,
force_immediately: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateIntervalConfig {
epochs_in_interval,
epoch_duration_secs,
force_immediately,
},
vec![],
)
.await
}
async fn advance_current_epoch(
&self,
new_rewarded_set: Vec<NodeId>,
expected_active_set_size: u32,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::AdvanceCurrentEpoch {
new_rewarded_set,
expected_active_set_size,
},
vec![],
)
.await
}
async fn reconcile_epoch_events(
&self,
limit: Option<u32>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::ReconcileEpochEvents { limit },
vec![],
)
.await
}
// mixnode-related:
async fn bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
},
vec![pledge],
)
.await
}
async fn bond_mixnode_on_behalf(
&self,
owner: AccountId,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondMixnodeOnBehalf {
mix_node,
cost_params,
owner_signature,
owner: owner.to_string(),
},
vec![pledge],
)
.await
}
async fn unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![])
.await
}
async fn unbond_mixnode_on_behalf(
&self,
owner: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UnbondMixnodeOnBehalf {
owner: owner.to_string(),
},
vec![],
)
.await
}
async fn update_mixnode_cost_params(
&self,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeCostParams { new_costs },
vec![],
)
.await
}
async fn update_mixnode_cost_params_on_behalf(
&self,
owner: AccountId,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeCostParamsOnBehalf {
new_costs,
owner: owner.to_string(),
},
vec![],
)
.await
}
async fn update_mixnode_config(
&self,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeConfig { new_config },
vec![],
)
.await
}
async fn update_mixnode_config_on_behalf(
&self,
owner: AccountId,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeConfigOnBehalf {
new_config,
owner: owner.to_string(),
},
vec![],
)
.await
}
// gateway-related:
async fn bond_gateway(
&self,
gateway: Gateway,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondGateway {
gateway,
owner_signature,
},
vec![pledge],
)
.await
}
async fn bond_gateway_on_behalf(
&self,
owner: AccountId,
gateway: Gateway,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondGatewayOnBehalf {
gateway,
owner_signature,
owner: owner.to_string(),
},
vec![pledge],
)
.await
}
async fn unbond_gateway(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondGateway {}, vec![])
.await
}
async fn unbond_gateway_on_behalf(
&self,
owner: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UnbondGatewayOnBehalf {
owner: owner.to_string(),
},
vec![],
)
.await
}
// delegation-related:
async fn delegate_to_mixnode(
&self,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DelegateToMixnode { mix_id },
vec![amount],
)
.await
}
async fn delegate_to_mixnode_on_behalf(
&self,
delegate: AccountId,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DelegateToMixnodeOnBehalf {
mix_id,
delegate: delegate.to_string(),
},
vec![amount],
)
.await
}
async fn undelegate_from_mixnode(
&self,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UndelegateFromMixnode { mix_id },
vec![],
)
.await
}
async fn undelegate_to_mixnode_on_behalf(
&self,
delegate: AccountId,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UndelegateFromMixnodeOnBehalf {
mix_id,
delegate: delegate.to_string(),
},
vec![],
)
.await
}
// reward-related
async fn reward_mixnode(
&self,
mix_id: NodeId,
performance: Performance,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::RewardMixnode {
mix_id,
performance,
},
vec![],
)
.await
}
async fn withdraw_operator_reward(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::WithdrawOperatorReward {}, vec![])
.await
}
async fn withdraw_operator_reward_on_behalf(
&self,
owner: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::WithdrawOperatorRewardOnBehalf {
owner: owner.to_string(),
},
vec![],
)
.await
}
async fn withdraw_delegator_reward(
&self,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::WithdrawDelegatorReward { mix_id },
vec![],
)
.await
}
async fn withdraw_delegator_reward_on_behalf(
&self,
owner: AccountId,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::WithdrawDelegatorRewardOnBehalf {
mix_id,
owner: owner.to_string(),
},
vec![],
)
.await
}
}
#[async_trait]
impl<C> MixnetSigningClient for NymdClient<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn execute_mixnet_contract(
&self,
fee: Option<Fee>,
msg: MixnetExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.default_memo();
self.client
.execute(
self.address(),
self.mixnet_contract_address(),
&msg,
fee,
memo,
funds,
)
.await
}
}
#[async_trait]
impl<C> MixnetSigningClient for crate::Client<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn execute_mixnet_contract(
&self,
fee: Option<Fee>,
msg: MixnetExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError> {
self.nymd.execute_mixnet_contract(fee, msg, funds).await
}
}
@@ -1,8 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod coconut_bandwidth_query_client;
mod coconut_bandwidth_signing_client;
mod mixnet_query_client;
mod mixnet_signing_client;
mod multisig_query_client;
mod multisig_signing_client;
mod vesting_query_client;
@@ -10,6 +12,8 @@ mod vesting_signing_client;
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use mixnet_query_client::MixnetQueryClient;
pub use mixnet_signing_client::MixnetSigningClient;
pub use multisig_query_client::MultisigQueryClient;
pub use multisig_signing_client::MultisigSigningClient;
pub use vesting_query_client::VestingQueryClient;
@@ -7,7 +7,7 @@ use crate::nymd::error::NymdError;
use crate::nymd::NymdClient;
use async_trait::async_trait;
use cosmwasm_std::{Coin as CosmWasmCoin, Timestamp};
use mixnet_contract_common::IdentityKey;
use mixnet_contract_common::NodeId;
use vesting_contract::vesting::Account;
use vesting_contract_common::{
messages::QueryMsg as VestingQueryMsg, AllDelegationsResponse, DelegationTimesResponse,
@@ -76,12 +76,12 @@ pub trait VestingQueryClient {
async fn get_delegation_timestamps(
&self,
address: &str,
mix_identity: String,
mix_id: NodeId,
) -> Result<DelegationTimesResponse, NymdError>;
async fn get_all_vesting_delegations_paged(
&self,
start_after: Option<(u32, IdentityKey, u64)>,
start_after: Option<(u32, NodeId, u64)>,
limit: Option<u32>,
) -> Result<AllDelegationsResponse, NymdError>;
@@ -269,11 +269,11 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn get_delegation_timestamps(
&self,
address: &str,
mix_identity: String,
mix_id: NodeId,
) -> Result<DelegationTimesResponse, NymdError> {
let request = VestingQueryMsg::GetDelegationTimes {
address: address.to_string(),
mix_identity,
mix_id,
};
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
@@ -282,7 +282,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn get_all_vesting_delegations_paged(
&self,
start_after: Option<(u32, IdentityKey, u64)>,
start_after: Option<(u32, NodeId, u64)>,
limit: Option<u32>,
) -> Result<AllDelegationsResponse, NymdError> {
let request = VestingQueryMsg::GetAllDelegations { start_after, limit };
@@ -6,14 +6,28 @@ use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{Coin, Fee, NymdClient};
use async_trait::async_trait;
use mixnet_contract_common::{Gateway, IdentityKey, IdentityKeyRef, MixNode};
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::{Gateway, MixNode, NodeId};
use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, VestingSpecification};
#[async_trait]
pub trait VestingSigningClient {
async fn execute_vesting_contract(
&self,
fee: Option<Fee>,
msg: VestingExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_update_mixnode_cost_params(
&self,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_update_mixnode_config(
&self,
profix_margin_percent: u8,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
@@ -43,10 +57,12 @@ pub trait VestingSigningClient {
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: &str,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_mixnode(
@@ -65,21 +81,21 @@ pub trait VestingSigningClient {
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_delegate_to_mixnode<'a>(
async fn vesting_delegate_to_mixnode(
&self,
mix_identity: IdentityKeyRef<'a>,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_undelegate_from_mixnode<'a>(
async fn vesting_undelegate_from_mixnode(
&self,
mix_identity: IdentityKeyRef<'a>,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
@@ -95,15 +111,46 @@ pub trait VestingSigningClient {
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient<C> {
async fn execute_vesting_contract(
&self,
fee: Option<Fee>,
msg: VestingExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.name().to_string();
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&msg,
fee,
memo,
funds,
)
.await
}
async fn vesting_update_mixnode_cost_params(
&self,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::UpdateMixnodeCostParams { new_costs },
vec![],
)
.await
}
async fn vesting_update_mixnode_config(
&self,
profit_margin_percent: u8,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::UpdateMixnodeConfig {
profit_margin_percent,
};
let req = VestingExecuteMsg::UpdateMixnodeConfig { new_config };
self.client
.execute(
self.address(),
@@ -203,26 +250,22 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: &str,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::BondMixnode {
mix_node,
owner_signature: owner_signature.to_string(),
amount: pledge.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::BondMixnode",
vec![],
)
.await
self.execute_vesting_contract(
fee,
VestingExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature: owner_signature.to_string(),
amount: pledge.into(),
},
vec![],
)
.await
}
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
@@ -262,6 +305,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn withdraw_vested_coins(
&self,
amount: Coin,
@@ -282,72 +326,54 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_identity,
amount: amount.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::TrackUndelegation",
vec![],
)
.await
}
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::DelegateToMixnode {
mix_identity: mix_identity.into(),
amount: amount.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::DelegateToMixnode",
vec![],
)
.await
self.execute_vesting_contract(
fee,
VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_id,
amount: amount.into(),
},
vec![],
)
.await
}
async fn vesting_undelegate_from_mixnode<'a>(
async fn vesting_delegate_to_mixnode(
&self,
mix_identity: IdentityKeyRef<'a>,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::UndelegateFromMixnode {
mix_identity: mix_identity.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::UndelegateFromMixnode",
vec![],
)
.await
self.execute_vesting_contract(
fee,
VestingExecuteMsg::DelegateToMixnode {
mix_id,
amount: amount.into(),
},
vec![],
)
.await
}
async fn vesting_undelegate_from_mixnode(
&self,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::UndelegateFromMixnode { mix_id },
vec![],
)
.await
}
async fn create_periodic_vesting_account(
@@ -72,7 +72,7 @@ impl DirectSecp256k1HdWallet {
}
fn derive_keypair(&self, hd_path: &DerivationPath) -> Result<Secp256k1Keypair, NymdError> {
let extended_private_key = XPrv::derive_from_path(&self.seed, hd_path)?;
let extended_private_key = XPrv::derive_from_path(self.seed, hd_path)?;
let private_key: SigningKey = extended_private_key.into();
let public_key = private_key.public_key();
@@ -3,17 +3,18 @@
use crate::validator_api::error::ValidatorAPIError;
use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use url::Url;
use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
CoreNodeStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
GatewayCoreStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
};
pub mod error;
@@ -57,6 +58,7 @@ impl Client {
V: AsRef<str>,
{
let url = create_api_url(&self.url, path, params);
log::trace!("url: {:?}", url.as_str());
Ok(self.reqwest_client.get(url).send().await?.json().await?)
}
@@ -83,7 +85,7 @@ impl Client {
}
}
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
.await
}
@@ -103,7 +105,7 @@ impl Client {
.await
}
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorAPIError> {
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
NO_PARAMS,
@@ -126,7 +128,7 @@ impl Client {
.await
}
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorAPIError> {
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED],
NO_PARAMS,
@@ -149,28 +151,11 @@ impl Client {
.await
}
pub async fn get_probs_mixnode_rewarded(
&self,
mixnode_id: &str,
) -> Result<HashMap<String, f32>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::MIXNODES,
routes::REWARDED,
routes::INCLUSION_CHANCE,
mixnode_id,
],
NO_PARAMS,
)
.await
}
pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorAPIError> {
) -> Result<GatewayCoreStatusResponse, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
@@ -199,16 +184,16 @@ impl Client {
pub async fn get_mixnode_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorAPIError> {
) -> Result<MixnodeCoreStatusResponse, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
CORE_STATUS_COUNT,
],
&[(SINCE_ARG, since.to_string())],
@@ -220,7 +205,8 @@ impl Client {
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
CORE_STATUS_COUNT,
],
NO_PARAMS,
)
@@ -230,14 +216,14 @@ impl Client {
pub async fn get_mixnode_status(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<MixnodeStatusResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
routes::STATUS,
],
NO_PARAMS,
@@ -247,14 +233,14 @@ impl Client {
pub async fn get_mixnode_reward_estimation(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<RewardEstimationResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
routes::REWARD_ESTIMATION,
],
NO_PARAMS,
@@ -264,14 +250,14 @@ impl Client {
pub async fn get_mixnode_stake_saturation(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<StakeSaturationResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
routes::STAKE_SATURATION,
],
NO_PARAMS,
@@ -281,14 +267,14 @@ impl Client {
pub async fn get_mixnode_inclusion_probability(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<InclusionProbabilityResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
routes::INCLUSION_CHANCE,
],
NO_PARAMS,
@@ -298,27 +284,14 @@ impl Client {
pub async fn get_mixnode_avg_uptime(
&self,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Result<UptimeResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
routes::AVG_UPTIME,
],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_avg_uptimes(&self) -> Result<Vec<UptimeResponse>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODES,
&mix_id.to_string(),
routes::AVG_UPTIME,
],
NO_PARAMS,
+5
View File
@@ -28,6 +28,11 @@ pub fn pretty_cosmwasm_coin(coin: &CosmWasmCoin) -> String {
format!("{} {}", amount, denom)
}
pub fn pretty_decimal_with_denom(value: Decimal, denom: &str) -> String {
// TODO: we might have to truncate the value here (that's why I moved it to separate function)
format!("{} {}", value, denom)
}
pub fn show_error<E>(e: E)
where
E: Display,
@@ -51,7 +51,7 @@ pub async fn query_balance(
return;
}
let denom = args.denom.unwrap_or_else(|| "".to_string());
let denom = args.denom.unwrap_or_default();
for coin in coins {
if denom.is_empty() || denom.eq_ignore_ascii_case(&coin.denom) {
@@ -4,12 +4,16 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::Coin;
use mixnet_contract_common::{Coin, NodeId};
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
#[clap(long)]
pub amount: u128,
@@ -20,10 +24,25 @@ pub async fn delegate_to_mixnode(args: Args, client: SigningClient) {
info!("Starting delegation to mixnode");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let coin = Coin::new(args.amount, denom);
let res = client
.delegate_to_mixnode(&*args.identity_key, coin.into(), None)
.delegate_to_mixnode(mix_id, coin.into(), None)
.await
.expect("failed to delegate to mixnode!");
@@ -8,8 +8,8 @@ use crate::context::SigningClientWithValidatorAPI;
use crate::utils::{pretty_cosmwasm_coin, show_error_passthrough};
use comfy_table::Table;
use mixnet_contract_common::mixnode::DelegationEvent;
use mixnet_contract_common::Delegation;
use cosmwasm_std::Addr;
use mixnet_contract_common::{Delegation, PendingEpochEvent, PendingEpochEventData};
#[derive(Debug, Parser)]
pub struct Args {}
@@ -26,19 +26,7 @@ pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) {
.map_err(show_error_passthrough);
let mixnet_contract_events = client
.nymd
.get_pending_delegation_events(client.nymd.address().to_string(), None)
.await
.map_err(show_error_passthrough);
let vesting_contract = client.nymd.vesting_contract_address();
let vesting_contract_events = client
.nymd
.get_pending_delegation_events(
client.nymd.address().to_string(),
Some(vesting_contract.to_string()),
)
.get_all_nymd_pending_epoch_events()
.await
.map_err(show_error_passthrough);
@@ -58,13 +46,6 @@ pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) {
print_delegation_events(res, &client).await;
}
}
if let Ok(res) = vesting_contract_events {
if !res.is_empty() {
println!();
println!("Pending delegations (locked tokens):");
print_delegation_events(res, &client).await;
}
}
}
async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithValidatorAPI) -> String {
@@ -77,14 +58,17 @@ async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithValidator
async fn print_delegations(delegations: Vec<Delegation>, client: &SigningClientWithValidatorAPI) {
let mut table = Table::new();
table.set_header(vec!["Timestamp", "Identity Key", "Delegation", "Proxy"]);
table.set_header(vec!["Timestamp", "Mix Id", "Delegation", "Proxy"]);
for delegation in delegations {
table.add_row(vec![
to_iso_timestamp(delegation.block_height as u32, client).await,
delegation.node_identity.to_string(),
to_iso_timestamp(delegation.height as u32, client).await,
delegation.node_id.to_string(),
pretty_cosmwasm_coin(&delegation.amount),
format!("{:?}", delegation.proxy),
delegation
.proxy
.map(Addr::into_string)
.unwrap_or_else(|| "-".into()),
]);
}
@@ -92,36 +76,53 @@ async fn print_delegations(delegations: Vec<Delegation>, client: &SigningClientW
}
async fn print_delegation_events(
events: Vec<DelegationEvent>,
events: Vec<PendingEpochEvent>,
client: &SigningClientWithValidatorAPI,
) {
let mut table = Table::new();
table.set_header(vec![
"Timestamp",
"Identity Key",
"Mix id",
"Delegation",
"Event Type",
"Proxy",
]);
for event in events {
match event {
DelegationEvent::Delegate(delegation) => {
table.add_row(vec![
to_iso_timestamp(delegation.block_height as u32, client).await,
delegation.node_identity.to_string(),
pretty_cosmwasm_coin(&delegation.amount),
"Delegate".to_string(),
]);
match event.event {
PendingEpochEventData::Delegate {
owner,
mix_id,
amount,
proxy,
} => {
if owner.as_str() == client.nymd.address().as_ref() {
table.add_row(vec![
"not-sure-if-applicable".into(),
mix_id.to_string(),
pretty_cosmwasm_coin(&amount),
"Delegate".to_string(),
proxy.map(Addr::into_string).unwrap_or_else(|| "-".into()),
]);
}
}
DelegationEvent::Undelegate(undelegate) => {
table.add_row(vec![
to_iso_timestamp(undelegate.block_height() as u32, client).await,
undelegate.mix_identity().to_string(),
"-".to_string(),
"Undelegate".to_string(),
]);
PendingEpochEventData::Undelegate {
owner,
mix_id,
proxy,
} => {
if owner.as_str() == client.nymd.address().as_ref() {
table.add_row(vec![
"not-sure-if-applicable".into(),
mix_id.to_string(),
"-".to_string(),
"Undelegate".to_string(),
proxy.map(Addr::into_string).unwrap_or_else(|| "-".into()),
]);
}
}
_ => {}
}
}
@@ -4,18 +4,38 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::NodeId;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn claim_delegator_reward(args: Args, client: SigningClient) {
info!("Claim delegator reward");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.execute_claim_delegator_reward(args.identity_key, None)
.withdraw_delegator_reward(mix_id, None)
.await
.expect("failed to claim delegator-reward");
@@ -4,18 +4,38 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::NodeId;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity: String,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn vesting_claim_delegator_reward(args: Args, client: SigningClient) {
info!("Claim vesting delegator reward");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.execute_vesting_claim_delegator_reward(args.identity, None)
.withdraw_delegator_reward_on_behalf(client.address().clone(), mix_id, None)
.await
.expect("failed to claim vesting delegator-reward");
@@ -4,18 +4,38 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::NodeId;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn undelegate_from_mixnode(args: Args, client: SigningClient) {
info!("removing stake from mix-node");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.remove_mixnode_delegation(&*args.identity_key, None)
.undelegate_from_mixnode(mix_id, None)
.await
.expect("failed to remove stake from mixnode!");
@@ -4,7 +4,8 @@
use clap::Parser;
use log::info;
use mixnet_contract_common::Coin;
use mixnet_contract_common::{Coin, NodeId};
use validator_client::nymd::traits::MixnetQueryClient;
use validator_client::nymd::VestingSigningClient;
use crate::context::SigningClient;
@@ -12,7 +13,10 @@ use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
#[clap(long)]
pub amount: u128,
@@ -23,10 +27,25 @@ pub async fn vesting_delegate_to_mixnode(args: Args, client: SigningClient) {
info!("Starting vesting delegation to mixnode");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let coin = Coin::new(args.amount, denom);
let res = client
.vesting_delegate_to_mixnode(&*args.identity_key, coin.into(), None)
.vesting_delegate_to_mixnode(mix_id, coin.into(), None)
.await
.expect("failed to delegate to mixnode!");
@@ -3,7 +3,8 @@
use clap::Parser;
use log::info;
use mixnet_contract_common::NodeId;
use validator_client::nymd::traits::MixnetQueryClient;
use validator_client::nymd::VestingSigningClient;
use crate::context::SigningClient;
@@ -11,14 +12,32 @@ use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn vesting_undelegate_from_mixnode(args: Args, client: SigningClient) {
info!("removing stake from vesting mix-node");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.vesting_undelegate_from_mixnode(&*args.identity_key, None)
.vesting_undelegate_from_mixnode(mix_id, None)
.await
.expect("failed to remove stake from vesting account on mixnode!");
@@ -6,6 +6,7 @@ use clap::Parser;
use log::{info, warn};
use mixnet_contract_common::Coin;
use network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
@@ -4,6 +4,7 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
@@ -68,7 +68,7 @@ pub async fn vesting_bond_gateway(client: SigningClient, args: Args, denom: &str
let coin = Coin::new(args.amount, denom);
let res = client
.vesting_bond_gateway(gateway, &*args.signature, coin.into(), None)
.vesting_bond_gateway(gateway, &args.signature, coin.into(), None)
.await
.expect("failed to bond gateway!");
@@ -4,6 +4,7 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
@@ -2,12 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use cosmwasm_std::Uint128;
use log::{info, warn};
use mixnet_contract_common::Coin;
use mixnet_contract_common::{Coin, MixNodeCostParams, Percent};
use network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
};
use validator_client::nymd::traits::MixnetSigningClient;
use validator_client::nymd::CosmWasmCoin;
use crate::context::SigningClient;
@@ -40,6 +43,12 @@ pub struct Args {
#[clap(long)]
pub profit_margin_percent: Option<u8>,
#[clap(
long,
help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub interval_operating_cost: Option<u128>,
#[clap(
long,
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
@@ -71,13 +80,23 @@ pub async fn bond_mixnode(args: Args, client: SigningClient) {
sphinx_key: args.sphinx_key,
identity_key: args.identity_key,
version: args.version,
profit_margin_percent: args.profit_margin_percent.unwrap_or(10),
};
let coin = Coin::new(args.amount, denom);
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent.unwrap_or(10) as u64,
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)),
},
};
let res = client
.bond_mixnode(mixnode, args.signature, coin.into(), None)
.bond_mixnode(mixnode, cost_params, args.signature, coin.into(), None)
.await
.expect("failed to bond mixnode!");
@@ -4,6 +4,7 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
@@ -12,7 +13,7 @@ pub async fn claim_operator_reward(_args: Args, client: SigningClient) {
info!("Claim operator reward");
let res = client
.execute_claim_operator_reward(None)
.withdraw_operator_reward(None)
.await
.expect("failed to claim operator reward");
@@ -4,6 +4,7 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
@@ -15,7 +16,7 @@ pub async fn vesting_claim_operator_reward(client: SigningClient) {
info!("Claim vesting operator reward");
let res = client
.execute_vesting_claim_operator_reward(None)
.withdraw_operator_reward_on_behalf(client.address().clone(), None)
.await
.expect("failed to claim vesting operator reward");
@@ -3,8 +3,8 @@
use clap::{Args, Subcommand};
pub mod update_profit_percent;
pub mod vesting_update_profit_percent;
pub mod update_config;
pub mod vesting_update_config;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -15,8 +15,12 @@ pub struct MixnetOperatorsMixnodeSettings {
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsMixnodeSettingsCommands {
/// Update profit percentage
UpdateProfitPercentage(update_profit_percent::Args),
/// Update profit percentage for a mixnode bonded with locked tokens
VestingUpdateProfitPercentage(vesting_update_profit_percent::Args),
/// Update mixnode configuration
UpdateConfig(update_config::Args),
/// Update mixnode configuration for a mixnode bonded with locked tokens
VestingUpdateConfig(vesting_update_config::Args),
/// Update mixnode cost parameters
UpdateCostParameters,
/// Update mixnode cost parameters for a mixnode bonded with locked tokens
VestingUpdateCostParameters,
}
@@ -0,0 +1,68 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::MixNodeConfigUpdate;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: Option<String>,
#[clap(long)]
pub mix_port: Option<u16>,
#[clap(long)]
pub verloc_port: Option<u16>,
#[clap(long)]
pub http_api_port: Option<u16>,
#[clap(long)]
pub version: Option<String>,
}
pub async fn update_config(args: Args, client: SigningClient) {
info!("Update mix node config!");
let current_details = match client
.get_owned_mixnode(client.address())
.await
.expect("failed to query the chain for mixnode details")
.mixnode_details
{
Some(details) => details,
None => {
log::warn!("this operator does not own a mixnode to update");
return;
}
};
let update = MixNodeConfigUpdate {
host: args
.host
.unwrap_or(current_details.bond_information.mix_node.host),
mix_port: args
.mix_port
.unwrap_or(current_details.bond_information.mix_node.mix_port),
verloc_port: args
.verloc_port
.unwrap_or(current_details.bond_information.mix_node.verloc_port),
http_api_port: args
.http_api_port
.unwrap_or(current_details.bond_information.mix_node.http_api_port),
version: args
.version
.unwrap_or(current_details.bond_information.mix_node.version),
};
let res = client
.update_mixnode_config(update, None)
.await
.expect("updating mix-node config");
info!("mixnode config updated: {:?}", res)
}
@@ -1,24 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub profit_percent: u8,
}
pub async fn update_profit_percent(args: Args, client: SigningClient) {
info!("Update mix node profit percent - get those rewards!");
//profit percent between 1-100
let res = client
.update_mixnode_config(args.profit_percent, None)
.await
.expect("updating mix-node profit percent");
info!("profit percentage updated: {:?}", res)
}
@@ -0,0 +1,69 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::MixNodeConfigUpdate;
use validator_client::nymd::traits::MixnetQueryClient;
use validator_client::nymd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: Option<String>,
#[clap(long)]
pub mix_port: Option<u16>,
#[clap(long)]
pub verloc_port: Option<u16>,
#[clap(long)]
pub http_api_port: Option<u16>,
#[clap(long)]
pub version: Option<String>,
}
pub async fn vesting_update_config(client: SigningClient, args: Args) {
info!("Update vesting mix node config!");
let current_details = match client
.get_owned_mixnode(client.address())
.await
.expect("failed to query the chain for mixnode details")
.mixnode_details
{
Some(details) => details,
None => {
log::warn!("this operator does not own a mixnode to update");
return;
}
};
let update = MixNodeConfigUpdate {
host: args
.host
.unwrap_or(current_details.bond_information.mix_node.host),
mix_port: args
.mix_port
.unwrap_or(current_details.bond_information.mix_node.mix_port),
verloc_port: args
.verloc_port
.unwrap_or(current_details.bond_information.mix_node.verloc_port),
http_api_port: args
.http_api_port
.unwrap_or(current_details.bond_information.mix_node.http_api_port),
version: args
.version
.unwrap_or(current_details.bond_information.mix_node.version),
};
let res = client
.vesting_update_mixnode_config(update, None)
.await
.expect("updating vesting mix-node config");
info!("mixnode config updated: {:?}", res)
}
@@ -1,28 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub profit_percent: u8,
#[clap(long)]
pub gas: Option<u64>,
}
pub async fn vesting_update_profit_percent(client: SigningClient, args: Args) {
info!("Update vesting mix node profit percent - get those rewards!");
//profit percent between 1-100
let res = client
.vesting_update_mixnode_config(args.profit_percent, None)
.await
.expect("updating vesting mix-node profit percent");
info!("profit percentage updated: {:?}", res)
}
@@ -3,6 +3,7 @@
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
use crate::context::SigningClient;
@@ -3,13 +3,14 @@
use crate::context::SigningClient;
use clap::Parser;
use cosmwasm_std::Uint128;
use log::{info, warn};
use mixnet_contract_common::Coin;
use mixnet_contract_common::MixNode;
use mixnet_contract_common::{Coin, MixNodeCostParams};
use mixnet_contract_common::{MixNode, Percent};
use network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
};
use validator_client::nymd::VestingSigningClient;
use validator_client::nymd::{CosmWasmCoin, VestingSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
@@ -40,6 +41,12 @@ pub struct Args {
#[clap(long)]
pub profit_margin_percent: Option<u8>,
#[clap(
long,
help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub interval_operating_cost: Option<u128>,
#[clap(
long,
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
@@ -72,13 +79,23 @@ pub async fn vesting_bond_mixnode(client: SigningClient, args: Args, denom: &str
sphinx_key: args.sphinx_key,
identity_key: args.identity_key,
version: args.version,
profit_margin_percent: args.profit_margin_percent.unwrap_or(10),
};
let coin = Coin::new(args.amount, denom);
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent.unwrap_or(10) as u64,
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)),
},
};
let res = client
.vesting_bond_mixnode(mixnode, &*args.signature, coin.into(), None)
.vesting_bond_mixnode(mixnode, cost_params, &args.signature, coin.into(), None)
.await
.expect("failed to bond vesting mixnode!");
@@ -5,7 +5,7 @@ use clap::Parser;
use comfy_table::Table;
use crate::context::QueryClientWithValidatorAPI;
use crate::utils::{pretty_cosmwasm_coin, show_error};
use crate::utils::{pretty_decimal_with_denom, show_error};
#[derive(Debug, Parser)]
pub struct Args {
@@ -19,7 +19,8 @@ pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) {
Ok(res) => match args.identity_key {
Some(identity_key) => {
let node = res.iter().find(|node| {
node.mix_node
node.bond_information
.mix_node
.identity_key
.to_string()
.eq_ignore_ascii_case(&identity_key)
@@ -33,6 +34,7 @@ pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) {
let mut table = Table::new();
table.set_header(vec![
"Mix id",
"Identity Key",
"Owner",
"Host",
@@ -41,13 +43,15 @@ pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) {
"Version",
]);
for node in res {
let denom = &node.bond_information.original_pledge().denom;
table.add_row(vec![
node.mix_node.identity_key.to_string(),
node.owner.to_string(),
node.mix_node.host.to_string(),
pretty_cosmwasm_coin(&node.pledge_amount),
pretty_cosmwasm_coin(&node.total_delegation()),
node.mix_node.version,
node.mix_id().to_string(),
node.bond_information.mix_node.identity_key.clone(),
node.bond_information.owner.clone().into_string(),
node.bond_information.mix_node.host.clone(),
pretty_decimal_with_denom(node.rewarding_details.operator, denom),
pretty_decimal_with_denom(node.rewarding_details.delegates, denom),
node.bond_information.mix_node.version,
]);
}
@@ -51,7 +51,7 @@ pub async fn create(args: Args, client: SigningClient, network_details: &NymNetw
let res = client
.create_periodic_vesting_account(
&*args.address,
&args.address,
args.staking_address,
Some(vesting),
coin.into(),
@@ -70,7 +70,7 @@ pub async fn create(args: Args, client: SigningClient, network_details: &NymNetw
let send_coin_response = client
.send(
&AccountId::from_str(&*args.address).unwrap(),
&AccountId::from_str(&args.address).unwrap(),
vec![coin.into()],
"payment made :)",
None,
+1
View File
@@ -88,6 +88,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
let location = custom_location
.unwrap_or_else(|| self.config_directory().join(Self::config_file_name()));
log::info!("Configuration file will be saved to {:?}", location);
cfg_if::cfg_if! {
if #[cfg(unix)] {
@@ -8,3 +8,4 @@ edition = "2021"
[dependencies]
cosmwasm-std = "1.0.0"
serde = { version = "1.0", features = ["derive"] }
@@ -28,3 +28,26 @@ pub fn may_find_attribute(event: &Event, key: &str) -> Option<String> {
}
None
}
pub trait OptionallyAddAttribute {
fn add_optional_attribute(
self,
key: impl Into<String>,
value: Option<impl Into<String>>,
) -> Self;
}
impl OptionallyAddAttribute for Event {
fn add_optional_attribute(
self,
key: impl Into<String>,
value: Option<impl Into<String>>,
) -> Self {
if let Some(value) = value {
self.add_attribute(key, value)
} else {
// TODO: perhaps if value doesn't exist, we should emit explicit 'null'?
self
}
}
}
@@ -2,3 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
pub mod events;
pub mod types;
pub use types::*;
@@ -0,0 +1,33 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
// TODO: there's no reason this couldn't be used for proper binaries, but in that case
// perhaps the struct should get renamed and moved to a "more" common crate
#[derive(Debug, Serialize, Deserialize)]
pub struct ContractBuildInformation {
// VERGEN_BUILD_TIMESTAMP
/// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.
pub build_timestamp: String,
// VERGEN_BUILD_SEMVER
/// Provides the build version, for example `0.1.0-9-g46f83e1`.
pub build_version: String,
// VERGEN_GIT_SHA
/// Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.
pub commit_sha: String,
// VERGEN_GIT_COMMIT_TIMESTAMP
/// Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.
pub commit_timestamp: String,
// VERGEN_GIT_BRANCH
/// Provides the name of the git branch that was used for the build, for example `master`.
pub commit_branch: String,
// VERGEN_RUSTC_SEMVER
/// Provides the rustc version that was used for the build, for example `1.52.0-nightly`.
pub rustc_version: String,
}
@@ -8,21 +8,22 @@ rust-version = "1.62"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bs58 = "0.4.0"
cosmwasm-std = "1.0.0"
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
schemars = "0.8"
thiserror = "1.0"
fixed = { version = "1.1", features = ["serde"] }
az = "1.1"
contracts-common = { path = "../contracts-common" }
serde_json = "1.0.0"
# TO CHECK WHETHER STILL NEEDED:
log = "0.4.14"
time = { version = "0.3.6", features = ["parsing", "formatting"] }
ts-rs = {version = "6.1.2", optional = true}
contracts-common = { path = "../contracts-common" }
ts-rs = { version = "6.1.2", optional = true }
[dev-dependencies]
rand_chacha = "0.3"
time = { version = "0.3.5", features = ["serde", "macros"] }
[features]
@@ -0,0 +1,25 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Decimal;
// I'm still not 100% sure how to feel about existence of this file
// This is equivalent of representing our display coin with 6 decimal places.
// I'm using this one as opposed to "Decimal::one()", as this provides us with higher accuracy
// whilst providing no noticable drawbacks.
pub const UNIT_DELEGATION_BASE: Decimal =
Decimal::raw(1_000_000_000 * 1_000_000_000_000_000_000u128);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unit_delegation_didnt_change() {
// a sanity check test to make sure Decimal's `DECIMAL_FRACTIONAL` internal implementation hasn't changed
assert_eq!(
UNIT_DELEGATION_BASE,
Decimal::one() * Decimal::from_atomics(1_000_000_000u32, 0).unwrap()
)
}
}
@@ -1,151 +1,134 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
use crate::{Addr, IdentityKey};
use cosmwasm_std::{Coin, Uint128};
use crate::{Addr, NodeId};
use cosmwasm_std::{Coin, Decimal};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::hash::{Hash, Hasher};
type OwnerAddressBytes = Vec<u8>;
type BlockHeight = u64;
// just use a string representation of those so that we wouldn't need to bother with decoding bytes
// and trying to figure out whether they're valid, etc
pub type OwnerProxySubKey = String;
pub type StorageKey = (NodeId, OwnerProxySubKey);
pub fn generate_storage_key(address: &Addr, proxy: Option<&Addr>) -> Vec<u8> {
pub fn generate_owner_storage_subkey(address: &Addr, proxy: Option<&Addr>) -> String {
if let Some(proxy) = &proxy {
address
let key_bytes = address
.as_bytes()
.iter()
.zip(proxy.as_bytes())
.map(|(x, y)| x ^ y)
.collect()
.collect::<Vec<_>>();
bs58::encode(key_bytes).into_string()
} else {
address.as_bytes().to_vec()
address.clone().into_string()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
pub struct Delegation {
/// Address of the owner of this delegation.
pub owner: Addr,
pub node_identity: IdentityKey,
/// Id of the MixNode that this delegation was performed against.
pub node_id: NodeId,
// Note to UI/UX devs: there's absolutely no point in displaying this value to the users,
// it would serve them no purpose. It's only used for calculating rewards
/// Value of the "unit delegation" associated with the mixnode at the time of delegation.
#[serde(rename = "crr")]
pub cumulative_reward_ratio: Decimal,
/// Original delegation amount. Note that it is never mutated as delegation accumulates rewards.
pub amount: Coin,
pub block_height: u64,
pub proxy: Option<Addr>, // proxy address used to delegate the funds on behalf of another address
}
impl Eq for Delegation {}
/// Block height where this delegation occurred.
pub height: u64,
#[allow(clippy::derive_hash_xor_eq)]
impl Hash for Delegation {
fn hash<H: Hasher>(&self, state: &mut H) {
self.owner.hash(state);
self.node_identity.hash(state);
self.block_height.hash(state);
self.proxy.hash(state);
}
/// Proxy address used to delegate the funds on behalf of another address
pub proxy: Option<Addr>,
}
impl Delegation {
pub fn new(
owner: Addr,
node_identity: IdentityKey,
node_id: NodeId,
cumulative_reward_ratio: Decimal,
amount: Coin,
block_height: BlockHeight,
height: u64,
proxy: Option<Addr>,
) -> Self {
Delegation {
owner,
node_identity,
node_id,
cumulative_reward_ratio,
amount,
block_height,
height,
proxy,
}
}
pub fn storage_key(&self) -> (IdentityKey, OwnerAddressBytes, BlockHeight) {
(
self.node_identity(),
self.proxy_storage_key(),
self.block_height(),
)
pub fn generate_storage_key(
node_id: NodeId,
owner_address: &Addr,
proxy: Option<&Addr>,
) -> StorageKey {
(node_id, generate_owner_storage_subkey(owner_address, proxy))
}
pub fn event_storage_key(&self) -> (OwnerAddressBytes, BlockHeight, IdentityKey) {
(
self.proxy_storage_key(),
self.block_height(),
self.node_identity(),
)
// this function might seem a bit redundant, but I'd rather explicitly keep it around in case
// some types change in the future
pub fn generate_storage_key_with_subkey(
node_id: NodeId,
owner_proxy_subkey: OwnerProxySubKey,
) -> StorageKey {
(node_id, owner_proxy_subkey)
}
pub fn proxy_storage_key(&self) -> OwnerAddressBytes {
generate_storage_key(&self.owner, self.proxy.as_ref())
pub fn dec_amount(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed our base coin amount is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.amount.amount, 0).unwrap()
}
pub fn proxy(&self) -> Option<&Addr> {
self.proxy.as_ref()
pub fn proxy_storage_key(&self) -> OwnerProxySubKey {
generate_owner_storage_subkey(&self.owner, self.proxy.as_ref())
}
pub fn increment_amount(&mut self, amount: Uint128, at_height: Option<u64>) {
self.amount.amount += amount;
if let Some(at_height) = at_height {
self.block_height = at_height;
}
}
pub fn amount(&self) -> &Coin {
&self.amount
}
pub fn node_identity(&self) -> IdentityKey {
self.node_identity.clone()
}
pub fn owner(&self) -> Addr {
self.owner.clone()
}
pub fn block_height(&self) -> u64 {
self.block_height
pub fn storage_key(&self) -> StorageKey {
Self::generate_storage_key(self.node_id, &self.owner, self.proxy.as_ref())
}
}
impl Display for Delegation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} delegated towards {} by {} at block {}",
self.amount, self.node_identity, self.owner, self.block_height
)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedMixDelegationsResponse {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedMixNodeDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<(String, u64)>,
pub start_next_after: Option<OwnerProxySubKey>,
}
impl PagedMixDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<(String, u64)>) -> Self {
PagedMixDelegationsResponse {
impl PagedMixNodeDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<OwnerProxySubKey>) -> Self {
PagedMixNodeDelegationsResponse {
delegations,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedDelegatorDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<IdentityKey>,
pub start_next_after: Option<(NodeId, OwnerProxySubKey)>,
}
impl PagedDelegatorDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<IdentityKey>) -> Self {
pub fn new(
delegations: Vec<Delegation>,
start_next_after: Option<(NodeId, OwnerProxySubKey)>,
) -> Self {
PagedDelegatorDelegationsResponse {
delegations,
start_next_after,
@@ -153,20 +136,32 @@ impl PagedDelegatorDelegationsResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeDelegationResponse {
pub delegation: Option<Delegation>,
pub mixnode_still_bonded: bool,
}
impl PagedAllDelegationsResponse {
pub fn new(
delegations: Vec<Delegation>,
start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
) -> Self {
PagedAllDelegationsResponse {
delegations,
start_next_after: start_next_after.map(|(id, addr, height)| (id, addr, height)),
impl MixNodeDelegationResponse {
pub fn new(delegation: Option<Delegation>, mixnode_still_bonded: bool) -> Self {
MixNodeDelegationResponse {
delegation,
mixnode_still_bonded,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<StorageKey>,
}
impl PagedAllDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<StorageKey>) -> Self {
PagedAllDelegationsResponse {
delegations,
start_next_after,
}
}
}
@@ -1,23 +1,139 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::NodeId;
use cosmwasm_std::{Addr, Coin, Decimal};
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum MixnetContractError {
#[error("Overflow Error")]
OverflowError(#[from] cosmwasm_std::OverflowError),
#[error("reward_blockstamp field not set, set_reward_blockstamp must be called before attempting to issue rewards")]
BlockstampNotSet,
#[error("{source}")]
TryFromIntError {
#[from]
source: std::num::TryFromIntError,
},
#[error("Error casting from U128")]
CastError,
#[error("{source}")]
StdErr {
#[from]
source: cosmwasm_std::StdError,
},
#[error("Division by zero at {}", line!())]
DivisionByZero,
#[error("Provided percent value is greater than 100%")]
InvalidPercent,
#[error("Attempted to subtract decimals with overflow ({minuend}.sub({subtrahend}))")]
OverflowDecimalSubtraction {
minuend: Decimal,
subtrahend: Decimal,
},
#[error("Attempted to subtract with overflow ({minuend}.sub({subtrahend}))")]
OverflowSubtraction { minuend: u64, subtrahend: u64 },
#[error("Not enough funds sent for node pledge. (received {received}, minimum {minimum})")]
InsufficientPledge { received: Coin, minimum: Coin },
#[error("Not enough funds sent for node delegation. (received {received}, minimum {minimum})")]
InsufficientDelegation { received: Coin, minimum: Coin },
#[error("Mixnode ({id}) does not exist")]
MixNodeBondNotFound { id: NodeId },
#[error("{owner} does not seem to own any mixnodes")]
NoAssociatedMixNodeBond { owner: Addr },
#[error("{owner} does not seem to own any gateways")]
NoAssociatedGatewayBond { owner: Addr },
#[error("This address has already bonded a mixnode")]
AlreadyOwnsMixnode,
#[error("This address has already bonded a gateway")]
AlreadyOwnsGateway,
#[error("Gateway with this identity already exists. Its owner is {owner}")]
DuplicateGateway { owner: Addr },
#[error("Unauthorized")]
Unauthorized,
#[error("No tokens were sent for the bonding")]
NoBondFound,
#[error("No funds were provided for the delegation")]
EmptyDelegation,
#[error("Wrong coin denomination. Received: {received}, expected: {expected}")]
WrongDenom { received: String, expected: String },
#[error("Received multiple coin types during staking")]
MultipleDenoms,
#[error("Proxy address mismatch, expected {existing}, got {incoming}")]
ProxyMismatch { existing: String, incoming: String },
#[error("Failed to recover ed25519 public key from its base58 representation - {0}")]
MalformedEd25519IdentityKey(String),
#[error("Failed to recover ed25519 signature from its base58 representation - {0}")]
MalformedEd25519Signature(String),
#[error("Provided ed25519 signature did not verify correctly")]
InvalidEd25519Signature,
#[error("Can't perform the specified action as the current epoch is still progress. It started at {epoch_start} and finishes at {epoch_end}, while the current block time is {current_block_time}")]
EpochInProgress {
current_block_time: u64,
epoch_start: i64,
epoch_end: i64,
},
#[error("Mixnode {node_id} has already been rewarded during the current rewarding epoch ({absolute_epoch_id})")]
MixnodeAlreadyRewarded {
node_id: NodeId,
absolute_epoch_id: u32,
},
#[error("Mixnode {node_id} hasn't been selected to the rewarding set in this epoch ({absolute_epoch_id})")]
MixnodeNotInRewardedSet {
node_id: NodeId,
absolute_epoch_id: u32,
},
#[error("Mixnode {node_id} is currently in the process of unbonding")]
MixnodeIsUnbonding { node_id: NodeId },
#[error("Mixnode {node_id} has already unbonded")]
MixnodeHasUnbonded { node_id: NodeId },
#[error("The contract has ended up in a state that was deemed impossible: {comment}")]
InconsistentState { comment: String },
#[error(
"Could not find any delegation information associated with mixnode {mix_id} for {address} (proxy: {proxy:?})"
)]
NoMixnodeDelegationFound {
mix_id: NodeId,
address: String,
proxy: Option<String>,
},
#[error("Provided message to update rewarding params did not contain any updates")]
EmptyParamsChangeMsg,
#[error("Provided active set size is bigger than the rewarded set")]
InvalidActiveSetSize,
#[error("Provided rewarded set size is smaller than the active set")]
InvalidRewardedSetSize,
#[error("Provided active set size is zero")]
ZeroActiveSet,
#[error("Provided rewarded set size is zero")]
ZeroRewardedSet,
#[error("Received unexpected value for the active set. Got: {received}, expected: {expected}")]
UnexpectedActiveSetSize { received: u32, expected: u32 },
#[error("Received unexpected value for the rewarded set. Got: {received}, expected at most: {expected}")]
UnexpectedRewardedSetSize { received: u32, expected: u32 },
#[error("Mixnode {node_id} appears multiple times in the provided rewarded set update!")]
DuplicateRewardedSetNode { node_id: NodeId },
}
@@ -1,32 +1,87 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::NodeRewardResult;
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer};
use cosmwasm_std::{Addr, Coin, Event, Uint128};
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate};
use crate::rewarding::RewardDistribution;
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer, NodeId};
pub use contracts_common::events::*;
// FIXME: This should becoma an Enum
// event types
pub const DELEGATION_EVENT_TYPE: &str = "delegation";
pub const PENDING_DELEGATION_EVENT_TYPE: &str = "pending_delegation";
pub const RECONCILE_DELEGATION_EVENT_TYPE: &str = "reconcile_delegation";
pub const UNDELEGATION_EVENT_TYPE: &str = "undelegation";
pub const PENDING_UNDELEGATION_EVENT_TYPE: &str = "pending_undelegation";
pub const GATEWAY_BONDING_EVENT_TYPE: &str = "gateway_bonding";
pub const GATEWAY_UNBONDING_EVENT_TYPE: &str = "gateway_unbonding";
pub const MIXNODE_BONDING_EVENT_TYPE: &str = "mixnode_bonding";
pub const MIXNODE_UNBONDING_EVENT_TYPE: &str = "mixnode_unbonding";
pub const SETTINGS_UPDATE_EVENT_TYPE: &str = "settings_update";
pub const OPERATOR_REWARDING_EVENT_TYPE: &str = "mix_rewarding";
pub const MIX_DELEGATORS_REWARDING_EVENT_TYPE: &str = "mix_delegators_rewarding";
pub const CHANGE_REWARDED_SET_EVENT_TYPE: &str = "change_rewarded_set";
pub const ADVANCE_INTERVAL_EVENT_TYPE: &str = "advance_interval";
pub const ADVANCE_EPOCH_EVENT_TYPE: &str = "advance_epoch";
pub const COMPOUND_DELEGATOR_REWARD_EVENT_TYPE: &str = "compound_delegator_reward";
pub const CLAIM_DELEGATOR_REWARD_EVENT_TYPE: &str = "claim_delegator_reward";
pub const COMPOUND_OPERATOR_REWARD_EVENT_TYPE: &str = "compound_operator_reward";
pub const CLAIM_OPERATOR_REWARD_EVENT_TYPE: &str = "claim_operator_reward";
pub const SNAPSHOT_MIXNODES_EVENT: &str = "snapshot_mixnodes";
use cosmwasm_std::{Addr, Coin, Event};
pub enum MixnetEventType {
MixnodeBonding,
GatewayBonding,
GatewayUnbonding,
PendingMixnodeUnbonding,
MixnodeUnbonding,
MixnodeConfigUpdate,
PendingMixnodeCostParamsUpdate,
MixnodeCostParamsUpdate,
MixnodeRewarding,
WithdrawDelegatorReward,
WithdrawOperatorReward,
PendingActiveSetUpdate,
ActiveSetUpdate,
PendingIntervalRewardingParamsUpdate,
IntervalRewardingParamsUpdate,
PendingDelegation,
PendingUndelegation,
Delegation,
DelegationOnUnbonding,
Undelegation,
ContractSettingsUpdate,
RewardingValidatorUpdate,
AdvanceEpoch,
ExecutePendingEpochEvents,
ExecutePendingIntervalEvents,
ReconcilePendingEvents,
PendingIntervalConfigUpdate,
IntervalConfigUpdate,
}
impl From<MixnetEventType> for String {
fn from(typ: MixnetEventType) -> Self {
typ.to_string()
}
}
impl ToString for MixnetEventType {
fn to_string(&self) -> String {
match self {
MixnetEventType::MixnodeBonding => "mixnode_bonding",
MixnetEventType::GatewayBonding => "gateway_bonding",
MixnetEventType::GatewayUnbonding => "gateway_unbonding",
MixnetEventType::PendingMixnodeUnbonding => "pending_mixnode_unbonding",
MixnetEventType::MixnodeConfigUpdate => "mixnode_config_update",
MixnetEventType::MixnodeUnbonding => "mixnode_unbonding",
MixnetEventType::PendingMixnodeCostParamsUpdate => "pending_mixnode_cost_params_update",
MixnetEventType::MixnodeCostParamsUpdate => "mixnode_cost_params_update",
MixnetEventType::MixnodeRewarding => "mix_rewarding",
MixnetEventType::WithdrawDelegatorReward => "withdraw_delegator_reward",
MixnetEventType::WithdrawOperatorReward => "withdraw_operator_reward",
MixnetEventType::PendingActiveSetUpdate => "pending_active_set_update",
MixnetEventType::ActiveSetUpdate => "active_set_update",
MixnetEventType::PendingIntervalRewardingParamsUpdate => {
"pending_interval_rewarding_params_update"
}
MixnetEventType::IntervalRewardingParamsUpdate => "interval_rewarding_params_update",
MixnetEventType::PendingDelegation => "pending_delegation",
MixnetEventType::PendingUndelegation => "pending_undelegation",
MixnetEventType::Delegation => "delegation",
MixnetEventType::Undelegation => "undelegation",
MixnetEventType::ContractSettingsUpdate => "settings_update",
MixnetEventType::RewardingValidatorUpdate => "rewarding_validator_address_update",
MixnetEventType::AdvanceEpoch => "advance_epoch",
MixnetEventType::ExecutePendingEpochEvents => "execute_pending_epoch_events",
MixnetEventType::ExecutePendingIntervalEvents => "execute_pending_interval_events",
MixnetEventType::ReconcilePendingEvents => "reconcile_pending_events",
MixnetEventType::PendingIntervalConfigUpdate => "pending_interval_config_update",
MixnetEventType::IntervalConfigUpdate => "interval_config_update",
MixnetEventType::DelegationOnUnbonding => "delegation_on_unbonding_node",
}
.into()
}
}
// attributes that are used in multiple places
pub const OWNER_KEY: &str = "owner";
@@ -41,35 +96,42 @@ pub const DELEGATION_TARGET_KEY: &str = "delegation_target";
pub const DELEGATION_HEIGHT_KEY: &str = "delegation_latest_block_height";
// bonding/unbonding
pub const NODE_ID_KEY: &str = "node_id";
pub const NODE_IDENTITY_KEY: &str = "identity";
pub const ASSIGNED_LAYER_KEY: &str = "assigned_layer";
// settings change
pub const OLD_MINIMUM_MIXNODE_PLEDGE_KEY: &str = "old_minimum_mixnode_pledge";
pub const OLD_MINIMUM_GATEWAY_PLEDGE_KEY: &str = "old_minimum_gateway_pledge";
pub const OLD_MIXNODE_REWARDED_SET_SIZE_KEY: &str = "old_mixnode_rewarded_set_size";
pub const OLD_MIXNODE_ACTIVE_SET_SIZE_KEY: &str = "old_mixnode_active_set_size";
pub const OLD_ACTIVE_SET_WORK_FACTOR_KEY: &str = "old_active_set_work_factor";
pub const OLD_MINIMUM_DELEGATION_KEY: &str = "old_minimum_delegation";
pub const NEW_MINIMUM_MIXNODE_PLEDGE_KEY: &str = "new_minimum_mixnode_pledge";
pub const NEW_MINIMUM_GATEWAY_PLEDGE_KEY: &str = "new_minimum_gateway_pledge";
pub const NEW_MIXNODE_REWARDED_SET_SIZE_KEY: &str = "new_mixnode_rewarded_set_size";
pub const NEW_MIXNODE_ACTIVE_SET_SIZE_KEY: &str = "new_mixnode_active_set_size";
pub const NEW_MINIMUM_DELEGATION_KEY: &str = "new_minimum_delegation";
pub const OLD_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "old_rewarding_validator_address";
pub const NEW_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "new_rewarding_validator_address";
pub const UPDATED_MIXNODE_CONFIG_KEY: &str = "updated_mixnode_config";
pub const UPDATED_MIXNODE_COST_PARAMS_KEY: &str = "updated_mixnode_cost_params";
// rewarding
pub const INTERVAL_ID_KEY: &str = "interval_id";
pub const INTERVAL_KEY: &str = "interval_details";
pub const TOTAL_MIXNODE_REWARD_KEY: &str = "total_node_reward";
pub const OPERATOR_REWARD_KEY: &str = "operator_reward";
pub const TOTAL_PLEDGE_KEY: &str = "pledge";
pub const TOTAL_DELEGATIONS_KEY: &str = "delegated";
pub const LAMBDA_KEY: &str = "lambda";
pub const SIGMA_KEY: &str = "sigma";
pub const OPERATOR_REWARD_KEY: &str = "operator_reward";
pub const DELEGATES_REWARD_KEY: &str = "delegates_reward";
pub const APPROXIMATE_TIME_LEFT_SECS_KEY: &str = "approximate_time_left_secs";
pub const INTERVAL_REWARDING_PARAMS_UPDATE_KEY: &str = "interval_rewarding_params_update";
pub const UPDATED_INTERVAL_REWARDING_PARAMS_KEY: &str = "updated_interval_rewarding_params";
pub const DISTRIBUTED_DELEGATION_REWARDS_KEY: &str = "distributed_delegation_rewards";
pub const FURTHER_DELEGATIONS_TO_REWARD_KEY: &str = "further_delegations";
pub const NO_REWARD_REASON_KEY: &str = "no_reward_reason";
pub const BOND_NOT_FOUND_VALUE: &str = "bond_not_found";
pub const BOND_TOO_FRESH_VALUE: &str = "bond_too_fresh";
pub const ZERO_UPTIME_VALUE: &str = "zero_uptime";
pub const ZERO_PERFORMANCE_VALUE: &str = "zero_performance";
// rewarded set update
pub const ACTIVE_SET_SIZE_KEY: &str = "active_set_size";
@@ -80,156 +142,140 @@ pub const CURRENT_INTERVAL_ID_KEY: &str = "current_interval";
pub const NEW_CURRENT_INTERVAL_KEY: &str = "new_current_interval";
pub const NEW_CURRENT_EPOCH_KEY: &str = "new_current_epoch";
pub const BLOCK_HEIGHT_KEY: &str = "block_height";
pub const CHECKPOINT_MIXNODES_EVENT: &str = "checkpoint_mixnodes";
pub const RECONCILIATION_ERROR_EVENT: &str = "reconciliation_error";
pub fn new_checkpoint_mixnodes_event(block_height: u64) -> Event {
Event::new(CHECKPOINT_MIXNODES_EVENT)
.add_attribute(BLOCK_HEIGHT_KEY, format!("{}", block_height))
}
pub fn new_error_event(err: String) -> Event {
Event::new(RECONCILIATION_ERROR_EVENT).add_attribute("error", err)
}
// interval
pub const EVENTS_EXECUTED_KEY: &str = "number_of_events_executed";
pub const REWARDED_SET_NODES_KEY: &str = "rewarded_set_nodes";
pub const NEW_EPOCHS_DURATION_SECS_KEY: &str = "new_epoch_durations_secs";
pub const NEW_EPOCHS_IN_INTERVAL: &str = "new_epochs_in_interval";
pub fn new_delegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Event {
let mut event = Event::new(DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
Event::new(MixnetEventType::Delegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
}
pub fn new_delegation_on_unbonded_node_event(
delegator: &Addr,
proxy: &Option<Addr>,
mix_id: NodeId,
) -> Event {
Event::new(MixnetEventType::Delegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
}
pub fn new_pending_delegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Event {
let mut event =
Event::new(PENDING_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_reconcile_delegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(RECONCILE_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_compound_operator_reward_event(owner: &Addr, amount: Uint128) -> Event {
let event = Event::new(COMPOUND_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner);
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_claim_operator_reward_event(owner: &Addr, amount: Uint128) -> Event {
let event = Event::new(CLAIM_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner);
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_compound_delegator_reward_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: Uint128,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(COMPOUND_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
Event::new(MixnetEventType::PendingDelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
}
pub fn new_claim_delegator_reward_event(
pub fn new_withdraw_operator_reward_event(
owner: &Addr,
proxy: &Option<Addr>,
amount: Coin,
mix_id: NodeId,
) -> Event {
Event::new(MixnetEventType::WithdrawOperatorReward)
.add_attribute(OWNER_KEY, owner.as_str())
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(NODE_ID_KEY, mix_id.to_string())
}
pub fn new_withdraw_delegator_reward_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: Uint128,
mix_identity: IdentityKeyRef<'_>,
amount: Coin,
mix_id: NodeId,
) -> Event {
let mut event =
Event::new(CLAIM_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
Event::new(MixnetEventType::WithdrawDelegatorReward)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
}
pub fn new_undelegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
mix_identity: IdentityKeyRef<'_>,
amount: Uint128,
pub fn new_active_set_update_event(new_size: u32) -> Event {
Event::new(MixnetEventType::ActiveSetUpdate)
.add_attribute(ACTIVE_SET_SIZE_KEY, new_size.to_string())
}
pub fn new_pending_active_set_update_event(
new_size: u32,
approximate_time_remaining_secs: i64,
) -> Event {
let mut event = Event::new(UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
Event::new(MixnetEventType::PendingActiveSetUpdate)
.add_attribute(ACTIVE_SET_SIZE_KEY, new_size.to_string())
.add_attribute(
APPROXIMATE_TIME_LEFT_SECS_KEY,
approximate_time_remaining_secs.to_string(),
)
}
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
pub fn new_rewarding_params_update_event(
update: IntervalRewardingParamsUpdate,
updated: IntervalRewardParams,
) -> Event {
Event::new(MixnetEventType::IntervalRewardingParamsUpdate)
.add_attribute(
INTERVAL_REWARDING_PARAMS_UPDATE_KEY,
update.to_inline_json(),
)
.add_attribute(
UPDATED_INTERVAL_REWARDING_PARAMS_KEY,
updated.to_inline_json(),
)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
pub fn new_pending_rewarding_params_update_event(
update: IntervalRewardingParamsUpdate,
approximate_time_remaining_secs: i64,
) -> Event {
Event::new(MixnetEventType::PendingIntervalRewardingParamsUpdate)
.add_attribute(
INTERVAL_REWARDING_PARAMS_UPDATE_KEY,
update.to_inline_json(),
)
.add_attribute(
APPROXIMATE_TIME_LEFT_SECS_KEY,
approximate_time_remaining_secs.to_string(),
)
}
pub fn new_undelegation_event(delegator: &Addr, proxy: &Option<Addr>, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::Undelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(NODE_ID_KEY, mix_id.to_string())
}
pub fn new_pending_undelegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
mix_identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Event {
let mut event =
Event::new(PENDING_UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
Event::new(MixnetEventType::PendingUndelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(NODE_ID_KEY, mix_id.to_string())
}
pub fn new_gateway_bonding_event(
@@ -238,16 +284,11 @@ pub fn new_gateway_bonding_event(
amount: &Coin,
identity: IdentityKeyRef<'_>,
) -> Event {
let mut event = Event::new(GATEWAY_BONDING_EVENT_TYPE)
Event::new(MixnetEventType::GatewayBonding)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_gateway_unbonding_event(
@@ -256,16 +297,11 @@ pub fn new_gateway_unbonding_event(
amount: &Coin,
identity: IdentityKeyRef<'_>,
) -> Event {
let mut event = Event::new(GATEWAY_UNBONDING_EVENT_TYPE)
Event::new(MixnetEventType::GatewayUnbonding)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_bonding_event(
@@ -273,55 +309,92 @@ pub fn new_mixnode_bonding_event(
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
node_id: NodeId,
assigned_layer: Layer,
) -> Event {
let mut event = Event::new(MIXNODE_BONDING_EVENT_TYPE)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
Event::new(MixnetEventType::MixnodeBonding)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(ASSIGNED_LAYER_KEY, assigned_layer)
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_unbonding_event(
pub fn new_mixnode_unbonding_event(node_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeUnbonding).add_attribute(NODE_ID_KEY, node_id.to_string())
}
pub fn new_pending_mixnode_unbonding_event(
owner: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
node_id: NodeId,
) -> Event {
let mut event = Event::new(MIXNODE_UNBONDING_EVENT_TYPE)
Event::new(MixnetEventType::PendingMixnodeUnbonding)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity);
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
}
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
pub fn new_mixnode_config_update_event(
node_id: NodeId,
owner: &Addr,
proxy: &Option<Addr>,
update: &MixNodeConfigUpdate,
) -> Event {
Event::new(MixnetEventType::MixnodeConfigUpdate)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_MIXNODE_CONFIG_KEY, update.to_inline_json())
}
// coin implements Display trait and we use that implementation here
event.add_attribute(AMOUNT_KEY, amount.to_string())
pub fn new_mixnode_pending_cost_params_update_event(
node_id: NodeId,
owner: &Addr,
proxy: &Option<Addr>,
new_costs: &MixNodeCostParams,
) -> Event {
Event::new(MixnetEventType::PendingMixnodeCostParamsUpdate)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
}
pub fn new_mixnode_cost_params_update_event(
node_id: NodeId,
new_costs: &MixNodeCostParams,
) -> Event {
Event::new(MixnetEventType::MixnodeCostParamsUpdate)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
}
pub fn new_rewarding_validator_address_update_event(old: Addr, new: Addr) -> Event {
Event::new(MixnetEventType::RewardingValidatorUpdate)
.add_attribute(OLD_REWARDING_VALIDATOR_ADDRESS_KEY, old)
.add_attribute(NEW_REWARDING_VALIDATOR_ADDRESS_KEY, new)
}
pub fn new_settings_update_event(
old_params: &ContractStateParams,
new_params: &ContractStateParams,
) -> Event {
let mut event = Event::new(SETTINGS_UPDATE_EVENT_TYPE);
let mut event = Event::new(MixnetEventType::ContractSettingsUpdate);
if old_params.minimum_mixnode_pledge != new_params.minimum_mixnode_pledge {
event = event
.add_attribute(
OLD_MINIMUM_MIXNODE_PLEDGE_KEY,
old_params.minimum_mixnode_pledge,
old_params.minimum_mixnode_pledge.to_string(),
)
.add_attribute(
NEW_MINIMUM_MIXNODE_PLEDGE_KEY,
new_params.minimum_mixnode_pledge,
new_params.minimum_mixnode_pledge.to_string(),
)
}
@@ -329,128 +402,128 @@ pub fn new_settings_update_event(
event = event
.add_attribute(
OLD_MINIMUM_GATEWAY_PLEDGE_KEY,
old_params.minimum_gateway_pledge,
old_params.minimum_gateway_pledge.to_string(),
)
.add_attribute(
NEW_MINIMUM_GATEWAY_PLEDGE_KEY,
new_params.minimum_gateway_pledge,
new_params.minimum_gateway_pledge.to_string(),
)
}
if old_params.mixnode_rewarded_set_size != new_params.mixnode_rewarded_set_size {
event = event
.add_attribute(
OLD_MIXNODE_REWARDED_SET_SIZE_KEY,
old_params.mixnode_rewarded_set_size.to_string(),
)
.add_attribute(
NEW_MIXNODE_REWARDED_SET_SIZE_KEY,
new_params.mixnode_rewarded_set_size.to_string(),
)
}
if old_params.mixnode_active_set_size != new_params.mixnode_active_set_size {
event = event
.add_attribute(
OLD_MIXNODE_ACTIVE_SET_SIZE_KEY,
old_params.mixnode_active_set_size.to_string(),
)
.add_attribute(
NEW_MIXNODE_ACTIVE_SET_SIZE_KEY,
new_params.mixnode_active_set_size.to_string(),
)
if old_params.minimum_mixnode_delegation != new_params.minimum_mixnode_delegation {
if let Some(ref old) = old_params.minimum_mixnode_delegation {
event = event.add_attribute(OLD_MINIMUM_DELEGATION_KEY, old.to_string())
} else {
event = event.add_attribute(OLD_MINIMUM_DELEGATION_KEY, "None")
}
if let Some(ref new) = new_params.minimum_mixnode_delegation {
event = event.add_attribute(NEW_MINIMUM_DELEGATION_KEY, new.to_string())
} else {
event = event.add_attribute(NEW_MINIMUM_DELEGATION_KEY, "None")
}
}
event
}
pub fn new_not_found_mix_operator_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
pub fn new_not_found_mix_operator_rewarding_event(interval: Interval, node_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NO_REWARD_REASON_KEY, BOND_NOT_FOUND_VALUE)
}
pub fn new_too_fresh_bond_mix_operator_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(NO_REWARD_REASON_KEY, BOND_TOO_FRESH_VALUE)
}
pub fn new_zero_uptime_mix_operator_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(NO_REWARD_REASON_KEY, ZERO_UPTIME_VALUE)
}
#[allow(clippy::too_many_arguments)]
pub fn new_mix_operator_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
node_reward_result: NodeRewardResult,
node_pledge: Uint128,
node_delegation: Uint128,
) -> Event {
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(TOTAL_PLEDGE_KEY, node_pledge)
.add_attribute(TOTAL_DELEGATIONS_KEY, node_delegation)
pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, node_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
.add_attribute(
TOTAL_MIXNODE_REWARD_KEY,
node_reward_result.reward().to_string(),
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(LAMBDA_KEY, node_reward_result.lambda().to_string())
.add_attribute(SIGMA_KEY, node_reward_result.sigma().to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NO_REWARD_REASON_KEY, ZERO_PERFORMANCE_VALUE)
}
pub fn new_mix_delegators_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
delegation_rewards_distributed: Uint128,
further_delegations: bool,
pub fn new_mix_rewarding_event(
interval: Interval,
node_id: NodeId,
reward_distribution: RewardDistribution,
) -> Event {
Event::new(MIX_DELEGATORS_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
Event::new(MixnetEventType::MixnodeRewarding)
// TODO: to calculate Timmy's (delegator) reward at this time
// emit:
// - unit delegation BEFORE rewarding (to determine Timmy's state before rewarding happened)
// - total delegation BEFORE rewarding
.add_attribute(
DISTRIBUTED_DELEGATION_REWARDS_KEY,
delegation_rewards_distributed,
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(
OPERATOR_REWARD_KEY,
reward_distribution.operator.to_string(),
)
.add_attribute(
FURTHER_DELEGATIONS_TO_REWARD_KEY,
further_delegations.to_string(),
DELEGATES_REWARD_KEY,
reward_distribution.delegates.to_string(),
)
}
// note that when this event is emitted, we'll know the current block height
pub fn new_change_rewarded_set_event(
active_set_size: u32,
rewarded_set_size: u32,
nodes_in_rewarded_set: u32,
pub fn new_advance_epoch_event(interval: Interval, rewarded_nodes: u32) -> Event {
Event::new(MixnetEventType::AdvanceEpoch)
.add_attribute(
NEW_CURRENT_EPOCH_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(REWARDED_SET_NODES_KEY, rewarded_nodes.to_string())
}
pub fn new_pending_epoch_events_execution_event(executed: u32) -> Event {
Event::new(MixnetEventType::ExecutePendingEpochEvents)
.add_attribute(EVENTS_EXECUTED_KEY, executed.to_string())
}
pub fn new_pending_interval_events_execution_event(executed: u32) -> Event {
Event::new(MixnetEventType::ExecutePendingIntervalEvents)
.add_attribute(EVENTS_EXECUTED_KEY, executed.to_string())
}
pub fn new_reconcile_pending_events() -> Event {
Event::new(MixnetEventType::ReconcilePendingEvents)
}
pub fn new_interval_config_update_event(
epochs_in_interval: u32,
epoch_duration_secs: u64,
updated_rewarding_params: IntervalRewardParams,
) -> Event {
Event::new(CHANGE_REWARDED_SET_EVENT_TYPE)
.add_attribute(ACTIVE_SET_SIZE_KEY, active_set_size.to_string())
.add_attribute(REWARDED_SET_SIZE_KEY, rewarded_set_size.to_string())
.add_attribute(NODES_IN_REWARDED_SET_KEY, nodes_in_rewarded_set.to_string())
Event::new(MixnetEventType::IntervalConfigUpdate)
.add_attribute(
NEW_EPOCHS_DURATION_SECS_KEY,
epoch_duration_secs.to_string(),
)
.add_attribute(NEW_EPOCHS_IN_INTERVAL, epochs_in_interval.to_string())
.add_attribute(
UPDATED_INTERVAL_REWARDING_PARAMS_KEY,
updated_rewarding_params.to_inline_json(),
)
}
pub fn new_advance_interval_event(interval: Interval) -> Event {
Event::new(ADVANCE_INTERVAL_EVENT_TYPE)
.add_attribute(NEW_CURRENT_INTERVAL_KEY, interval.to_string())
}
pub fn new_advance_epoch_event(interval: Interval) -> Event {
Event::new(ADVANCE_EPOCH_EVENT_TYPE).add_attribute(NEW_CURRENT_EPOCH_KEY, interval.to_string())
pub fn new_pending_interval_config_update_event(
epochs_in_interval: u32,
epoch_duration_secs: u64,
approximate_time_remaining_secs: i64,
) -> Event {
Event::new(MixnetEventType::PendingIntervalConfigUpdate)
.add_attribute(
NEW_EPOCHS_DURATION_SECS_KEY,
epoch_duration_secs.to_string(),
)
.add_attribute(NEW_EPOCHS_IN_INTERVAL, epochs_in_interval.to_string())
.add_attribute(
APPROXIMATE_TIME_LEFT_SECS_KEY,
approximate_time_remaining_secs.to_string(),
)
}
@@ -1,3 +1,6 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
@@ -1,12 +1,13 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::pending_events::{PendingEpochEvent, PendingIntervalEvent};
use crate::{EpochId, IntervalId};
use cosmwasm_std::Env;
use schemars::gen::SchemaGenerator;
use schemars::schema::{InstanceType, Schema, SchemaObject};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::fmt::{Display, Formatter};
use std::time::Duration;
use time::OffsetDateTime;
@@ -59,12 +60,16 @@ pub(crate) mod string_rfc3339_offset_date_time {
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct Interval {
id: u32,
id: IntervalId,
epochs_in_interval: u32,
#[serde(with = "string_rfc3339_offset_date_time")]
start: OffsetDateTime,
length: Duration,
current_epoch_start: OffsetDateTime,
current_epoch_id: EpochId,
epoch_length: Duration,
total_elapsed_epochs: EpochId,
}
impl JsonSchema for Interval {
@@ -81,20 +86,46 @@ impl JsonSchema for Interval {
let object_validation = schema_object.object();
object_validation
.properties
.insert("id".to_owned(), gen.subschema_for::<u32>());
.insert("id".to_owned(), gen.subschema_for::<IntervalId>());
object_validation.required.insert("id".to_owned());
object_validation
.properties
.insert("epochs_in_interval".to_owned(), gen.subschema_for::<u32>());
object_validation
.required
.insert("epochs_in_interval".to_owned());
// PrimitiveDateTime does not implement JsonSchema. However it has a custom
// serialization to string, so we just specify the schema to be String.
object_validation.properties.insert(
"current_epoch_start".to_owned(),
gen.subschema_for::<String>(),
);
object_validation
.properties
.insert("start".to_owned(), gen.subschema_for::<String>());
object_validation.required.insert("start".to_owned());
.required
.insert("current_epoch_start".to_owned());
object_validation.properties.insert(
"current_epoch_id".to_owned(),
gen.subschema_for::<EpochId>(),
);
object_validation
.required
.insert("current_epoch_id".to_owned());
object_validation
.properties
.insert("length".to_owned(), gen.subschema_for::<Duration>());
object_validation.required.insert("length".to_owned());
.insert("epoch_length".to_owned(), gen.subschema_for::<Duration>());
object_validation.required.insert("epoch_length".to_owned());
object_validation.properties.insert(
"total_elapsed_epochs".to_owned(),
gen.subschema_for::<EpochId>(),
);
object_validation
.required
.insert("total_elapsed_epochs".to_owned());
Schema::Object(schema_object)
}
@@ -102,328 +133,451 @@ impl JsonSchema for Interval {
impl Interval {
/// Initialize epoch in the contract with default values.
pub fn init_epoch(env: Env) -> Self {
pub fn init_interval(epochs_in_interval: u32, epoch_length: Duration, env: &Env) -> Self {
Interval {
id: 0,
epochs_in_interval,
// I really don't see a way for this to fail, unless the blockchain is lying to us
start: OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64)
.expect("Invalid timestamp from env.block.time"),
length: Duration::from_secs(3600),
current_epoch_start: OffsetDateTime::from_unix_timestamp(
env.block.time.seconds() as i64
)
.expect("Invalid timestamp from env.block.time"),
current_epoch_id: 0,
epoch_length,
total_elapsed_epochs: 0,
}
}
pub fn is_over(&self, env: Env) -> bool {
self.end_unix_timestamp() <= env.block.time.seconds() as i64
pub const fn current_epoch_id(&self) -> EpochId {
self.current_epoch_id
}
pub fn in_progress(&self, env: Env) -> bool {
let block_time = env.block.time.seconds() as i64;
self.start_unix_timestamp() <= block_time && block_time < self.end_unix_timestamp()
}
pub fn update_duration(&mut self, secs: u64) {
self.length = Duration::from_secs(secs);
}
pub const fn length_secs(&self) -> u64 {
self.length.as_secs()
}
/// Returns the next interval.
#[must_use]
pub fn next(&self) -> Self {
Interval {
id: self.id + 1,
start: self.end(),
length: self.length,
}
}
pub fn next_on_chain(&self, env: Env) -> Self {
let start = self
.end()
.max(OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap());
Interval {
id: self.id + 1,
start,
length: self.length,
}
}
/// Returns the last interval.
pub fn previous(&self) -> Option<Self> {
if self.id > 0 {
Some(Interval {
id: self.id - 1,
start: self.start - self.length,
length: self.length,
})
} else {
None
}
}
/// Determines whether the provided datetime is contained within the interval
///
/// # Arguments
///
/// * `datetime`: specified datetime
pub fn contains(&self, datetime: OffsetDateTime) -> bool {
self.start <= datetime && datetime <= self.end()
}
/// Determines whether the provided unix timestamp is contained within the interval
///
/// # Arguments
///
/// * `timestamp`: specified timestamp
pub fn contains_timestamp(&self, timestamp: i64) -> bool {
self.start_unix_timestamp() <= timestamp && timestamp <= self.end_unix_timestamp()
}
/// Returns new instance of [Interval] such that the provided datetime would be within
/// its duration.
///
/// # Arguments
///
/// * `now`: current datetime
pub fn current(&self, now: OffsetDateTime) -> Option<Self> {
let mut candidate = *self;
if now > self.start {
loop {
if candidate.contains(now) {
return Some(candidate);
}
candidate = candidate.next();
}
} else {
loop {
if candidate.contains(now) {
return Some(candidate);
}
candidate = candidate.previous()?;
}
}
}
/// Returns new instance of [Interval] such that the provided unix timestamp would be within
/// its duration.
///
/// # Arguments
///
/// * `now_unix`: current unix time
pub fn current_with_timestamp(&self, now_unix: i64) -> Option<Self> {
let mut candidate = *self;
if now_unix > self.start_unix_timestamp() {
loop {
if candidate.contains_timestamp(now_unix) {
return Some(candidate);
}
candidate = candidate.next();
}
} else {
loop {
if candidate.contains_timestamp(now_unix) {
return Some(candidate);
}
candidate = candidate.previous()?;
}
}
}
/// Checks whether this interval has already finished
///
/// # Arguments
///
/// * `now`: current datetime
pub fn has_elapsed(&self, now: OffsetDateTime) -> bool {
self.end() < now
}
/// Returns id of this interval
pub const fn id(&self) -> u32 {
pub const fn current_interval_id(&self) -> IntervalId {
self.id
}
/// Determines amount of time left until this interval finishes.
///
/// # Arguments
///
/// * `now`: current datetime
pub fn until_end(&self, now: OffsetDateTime) -> Option<Duration> {
let remaining = self.end() - now;
if remaining.is_negative() {
None
pub const fn epochs_in_interval(&self) -> u32 {
self.epochs_in_interval
}
pub fn force_change_epochs_in_interval(&mut self, epochs_in_interval: u32) {
self.epochs_in_interval = epochs_in_interval;
if self.current_epoch_id >= epochs_in_interval {
// we have to go to the next interval as we can't
// have the same (interval, epoch) combo as we had in the past
self.id += self.current_epoch_id / epochs_in_interval;
self.current_epoch_id %= epochs_in_interval;
}
}
pub fn change_epoch_length(&mut self, epoch_length: Duration) {
self.epoch_length = epoch_length
}
pub const fn total_elapsed_epochs(&self) -> u32 {
self.total_elapsed_epochs
}
pub const fn current_epoch_absolute_id(&self) -> u32 {
// since we count epochs starting from 0, if n epochs have elapsed, the current one has absolute id of n
self.total_elapsed_epochs
}
#[inline]
pub fn is_current_epoch_over(&self, env: &Env) -> bool {
self.current_epoch_end_unix_timestamp() <= env.block.time.seconds() as i64
}
pub fn secs_until_current_epoch_end(&self, env: &Env) -> i64 {
if self.is_current_epoch_over(env) {
0
} else {
remaining.try_into().ok()
self.current_epoch_end_unix_timestamp() - env.block.time.seconds() as i64
}
}
#[inline]
pub fn is_current_interval_over(&self, env: &Env) -> bool {
self.current_interval_end_unix_timestamp() <= env.block.time.seconds() as i64
}
pub fn secs_until_current_interval_end(&self, env: &Env) -> i64 {
if self.is_current_interval_over(env) {
0
} else {
self.current_interval_end_unix_timestamp() - env.block.time.seconds() as i64
}
}
pub fn current_epoch_in_progress(&self, env: &Env) -> bool {
let block_time = env.block.time.seconds() as i64;
self.current_epoch_start_unix_timestamp() <= block_time
&& block_time < self.current_epoch_end_unix_timestamp()
}
pub fn update_epoch_duration(&mut self, secs: u64) {
self.epoch_length = Duration::from_secs(secs);
}
pub const fn epoch_length_secs(&self) -> u64 {
self.epoch_length.as_secs()
}
/// Returns the next epoch. If if would result in advancing the interval,
/// the relevant changes are applied.
#[must_use]
pub fn advance_epoch(&self) -> Self {
// remember we start from 0th epoch, so if we're supposed to have 100 epochs in interval,
// epoch 99 is going to be the last one
if self.current_epoch_id == self.epochs_in_interval - 1 {
Interval {
id: self.id + 1,
epochs_in_interval: self.epochs_in_interval,
current_epoch_start: self.current_epoch_end(),
current_epoch_id: 0,
epoch_length: self.epoch_length,
total_elapsed_epochs: self.total_elapsed_epochs + 1,
}
} else {
Interval {
id: self.id,
epochs_in_interval: self.epochs_in_interval,
current_epoch_start: self.current_epoch_end(),
current_epoch_id: self.current_epoch_id + 1,
epoch_length: self.epoch_length,
total_elapsed_epochs: self.total_elapsed_epochs + 1,
}
}
}
/// Returns the starting datetime of this interval.
pub const fn start(&self) -> OffsetDateTime {
self.start
pub const fn current_epoch_start(&self) -> OffsetDateTime {
self.current_epoch_start
}
/// Returns the length of this interval.
pub const fn length(&self) -> Duration {
self.length
pub const fn epoch_length(&self) -> Duration {
self.epoch_length
}
/// Returns the ending datetime of this interval.
pub fn end(&self) -> OffsetDateTime {
self.start + self.length
/// Returns the ending datetime of the current epoch.
pub fn current_epoch_end(&self) -> OffsetDateTime {
self.current_epoch_start + self.epoch_length
}
/// Returns the unix timestamp of the start of this interval.
pub const fn start_unix_timestamp(&self) -> i64 {
self.start().unix_timestamp()
pub fn epochs_until_interval_end(&self) -> u32 {
self.epochs_in_interval - self.current_epoch_id
}
/// Returns the unix timestamp of the end of this interval.
pub fn end_unix_timestamp(&self) -> i64 {
self.end().unix_timestamp()
/// Returns the ending datetime of the current interval.
pub fn current_interval_end(&self) -> OffsetDateTime {
self.current_epoch_start + self.epochs_until_interval_end() * self.epoch_length
}
/// Returns the unix timestamp of the start of the current epoch.
pub const fn current_epoch_start_unix_timestamp(&self) -> i64 {
self.current_epoch_start().unix_timestamp()
}
/// Returns the unix timestamp of the end of the current epoch.
#[inline]
pub fn current_epoch_end_unix_timestamp(&self) -> i64 {
self.current_epoch_end().unix_timestamp()
}
/// Returns the unix timestamp of the end of the current interval.
#[inline]
pub fn current_interval_end_unix_timestamp(&self) -> i64 {
self.current_interval_end().unix_timestamp()
}
}
impl Display for Interval {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let length = self.length().as_secs();
let length = self.epoch_length_secs();
let full_hours = length / 3600;
let rem = length % 3600;
write!(
f,
"Interval {}: {} - {} ({}h {}s)",
"Interval {}: epoch {}/{} (current epoch begun at: {}; epoch lengths: {}h {}s)",
self.id,
self.start(),
self.end(),
self.current_epoch_id + 1,
self.epochs_in_interval,
self.current_epoch_start,
full_hours,
rem
)
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct CurrentIntervalResponse {
pub interval: Interval,
pub current_blocktime: u64,
pub is_current_interval_over: bool,
pub is_current_epoch_over: bool,
}
impl CurrentIntervalResponse {
pub fn new(interval: Interval, env: Env) -> Self {
CurrentIntervalResponse {
interval,
current_blocktime: env.block.time.seconds(),
is_current_interval_over: interval.is_current_interval_over(&env),
is_current_epoch_over: interval.is_current_epoch_over(&env),
}
}
pub fn time_until_current_epoch_end(&self) -> Duration {
if self.is_current_epoch_over {
Duration::from_secs(0)
} else {
let remaining_secs =
self.interval.current_epoch_end_unix_timestamp() - self.current_blocktime as i64;
// this should never be negative, but better safe than sorry and guard ourselves against that case
if remaining_secs <= 0 {
Duration::from_secs(0)
} else {
Duration::from_secs(remaining_secs as u64)
}
}
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PendingEpochEventsResponse {
pub seconds_until_executable: i64,
pub events: Vec<PendingEpochEvent>,
pub start_next_after: Option<u32>,
}
impl PendingEpochEventsResponse {
pub fn new(
seconds_until_executable: i64,
events: Vec<PendingEpochEvent>,
start_next_after: Option<u32>,
) -> Self {
PendingEpochEventsResponse {
seconds_until_executable,
events,
start_next_after,
}
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PendingIntervalEventsResponse {
pub seconds_until_executable: i64,
pub events: Vec<PendingIntervalEvent>,
pub start_next_after: Option<u32>,
}
impl PendingIntervalEventsResponse {
pub fn new(
seconds_until_executable: i64,
events: Vec<PendingIntervalEvent>,
start_next_after: Option<u32>,
) -> Self {
PendingIntervalEventsResponse {
seconds_until_executable,
events,
start_next_after,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::mock_env;
use rand_chacha::rand_core::{RngCore, SeedableRng};
#[test]
fn previous() {
fn advancing_epoch() {
// just advancing epoch
let interval = Interval {
id: 1,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
id: 0,
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 12:00 UTC),
current_epoch_id: 23,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 0,
};
let expected = Interval {
id: 0,
start: time::macros::datetime!(2021-08-22 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 13:00 UTC),
current_epoch_id: 24,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 1,
};
assert_eq!(expected, interval.previous().unwrap());
assert_eq!(expected, interval.advance_epoch());
let genesis_interval = Interval {
id: 0,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
};
assert!(genesis_interval.previous().is_none());
}
#[test]
fn next() {
// results in advancing interval
let interval = Interval {
id: 0,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 12:00 UTC),
current_epoch_id: 99,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 42,
};
let expected = Interval {
id: 1,
start: time::macros::datetime!(2021-08-24 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 13:00 UTC),
current_epoch_id: 0,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 43,
};
assert_eq!(expected, interval.next())
assert_eq!(expected, interval.advance_epoch())
}
#[test]
fn checking_for_datetime_inclusion() {
fn checking_for_epoch_ends() {
let env = mock_env();
// epoch just begun
let interval = Interval {
id: 100,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
id: 0,
epochs_in_interval: 100,
current_epoch_start: OffsetDateTime::from_unix_timestamp(
env.block.time.seconds() as i64 - 100,
)
.unwrap(),
current_epoch_id: 23,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 0,
};
assert!(!interval.is_current_epoch_over(&env));
// it must contain its own boundaries
assert!(interval.contains(interval.start));
assert!(interval.contains(interval.end()));
// current time == current epoch start
let mut interval = interval;
interval.current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap();
assert!(!interval.is_current_epoch_over(&env));
let in_the_midle = interval.start + Duration::from_secs(interval.length.as_secs() / 2);
assert!(interval.contains(in_the_midle));
// epoch HASN'T yet begun (weird edge case, but can happen if we decide to manually adjust things)
let mut interval = interval;
interval.current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64 + 100).unwrap();
assert!(!interval.is_current_epoch_over(&env));
assert!(!interval.contains(interval.next().end()));
assert!(!interval.contains(interval.previous().unwrap().start()));
// current_time = EXACTLY end of the epoch
let mut interval = interval;
interval.current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap()
- interval.epoch_length;
assert!(interval.is_current_epoch_over(&env));
// revert time a bit more
interval.current_epoch_start -= Duration::from_secs(42);
assert!(interval.is_current_epoch_over(&env));
// revert by A LOT -> epoch still should be in finished state
interval.current_epoch_start -= Duration::from_secs(5 * 31 * 60 * 60);
assert!(interval.is_current_epoch_over(&env));
}
#[test]
fn determining_current_interval() {
let first_interval = Interval {
id: 100,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
fn interval_end() {
let mut interval = Interval {
id: 0,
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 12:00 UTC),
current_epoch_id: 99,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 0,
};
// interval just before
let fake_now = first_interval.start - Duration::from_secs(123);
assert_eq!(first_interval.previous(), first_interval.current(fake_now));
// this interval (start boundary)
assert_eq!(
first_interval,
first_interval.current(first_interval.start).unwrap()
interval.current_epoch_start + interval.epoch_length,
interval.current_interval_end()
);
// this interval (in the middle)
let fake_now = first_interval.start + Duration::from_secs(123);
assert_eq!(first_interval, first_interval.current(fake_now).unwrap());
// this interval (end boundary)
interval.current_epoch_id -= 1;
assert_eq!(
first_interval,
first_interval.current(first_interval.end()).unwrap()
interval.current_epoch_start + 2 * interval.epoch_length,
interval.current_interval_end()
);
// next interval
let fake_now = first_interval.end() + Duration::from_secs(123);
interval.current_epoch_id -= 10;
assert_eq!(
first_interval.next(),
first_interval.current(fake_now).unwrap()
interval.current_epoch_start + 12 * interval.epoch_length,
interval.current_interval_end()
);
// few intervals in the past
let fake_now = first_interval.start()
- first_interval.length
- first_interval.length
- first_interval.length;
interval.current_epoch_id = 0;
assert_eq!(
first_interval
.previous()
.unwrap()
.previous()
.unwrap()
.previous()
.unwrap(),
first_interval.current(fake_now).unwrap()
interval.current_epoch_start + interval.epochs_in_interval * interval.epoch_length,
interval.current_interval_end()
);
}
// few intervals in the future
let fake_now = first_interval.end()
+ first_interval.length
+ first_interval.length
+ first_interval.length;
assert_eq!(
first_interval.next().next().next(),
first_interval.current(fake_now).unwrap()
);
#[test]
fn checking_for_interval_ends() {
let env = mock_env();
let epoch_length = Duration::from_secs(60 * 60);
let mut interval = Interval {
id: 0,
epochs_in_interval: 100,
current_epoch_start: OffsetDateTime::from_unix_timestamp(
env.block.time.seconds() as i64
)
.unwrap(),
current_epoch_id: 98,
epoch_length,
total_elapsed_epochs: 0,
};
// current epoch just started (we still have to finish 2 epochs)
assert!(!interval.is_current_interval_over(&env));
// still need to finish the 99th epoch
interval.current_epoch_start -= epoch_length;
assert!(!interval.is_current_interval_over(&env));
// it JUST finished
interval.current_epoch_start -= epoch_length;
assert!(interval.is_current_interval_over(&env));
// nobody updated the interval data, but the current one should still be in finished state
interval.current_epoch_start -= 10 * epoch_length;
assert!(interval.is_current_interval_over(&env));
}
#[test]
fn getting_current_full_epoch_id() {
let env = mock_env();
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let epoch_length = Duration::from_secs(60 * 60);
let mut interval = Interval::init_interval(100, epoch_length, &env);
// normal situation
for i in 0u32..2000 {
assert_eq!(interval.current_epoch_absolute_id(), i);
interval = interval.advance_epoch();
}
let mut interval = Interval::init_interval(100, epoch_length, &env);
for i in 0u32..2000 {
// every few epochs decide to change epochs in interval
if i % 7 == 0 {
let new_epochs_in_interval = (rng.next_u32() % 200) + 42;
interval.force_change_epochs_in_interval(new_epochs_in_interval)
}
// make sure full epoch id is always monotonically increasing
assert_eq!(interval.current_epoch_absolute_id(), i);
interval = interval.advance_epoch();
}
}
}
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod constants;
pub mod delegation;
pub mod error;
pub mod events;
@@ -8,29 +9,31 @@ pub mod gateway;
mod interval;
pub mod mixnode;
mod msg;
pub mod pending_events;
pub mod reward_params;
pub mod rewarding;
mod types;
pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
pub use cosmwasm_std::{Addr, Coin};
pub use contracts_common::types::*;
pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction};
pub use delegation::{
Delegation, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
PagedMixDelegationsResponse,
PagedMixNodeDelegationsResponse,
};
pub use gateway::{
Gateway, GatewayBond, GatewayBondResponse, GatewayOwnershipResponse, PagedGatewayResponse,
};
pub use interval::Interval;
pub use interval::{
CurrentIntervalResponse, Interval, PendingEpochEventsResponse, PendingIntervalEventsResponse,
};
pub use mixnode::{
Layer, MixNode, MixNodeBond, MixOwnershipResponse, MixnodeBondResponse, PagedMixnodeResponse,
RewardedSetNodeStatus,
Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails,
MixNodeRewarding, MixOwnershipResponse, MixnodeDetailsResponse, PagedMixnodeBondsResponse,
RewardedSetNodeStatus, UnbondedMixnode,
};
pub use msg::*;
pub use pending_events::{
PendingEpochEvent, PendingEpochEventData, PendingIntervalEvent, PendingIntervalEventData,
};
pub use reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate, RewardingParams};
pub use types::*;
pub type U128 = fixed::types::U75F53;
fixed::const_fixed_from_int! {
const ONE: U128 = 1;
}
File diff suppressed because it is too large Load Diff
@@ -1,160 +1,342 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::reward_params::NodeRewardParams;
use crate::ContractStateParams;
use crate::delegation::OwnerProxySubKey;
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
};
use crate::{delegation, ContractStateParams, NodeId, Percent};
use crate::{Gateway, IdentityKey, MixNode};
use cosmwasm_std::Decimal;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct InstantiateMsg {
pub rewarding_validator_address: String,
pub mixnet_denom: String,
pub vesting_contract_address: String,
pub rewarding_denom: String,
pub epochs_in_interval: u32,
pub epoch_duration: Duration,
pub initial_rewarding_params: InitialRewardingParams,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct InitialRewardingParams {
pub initial_reward_pool: Decimal,
pub initial_staking_supply: Decimal,
pub sybil_resistance: Percent,
pub active_set_work_factor: Decimal,
pub interval_pool_emission: Percent,
pub rewarded_set_size: u32,
pub active_set_size: u32,
}
impl InitialRewardingParams {
pub fn into_rewarding_params(self, epochs_in_interval: u32) -> RewardingParams {
let epoch_reward_budget = self.initial_reward_pool
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
* self.interval_pool_emission;
let stake_saturation_point =
self.initial_staking_supply / Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
RewardingParams {
interval: IntervalRewardParams {
reward_pool: self.initial_reward_pool,
staking_supply: self.initial_staking_supply,
epoch_reward_budget,
stake_saturation_point,
sybil_resistance: self.sybil_resistance,
active_set_work_factor: self.active_set_work_factor,
interval_pool_emission: self.interval_pool_emission,
},
rewarded_set_size: self.rewarded_set_size,
active_set_size: self.active_set_size,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
// state/sys-params-related
UpdateRewardingValidatorAddress {
address: String,
},
InitEpoch {},
ReconcileDelegations {},
CheckpointMixnodes {},
CompoundOperatorRewardOnBehalf {
owner: String,
UpdateContractStateParams {
updated_parameters: ContractStateParams,
},
CompoundDelegatorRewardOnBehalf {
owner: String,
mix_identity: IdentityKey,
UpdateActiveSetSize {
active_set_size: u32,
force_immediately: bool,
},
CompoundOperatorReward {},
CompoundDelegatorReward {
mix_identity: IdentityKey,
UpdateRewardingParams {
updated_params: IntervalRewardingParamsUpdate,
force_immediately: bool,
},
CompoundReward {
operator: Option<String>,
delegator: Option<String>,
mix_identity: Option<IdentityKey>,
proxy: Option<String>,
UpdateIntervalConfig {
epochs_in_interval: u32,
epoch_duration_secs: u64,
force_immediately: bool,
},
AdvanceCurrentEpoch {
new_rewarded_set: Vec<NodeId>,
expected_active_set_size: u32,
},
ReconcileEpochEvents {
limit: Option<u32>,
},
// mixnode-related:
BondMixnode {
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
},
UnbondMixnode {},
UpdateMixnodeConfig {
profit_margin_percent: u8,
},
UpdateMixnodeConfigOnBehalf {
profit_margin_percent: u8,
owner: String,
},
BondGateway {
gateway: Gateway,
owner_signature: String,
},
UnbondGateway {},
UpdateContractStateParams(ContractStateParams),
DelegateToMixnode {
mix_identity: IdentityKey,
},
UndelegateFromMixnode {
mix_identity: IdentityKey,
},
RewardMixnode {
identity: IdentityKey,
// percentage value in range 0-100
params: NodeRewardParams,
},
// RewardNextMixDelegators {
// mix_identity: IdentityKey,
// // id of the current rewarding interval
// interval_id: u32,
// },
DelegateToMixnodeOnBehalf {
mix_identity: IdentityKey,
delegate: String,
},
UndelegateFromMixnodeOnBehalf {
mix_identity: IdentityKey,
delegate: String,
},
BondMixnodeOnBehalf {
mix_node: MixNode,
owner: String,
cost_params: MixNodeCostParams,
owner_signature: String,
owner: String,
},
UnbondMixnode {},
UnbondMixnodeOnBehalf {
owner: String,
},
UpdateMixnodeCostParams {
new_costs: MixNodeCostParams,
},
UpdateMixnodeCostParamsOnBehalf {
new_costs: MixNodeCostParams,
owner: String,
},
UpdateMixnodeConfig {
new_config: MixNodeConfigUpdate,
},
UpdateMixnodeConfigOnBehalf {
new_config: MixNodeConfigUpdate,
owner: String,
},
// gateway-related:
BondGateway {
gateway: Gateway,
owner_signature: String,
},
BondGatewayOnBehalf {
gateway: Gateway,
owner: String,
owner_signature: String,
},
UnbondGateway {},
UnbondGatewayOnBehalf {
owner: String,
},
WriteRewardedSet {
rewarded_set: Vec<IdentityKey>,
expected_active_set_size: u32,
// delegation-related:
DelegateToMixnode {
mix_id: NodeId,
},
// AdvanceCurrentInterval {},
AdvanceCurrentEpoch {},
ClaimOperatorReward {},
ClaimOperatorRewardOnBehalf {
DelegateToMixnodeOnBehalf {
mix_id: NodeId,
delegate: String,
},
UndelegateFromMixnode {
mix_id: NodeId,
},
UndelegateFromMixnodeOnBehalf {
mix_id: NodeId,
delegate: String,
},
// reward-related
RewardMixnode {
mix_id: NodeId,
performance: Performance,
},
WithdrawOperatorReward {},
WithdrawOperatorRewardOnBehalf {
owner: String,
},
ClaimDelegatorReward {
mix_identity: IdentityKey,
WithdrawDelegatorReward {
mix_id: NodeId,
},
ClaimDelegatorRewardOnBehalf {
mix_identity: IdentityKey,
WithdrawDelegatorRewardOnBehalf {
mix_id: NodeId,
owner: String,
},
}
impl ExecuteMsg {
pub fn default_memo(&self) -> String {
match self {
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
format!("updating rewarding validator to {}", address)
}
ExecuteMsg::UpdateContractStateParams { .. } => {
"updating mixnet state parameters".into()
}
ExecuteMsg::UpdateActiveSetSize {
active_set_size,
force_immediately,
} => format!(
"updating active set size to {}. forced: {}",
active_set_size, force_immediately
),
ExecuteMsg::UpdateRewardingParams {
force_immediately, ..
} => format!(
"updating mixnet rewarding parameters. forced: {}",
force_immediately
),
ExecuteMsg::UpdateIntervalConfig {
force_immediately, ..
} => format!(
"updating mixnet interval configuration. forced: {}",
force_immediately
),
ExecuteMsg::AdvanceCurrentEpoch { .. } => "advancing current epoch".into(),
ExecuteMsg::ReconcileEpochEvents { .. } => "reconciling epoch events".into(),
ExecuteMsg::BondMixnode { mix_node, .. } => {
format!("bonding mixnode {}", mix_node.identity_key)
}
ExecuteMsg::BondMixnodeOnBehalf { mix_node, .. } => {
format!("bonding mixnode {} on behalf", mix_node.identity_key)
}
ExecuteMsg::UnbondMixnode { .. } => "unbonding mixnode".into(),
ExecuteMsg::UnbondMixnodeOnBehalf { .. } => "unbonding mixnode on behalf".into(),
ExecuteMsg::UpdateMixnodeCostParams { .. } => "updating mixnode cost parameters".into(),
ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { .. } => {
"updating mixnode cost parameters on behalf".into()
}
ExecuteMsg::UpdateMixnodeConfig { .. } => "updating mixnode configuration".into(),
ExecuteMsg::UpdateMixnodeConfigOnBehalf { .. } => {
"updating mixnode configuration on behalf".into()
}
ExecuteMsg::BondGateway { gateway, .. } => {
format!("bonding gateway {}", gateway.identity_key)
}
ExecuteMsg::BondGatewayOnBehalf { gateway, .. } => {
format!("bonding gateway {} on behalf", gateway.identity_key)
}
ExecuteMsg::UnbondGateway { .. } => "unbonding gateway".into(),
ExecuteMsg::UnbondGatewayOnBehalf { .. } => "unbonding gateway on behalf".into(),
ExecuteMsg::DelegateToMixnode { mix_id } => format!("delegating to mixnode {}", mix_id),
ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, .. } => {
format!("delegating to mixnode {} on behalf", mix_id)
}
ExecuteMsg::UndelegateFromMixnode { mix_id } => {
format!("removing delegation from mixnode {}", mix_id)
}
ExecuteMsg::UndelegateFromMixnodeOnBehalf { mix_id, .. } => {
format!("removing delegation from mixnode {} on behalf", mix_id)
}
ExecuteMsg::RewardMixnode {
mix_id,
performance,
} => format!(
"rewarding mixnode {} for performance {}",
mix_id, performance
),
ExecuteMsg::WithdrawOperatorReward { .. } => "withdrawing operator reward".into(),
ExecuteMsg::WithdrawOperatorRewardOnBehalf { .. } => {
"withdrawing operator reward on behalf".into()
}
ExecuteMsg::WithdrawDelegatorReward { mix_id } => {
format!("withdrawing delegator reward from mixnode {}", mix_id)
}
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, .. } => format!(
"withdrawing delegator reward from mixnode {} on behalf",
mix_id
),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetBlacklistedNodes {},
GetCurrentOperatorCost {},
GetRewardingValidatorAddress {},
GetAllDelegationKeys {},
DebugGetAllDelegationValues {},
// state/sys-params-related
GetContractVersion {},
GetMixNodes {
GetRewardingValidatorAddress {},
GetStateParams {},
GetState {},
GetRewardingParams {},
GetCurrentIntervalDetails {},
GetRewardedSet {
limit: Option<u32>,
start_after: Option<IdentityKey>,
start_after: Option<NodeId>,
},
// mixnode-related:
GetMixNodeBonds {
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetMixNodesDetailed {
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetUnbondedMixNodes {
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetUnbondedMixNodesByOwner {
owner: String,
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetUnbondedMixNodesByIdentityKey {
identity_key: String,
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetOwnedMixnode {
address: String,
},
GetMixnodeDetails {
mix_id: NodeId,
},
GetMixnodeRewardingDetails {
mix_id: NodeId,
},
GetStakeSaturation {
mix_id: NodeId,
},
GetUnbondedMixNodeInformation {
mix_id: NodeId,
},
GetBondedMixnodeDetailsByIdentity {
mix_identity: IdentityKey,
},
GetLayerDistribution {},
// gateway-related:
GetGateways {
start_after: Option<IdentityKey>,
limit: Option<u32>,
},
OwnsMixnode {
address: String,
},
OwnsGateway {
address: String,
},
GetMixnodeBond {
identity: IdentityKey,
},
GetGatewayBond {
identity: IdentityKey,
},
StateParams {},
GetOwnedGateway {
address: String,
},
// delegation-related:
// gets all [paged] delegations associated with particular mixnode
GetMixnodeDelegations {
mix_identity: IdentityKey,
mix_id: NodeId,
// since `start_after` is user-provided input, we can't use `Addr` as we
// can't guarantee it's validated.
start_after: Option<(String, u64)>,
start_after: Option<String>,
limit: Option<u32>,
},
// gets all [paged] delegations associated with particular delegator
@@ -162,81 +344,56 @@ pub enum QueryMsg {
// since `delegator` is user-provided input, we can't use `Addr` as we
// can't guarantee it's validated.
delegator: String,
start_after: Option<IdentityKey>,
start_after: Option<(NodeId, OwnerProxySubKey)>,
limit: Option<u32>,
},
// gets delegation associated with particular mixnode, delegator pair
GetDelegationDetails {
mix_identity: IdentityKey,
mix_id: NodeId,
delegator: String,
proxy: Option<String>,
},
LayerDistribution {},
GetRewardPool {},
GetCirculatingSupply {},
GetStakingSupply {},
GetIntervalRewardPercent {},
GetSybilResistancePercent {},
GetActiveSetWorkFactor {},
GetRewardingStatus {
mix_identity: IdentityKey,
interval_id: u32,
},
GetRewardedSet {
height: Option<u64>,
start_after: Option<IdentityKey>,
// gets all delegations in the system
GetAllDelegations {
start_after: Option<delegation::StorageKey>,
limit: Option<u32>,
},
GetRewardedSetUpdateDetails {},
GetCurrentRewardedSetHeight {},
GetRewardedSetRefreshBlocks {},
GetCurrentEpoch {},
GetEpochsInInterval {},
QueryOperatorReward {
// rewards related
GetPendingOperatorReward {
address: String,
},
QueryDelegatorReward {
GetPendingMixNodeOperatorReward {
mix_id: NodeId,
},
GetPendingDelegatorReward {
address: String,
mix_identity: IdentityKey,
mix_id: NodeId,
proxy: Option<String>,
},
GetPendingDelegationEvents {
owner_address: String,
proxy_address: Option<String>,
// given the provided performance, estimate the reward at the end of the current epoch
GetEstimatedCurrentEpochOperatorReward {
mix_id: NodeId,
estimated_performance: Performance,
},
GetCheckpointsForMixnode {
mix_identity: IdentityKey,
GetEstimatedCurrentEpochDelegatorReward {
address: String,
mix_id: NodeId,
proxy: Option<String>,
estimated_performance: Performance,
},
GetMixnodeAtHeight {
mix_identity: IdentityKey,
height: u64,
// interval-related
GetPendingEpochEvents {
limit: Option<u32>,
start_after: Option<u32>,
},
GetPendingIntervalEvents {
limit: Option<u32>,
start_after: Option<u32>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {
pub nodes_to_remove: Option<Vec<NodeToRemove>>,
}
impl MigrateMsg {
pub fn nodes_to_remove(&self) -> Vec<NodeToRemove> {
self.nodes_to_remove.clone().unwrap_or_default()
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct NodeToRemove {
owner: String,
proxy: Option<String>,
}
impl NodeToRemove {
pub fn owner(&self) -> &str {
&self.owner
}
pub fn proxy(&self) -> Option<&String> {
self.proxy.as_ref()
}
}
pub struct MigrateMsg {}
@@ -0,0 +1,77 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::MixNodeCostParams;
use crate::reward_params::IntervalRewardingParamsUpdate;
use crate::{EpochEventId, IntervalEventId, NodeId};
use cosmwasm_std::{Addr, Coin};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingEpochEvent {
pub id: EpochEventId,
pub event: PendingEpochEventData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum PendingEpochEventData {
// can't just pass the `Delegation` struct here as it's impossible to determine
// `cumulative_reward_ratio` ahead of time
Delegate {
owner: Addr,
mix_id: NodeId,
amount: Coin,
proxy: Option<Addr>,
},
Undelegate {
owner: Addr,
mix_id: NodeId,
proxy: Option<Addr>,
},
UnbondMixnode {
mix_id: NodeId,
},
UpdateActiveSetSize {
new_size: u32,
},
}
impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
fn from(data: (EpochEventId, PendingEpochEventData)) -> Self {
PendingEpochEvent {
id: data.0,
event: data.1,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingIntervalEvent {
pub id: EpochEventId,
pub event: PendingIntervalEventData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum PendingIntervalEventData {
ChangeMixCostParams {
mix_id: NodeId,
new_costs: MixNodeCostParams,
},
UpdateRewardingParams {
update: IntervalRewardingParamsUpdate,
},
UpdateIntervalConfig {
epochs_in_interval: u32,
epoch_duration_secs: u64,
},
}
impl From<(IntervalEventId, PendingIntervalEventData)> for PendingIntervalEvent {
fn from(data: (IntervalEventId, PendingIntervalEventData)) -> Self {
PendingIntervalEvent {
id: data.0,
event: data.1,
}
}
}
@@ -1,288 +1,264 @@
use crate::{error::MixnetContractError, mixnode::StoredNodeRewardResult, ONE, U128};
use az::CheckedCast;
use cosmwasm_std::Uint128;
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{error::MixnetContractError, Percent};
use cosmwasm_std::Decimal;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, JsonSchema, PartialEq, Eq, Serialize, Deserialize, Copy)]
pub struct NodeEpochRewards {
params: NodeRewardParams,
result: StoredNodeRewardResult,
epoch_id: u32,
pub type Performance = Percent;
/// Parameters required by the mix-mining reward distribution that do not change during an interval.
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/IntervalRewardParams.ts")
)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)]
pub struct IntervalRewardParams {
/// Current value of the rewarding pool.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub reward_pool: Decimal,
/// Current value of the staking supply.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub staking_supply: Decimal,
// computed values
/// Current value of the computed reward budget per epoch, per node.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub epoch_reward_budget: Decimal,
/// Current value of the stake saturation point.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub stake_saturation_point: Decimal,
// constants(-ish)
// default: 30%
/// Current value of the sybil resistance percent (`alpha`).
/// It is not really expected to be changing very often.
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub sybil_resistance: Percent,
// default: 10
/// Current active set work factor.
/// It is not really expected to be changing very often.
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub active_set_work_factor: Decimal,
// default: 2%
/// Current maximum interval pool emission.
/// Assuming all nodes in the rewarded set are fully saturated and have 100% performance,
/// this % of the reward pool would get distributed in rewards to all operators and its delegators.
/// It is not really expected to be changing very often.
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub interval_pool_emission: Percent,
}
impl NodeEpochRewards {
pub fn new(params: NodeRewardParams, result: StoredNodeRewardResult, epoch_id: u32) -> Self {
Self {
params,
result,
epoch_id,
}
}
pub fn epoch_id(&self) -> u32 {
self.epoch_id
}
pub fn sigma(&self) -> U128 {
self.result.sigma()
}
pub fn lambda(&self) -> U128 {
self.result.lambda()
}
pub fn params(&self) -> NodeRewardParams {
self.params
}
pub fn reward(&self) -> Uint128 {
self.result.reward()
}
pub fn operator_cost(&self, base_operator_cost: u64) -> U128 {
self.params.operator_cost(base_operator_cost)
}
pub fn node_profit(&self, base_operator_cost: u64) -> U128 {
let reward = U128::from_num(self.reward().u128());
// if operating cost is higher then the reward node profit is 0
reward.saturating_sub(self.operator_cost(base_operator_cost))
}
pub fn operator_reward(
&self,
profit_margin: U128,
base_operator_cost: u64,
) -> Result<Uint128, MixnetContractError> {
let reward = self.node_profit(base_operator_cost);
let operator_base_reward = reward.min(self.operator_cost(base_operator_cost));
let div_by_zero_check = if let Some(value) = self.lambda().checked_div(self.sigma()) {
value
} else {
return Err(MixnetContractError::DivisionByZero);
};
let operator_reward = (profit_margin + (ONE - profit_margin) * div_by_zero_check) * reward;
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0u128));
if let Some(int_reward) = reward.checked_cast() {
Ok(Uint128::new(int_reward))
} else {
Err(MixnetContractError::CastError)
}
}
pub fn delegation_reward(
&self,
delegation_amount: Uint128,
profit_margin: U128,
base_operator_cost: u64,
epoch_reward_params: EpochRewardParams,
) -> Result<Uint128, MixnetContractError> {
// change all values into their fixed representations
let delegation_amount = U128::from_num(delegation_amount.u128());
let staking_supply = U128::from_num(epoch_reward_params.staking_supply());
let scaled_delegation_amount = delegation_amount / staking_supply;
let check_div_by_zero =
if let Some(value) = scaled_delegation_amount.checked_div(self.sigma()) {
value
} else {
return Err(MixnetContractError::DivisionByZero);
};
let delegator_reward =
(ONE - profit_margin) * check_div_by_zero * self.node_profit(base_operator_cost);
let reward = delegator_reward.max(U128::ZERO);
if let Some(int_reward) = reward.checked_cast() {
Ok(Uint128::new(int_reward))
} else {
Err(MixnetContractError::CastError)
}
impl IntervalRewardParams {
pub fn to_inline_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Eq, Serialize, Deserialize, Copy)]
pub struct EpochRewardParams {
epoch_reward_pool: Uint128,
rewarded_set_size: Uint128,
active_set_size: Uint128,
#[serde(alias = "circulating_supply")]
staking_supply: Uint128,
sybil_resistance_percent: u8,
active_set_work_factor: u8,
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/RewardingParams.ts")
)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)]
pub struct RewardingParams {
/// Parameters that should remain unchanged throughout an interval.
pub interval: IntervalRewardParams,
// while the active set size can change between epochs to accommodate for bandwidth demands,
// the active set size should be unchanged between epochs and should only be adjusted between
// intervals. However, it makes more sense to keep both of those values together as they're
// very strongly related to each other.
pub rewarded_set_size: u32,
pub active_set_size: u32,
}
impl EpochRewardParams {
pub fn new(
epoch_reward_pool: u128,
rewarded_set_size: u128,
active_set_size: u128,
staking_supply: u128,
sybil_resistance_percent: u8,
active_set_work_factor: u8,
) -> EpochRewardParams {
EpochRewardParams {
epoch_reward_pool: Uint128::new(epoch_reward_pool),
rewarded_set_size: Uint128::new(rewarded_set_size),
active_set_size: Uint128::new(active_set_size),
staking_supply: Uint128::new(staking_supply),
sybil_resistance_percent,
active_set_work_factor,
impl RewardingParams {
pub fn active_node_work(&self) -> Decimal {
self.interval.active_set_work_factor * self.standby_node_work()
}
pub fn standby_node_work(&self) -> Decimal {
let f = self.interval.active_set_work_factor;
let k = self.dec_rewarded_set_size();
let one = Decimal::one();
// nodes in reserve
let k_r = self.dec_standby_set_size();
one / (f * k - (f - one) * k_r)
}
pub fn dec_rewarded_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.rewarded_set_size, 0).unwrap()
}
pub fn dec_active_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.active_set_size, 0).unwrap()
}
fn dec_standby_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.rewarded_set_size - self.active_set_size, 0).unwrap()
}
pub fn apply_epochs_in_interval_change(&mut self, new_epochs_in_interval: u32) {
self.interval.epoch_reward_budget = self.interval.reward_pool
/ Decimal::from_atomics(new_epochs_in_interval, 0).unwrap()
* self.interval.interval_pool_emission;
}
pub fn try_change_active_set_size(
&mut self,
new_active_set_size: u32,
) -> Result<(), MixnetContractError> {
if new_active_set_size == 0 {
return Err(MixnetContractError::ZeroActiveSet);
}
}
// technically it's identical to what would have been derived with a Default implementation,
// however, I prefer to be explicit about it, as a `Default::default` value makes no sense
// apart from the `ValidatorCacheInner` context, where this value is not going to be touched anyway
// (it's guarded behind an `initialised` flag)
pub fn new_empty() -> Self {
EpochRewardParams {
epoch_reward_pool: Uint128::new(0),
staking_supply: Uint128::new(0),
sybil_resistance_percent: 0,
rewarded_set_size: Uint128::new(0),
active_set_size: Uint128::new(0),
active_set_work_factor: 0,
if new_active_set_size > self.rewarded_set_size {
return Err(MixnetContractError::InvalidActiveSetSize);
}
self.active_set_size = new_active_set_size;
Ok(())
}
pub fn rewarded_set_size(&self) -> u128 {
self.rewarded_set_size.u128()
}
pub fn try_apply_updates(
&mut self,
updates: IntervalRewardingParamsUpdate,
epochs_in_interval: u32,
) -> Result<(), MixnetContractError> {
if !updates.contains_updates() {
return Err(MixnetContractError::EmptyParamsChangeMsg);
}
pub fn active_set_size(&self) -> u128 {
self.active_set_size.u128()
}
let mut recompute_epoch_budget = false;
let mut recompute_saturation_point = false;
pub fn staking_supply(&self) -> u128 {
self.staking_supply.u128()
}
if let Some(reward_pool) = updates.reward_pool {
recompute_epoch_budget = true;
self.interval.reward_pool = reward_pool;
}
pub fn epoch_reward_pool(&self) -> u128 {
self.epoch_reward_pool.u128()
}
if let Some(staking_supply) = updates.staking_supply {
recompute_saturation_point = true;
self.interval.staking_supply = staking_supply;
}
pub fn sybil_resistance_percent(&self) -> u8 {
self.sybil_resistance_percent
}
if let Some(sybil_resistance_percent) = updates.sybil_resistance_percent {
self.interval.sybil_resistance = sybil_resistance_percent;
}
pub fn active_set_work_factor(&self) -> u8 {
self.active_set_work_factor
if let Some(active_set_work_factor) = updates.active_set_work_factor {
self.interval.active_set_work_factor = active_set_work_factor;
}
if let Some(interval_pool_emission) = updates.interval_pool_emission {
recompute_epoch_budget = true;
self.interval.interval_pool_emission = interval_pool_emission;
}
if let Some(rewarded_set_size) = updates.rewarded_set_size {
if rewarded_set_size == 0 {
return Err(MixnetContractError::ZeroRewardedSet);
}
if rewarded_set_size < self.active_set_size {
return Err(MixnetContractError::InvalidRewardedSetSize);
}
recompute_saturation_point = true;
self.rewarded_set_size = rewarded_set_size;
}
if recompute_epoch_budget {
self.interval.epoch_reward_budget = self.interval.reward_pool
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
* self.interval.interval_pool_emission;
}
if recompute_saturation_point {
self.interval.stake_saturation_point = self.interval.staking_supply
/ Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
}
Ok(())
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Eq, Serialize, Deserialize, Copy)]
// TODO: possibly refactor this
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)]
pub struct NodeRewardParams {
reward_blockstamp: u64,
uptime: Uint128,
in_active_set: bool,
pub performance: Percent,
pub in_active_set: bool,
}
impl NodeRewardParams {
pub fn new(reward_blockstamp: u64, uptime: u128, in_active_set: bool) -> NodeRewardParams {
pub fn new(performance: Percent, in_active_set: bool) -> Self {
NodeRewardParams {
reward_blockstamp,
uptime: Uint128::new(uptime),
performance,
in_active_set,
}
}
pub fn operator_cost(&self, base_operator_cost: u64) -> U128 {
self.performance() * U128::from_num(base_operator_cost)
}
pub fn uptime(&self) -> Uint128 {
self.uptime
}
pub fn performance(&self) -> U128 {
U128::from_num(self.uptime.u128()) / U128::from_num(100)
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.reward_blockstamp = blockstamp;
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Eq, Serialize, Deserialize, Copy)]
pub struct RewardParams {
pub epoch: EpochRewardParams,
pub node: NodeRewardParams,
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/IntervalRewardingParamsUpdate.ts")
)]
#[derive(
Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema,
)]
pub struct IntervalRewardingParamsUpdate {
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub reward_pool: Option<Decimal>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub staking_supply: Option<Decimal>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub sybil_resistance_percent: Option<Percent>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub active_set_work_factor: Option<Decimal>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub interval_pool_emission: Option<Percent>,
pub rewarded_set_size: Option<u32>,
}
impl RewardParams {
pub fn new(epoch: EpochRewardParams, node: NodeRewardParams) -> RewardParams {
RewardParams { epoch, node }
impl IntervalRewardingParamsUpdate {
pub fn contains_updates(&self) -> bool {
// essentially at least a single field has to be a `Some`
self.reward_pool.is_some()
|| self.staking_supply.is_some()
|| self.sybil_resistance_percent.is_some()
|| self.active_set_work_factor.is_some()
|| self.interval_pool_emission.is_some()
|| self.rewarded_set_size.is_some()
}
pub fn omega(&self) -> U128 {
// As per keybase://chat/nymtech#tokeneconomics/1179
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
if denom == 0 {
return U128::ZERO;
}
// Div by zero checked above
if self.in_active_set() {
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
self.active_set_work_factor() / denom * self.rewarded_set_size()
} else {
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
ONE / denom * self.rewarded_set_size()
}
}
pub fn idle_nodes(&self) -> Uint128 {
self.epoch.rewarded_set_size - self.epoch.active_set_size
}
pub fn active_set_work_factor(&self) -> U128 {
U128::from_num(self.epoch.active_set_work_factor)
}
pub fn in_active_set(&self) -> bool {
self.node.in_active_set
}
pub fn performance(&self) -> U128 {
self.node.performance()
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.node.reward_blockstamp = blockstamp;
}
pub fn epoch_reward_pool(&self) -> u128 {
self.epoch.epoch_reward_pool.u128()
}
pub fn rewarded_set_size(&self) -> u128 {
self.epoch.rewarded_set_size.u128()
}
pub fn staking_supply(&self) -> u128 {
self.epoch.staking_supply.u128()
}
pub fn reward_blockstamp(&self) -> u64 {
self.node.reward_blockstamp
}
pub fn uptime(&self) -> Uint128 {
self.node.uptime()
}
pub fn one_over_k(&self) -> U128 {
ONE / U128::from_num(self.epoch.rewarded_set_size.u128())
}
pub fn alpha(&self) -> U128 {
U128::from_num(self.epoch.sybil_resistance_percent) / U128::from_num(100)
pub fn to_inline_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
}
}
@@ -0,0 +1,23 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Coin, Decimal, Uint128};
/// Truncates all decimal points so that the reward would fit in a `Coin` and so that we would
/// never attempt to reward more than the owner is due
/// for example it truncates "23.9" into "23"
pub fn truncate_reward(reward: Decimal, denom: impl Into<String>) -> Coin {
let amount = truncate_reward_amount(reward);
Coin {
denom: denom.into(),
amount,
}
}
pub fn truncate_reward_amount(reward: Decimal) -> Uint128 {
truncate_decimal(reward)
}
pub fn truncate_decimal(amount: Decimal) -> Uint128 {
amount * Uint128::new(1)
}
@@ -0,0 +1,61 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Coin, Decimal};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub mod helpers;
pub mod simulator;
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
pub struct RewardEstimate {
pub total_node_reward: Decimal,
// note that operator reward includes the operating_cost,
// i.e. say total_node_reward was `1nym` and operating_cost was `2nym`
// in that case the operator reward would still be `1nym` as opposed to 0
pub operator: Decimal,
pub delegates: Decimal,
pub operating_cost: Decimal,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
pub struct RewardDistribution {
pub operator: Decimal,
pub delegates: Decimal,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
pub struct PendingRewardResponse {
pub amount_staked: Option<Coin>,
pub amount_earned: Option<Coin>,
pub amount_earned_detailed: Option<Decimal>,
/// The associated mixnode is still fully bonded, meaning it is neither unbonded
/// nor in the process of unbonding that would have finished at the epoch transition.
pub mixnode_still_fully_bonded: bool,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
pub struct EstimatedCurrentEpochRewardResponse {
pub original_stake: Option<Coin>,
pub current_stake_value: Option<Coin>,
pub current_stake_value_detailed_amount: Option<Decimal>,
pub estimation: Option<Coin>,
pub detailed_estimation_amount: Option<Decimal>,
}
impl EstimatedCurrentEpochRewardResponse {
pub fn empty_response() -> Self {
EstimatedCurrentEpochRewardResponse {
original_stake: None,
current_stake_value: None,
current_stake_value_detailed_amount: None,
estimation: None,
detailed_estimation_amount: None,
}
}
}
@@ -0,0 +1,465 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::MixnetContractError;
use crate::mixnode::{MixNodeCostParams, MixNodeRewarding};
use crate::reward_params::{IntervalRewardParams, NodeRewardParams, RewardingParams};
use crate::rewarding::helpers::truncate_reward;
use crate::rewarding::RewardDistribution;
use crate::{Delegation, Interval, Percent};
use cosmwasm_std::{Addr, Coin, Decimal};
pub struct Simulator {
pub node_rewarding_details: MixNodeRewarding,
pub node_delegations: Vec<Delegation>,
pub system_rewarding_params: RewardingParams,
pub interval: Interval,
}
impl Simulator {
pub fn new(
profit_margin_percent: Percent,
interval_operating_cost: Coin,
system_rewarding_params: RewardingParams,
initial_pledge: Coin,
interval: Interval,
) -> Self {
let cost_params = MixNodeCostParams {
profit_margin_percent,
interval_operating_cost,
};
Simulator {
node_rewarding_details: MixNodeRewarding::initialise_new(
cost_params,
&initial_pledge,
Default::default(),
),
node_delegations: vec![],
system_rewarding_params,
interval,
}
}
pub fn delegate(&mut self, amount: Coin) {
let cumulative_reward_ratio = self.node_rewarding_details.total_unit_reward;
// let record = self.node_rewarding_details.increment_period();
// self.node_historical_records.insert(period, record);
self.node_rewarding_details
.add_base_delegation(amount.amount);
// we don't care about the owner/node details here
self.node_delegations.push(Delegation::new(
Addr::unchecked("bob"),
42,
cumulative_reward_ratio,
amount,
123,
None,
))
}
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal {
self.node_rewarding_details
.determine_delegation_reward(delegation)
}
// since this is a simulator only, not something to be used in the production code, the unwraps are fine
// if user inputs are invalid
pub fn undelegate(
&mut self,
delegation_index: usize,
) -> Result<(Coin, Coin), MixnetContractError> {
let delegation = self.node_delegations.remove(delegation_index);
let reward = self.determine_delegation_reward(&delegation);
self.node_rewarding_details
.decrease_delegates_decimal(delegation.dec_amount() + reward)?;
self.node_rewarding_details.unique_delegations -= 1;
let reward_denom = &delegation.amount.denom;
let truncated_reward = truncate_reward(reward, reward_denom);
// if this was last delegation, move all leftover decimal tokens to the operator
// (this is literally in the order of a millionth of a micronym)
if self.node_delegations.is_empty() {
assert_eq!(self.node_rewarding_details.unique_delegations, 0);
self.node_rewarding_details.operator += self.node_rewarding_details.delegates;
self.node_rewarding_details.delegates = Decimal::zero();
}
Ok((delegation.amount, truncated_reward))
}
pub fn determine_total_delegation_reward(&self) -> Decimal {
let mut total = Decimal::zero();
for delegation in &self.node_delegations {
total += self.determine_delegation_reward(delegation)
}
total
}
pub fn simulate_epoch(&mut self, node_params: NodeRewardParams) -> RewardDistribution {
let reward_distribution = self.node_rewarding_details.calculate_epoch_reward(
&self.system_rewarding_params,
node_params,
self.interval.epochs_in_interval(),
);
self.node_rewarding_details.distribute_rewards(
reward_distribution,
self.interval.current_epoch_absolute_id(),
);
self.interval = self.interval.advance_epoch();
reward_distribution
}
// assume node state doesn't change in the interval (kinda unrealistic)
pub fn simulate_interval(&mut self, node_params: NodeRewardParams) {
assert_eq!(self.interval.current_epoch_id(), 0);
let id = self.interval.current_interval_id();
let mut distributed = Decimal::zero();
for _ in 0..self.interval.epochs_in_interval() {
let distr = self.simulate_epoch(node_params);
distributed += distr.operator;
distributed += distr.delegates;
}
assert_eq!(id + 1, self.interval.current_interval_id());
// update reward pool and all of that
let old = self.system_rewarding_params.interval;
let reward_pool = old.reward_pool - distributed;
let staking_supply = old.staking_supply + distributed;
let epoch_reward_budget = reward_pool
/ Decimal::from_atomics(self.interval.epochs_in_interval(), 0).unwrap()
* old.interval_pool_emission.value();
let stake_saturation_point = staking_supply
/ Decimal::from_atomics(self.system_rewarding_params.rewarded_set_size, 0).unwrap();
let updated_params = RewardingParams {
interval: IntervalRewardParams {
reward_pool,
staking_supply,
epoch_reward_budget,
stake_saturation_point,
sybil_resistance: old.sybil_resistance,
active_set_work_factor: old.active_set_work_factor,
interval_pool_emission: old.interval_pool_emission,
},
rewarded_set_size: self.system_rewarding_params.rewarded_set_size,
active_set_size: self.system_rewarding_params.active_set_size,
};
self.system_rewarding_params = updated_params;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reward_params::IntervalRewardParams;
use crate::rewarding::helpers::truncate_reward_amount;
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::{coin, Uint128};
use std::time::Duration;
fn base_simulator(initial_pledge: u128) -> Simulator {
let profit_margin = Percent::from_percentage_value(10).unwrap();
let interval_operating_cost = Coin::new(40_000_000, "unym");
let epochs_in_interval = 720u32;
let rewarded_set_size = 240;
let active_set_size = 100;
let interval_pool_emission = Percent::from_percentage_value(2).unwrap();
let reward_pool = 250_000_000_000_000u128;
let staking_supply = 100_000_000_000_000u128;
let epoch_reward_budget =
interval_pool_emission * Decimal::from_ratio(reward_pool, epochs_in_interval);
let stake_saturation_point = Decimal::from_ratio(staking_supply, rewarded_set_size);
let rewarding_params = RewardingParams {
interval: IntervalRewardParams {
reward_pool: Decimal::from_atomics(reward_pool, 0).unwrap(), // 250M * 1M (we're expressing it all in base tokens)
staking_supply: Decimal::from_atomics(staking_supply, 0).unwrap(), // 100M * 1M
epoch_reward_budget,
stake_saturation_point,
sybil_resistance: Percent::from_percentage_value(30).unwrap(),
active_set_work_factor: Decimal::percent(1000), // value '10'
interval_pool_emission,
},
rewarded_set_size,
active_set_size,
};
let interval = Interval::init_interval(
epochs_in_interval,
Duration::from_secs(60 * 60),
&mock_env(),
);
let initial_pledge = Coin::new(initial_pledge, "unym");
Simulator::new(
profit_margin,
interval_operating_cost,
rewarding_params,
initial_pledge,
interval,
)
}
fn compare_decimals(a: Decimal, b: Decimal) {
let epsilon = Decimal::from_ratio(1u128, 100_000_000u128);
if a > b {
assert!(a - b < epsilon, "{} != {}", a, b)
} else {
assert!(b - a < epsilon, "{} != {}", a, b)
}
}
// essentially our delegations + estimated rewards HAVE TO equal to what we actually determined
fn check_rewarding_invariant(simulator: &Simulator) {
let delegation_sum: Decimal = simulator
.node_delegations
.iter()
.map(|d| d.dec_amount())
.sum();
let reward_sum = simulator.determine_total_delegation_reward();
compare_decimals(
delegation_sum + reward_sum,
simulator.node_rewarding_details.delegates,
)
}
#[test]
fn simulator_returns_expected_values_for_base_case() {
let mut simulator = base_simulator(10000_000000);
let epoch_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let rewards = simulator.simulate_epoch(epoch_params);
assert_eq!(rewards.delegates, Decimal::zero());
compare_decimals(rewards.operator, "1128452.5416104363".parse().unwrap());
}
#[test]
fn single_delegation_at_genesis() {
let mut simulator = base_simulator(10000_000000);
simulator.delegate(Coin::new(18000_000000, "unym"));
let node_params = NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let rewards = simulator.simulate_epoch(node_params);
compare_decimals(rewards.delegates, "1795950.2602660495".parse().unwrap());
compare_decimals(rewards.operator, "1363716.856243172".parse().unwrap());
compare_decimals(
rewards.delegates,
simulator.determine_total_delegation_reward(),
);
assert_eq!(
simulator.node_rewarding_details.operator,
rewards.operator + Decimal::from_atomics(10000_000000u128, 0).unwrap()
);
assert_eq!(
simulator.node_rewarding_details.delegates,
rewards.delegates + Decimal::from_atomics(18000_000000u128, 0).unwrap()
);
}
#[test]
fn delegation_and_undelegation() {
let mut simulator = base_simulator(10000_000000);
let node_params = NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let rewards1 = simulator.simulate_epoch(node_params);
let expected_operator1 = "1128452.5416104363".parse().unwrap();
assert_eq!(rewards1.delegates, Decimal::zero());
compare_decimals(rewards1.operator, expected_operator1);
simulator.delegate(Coin::new(18000_000000, "unym"));
let rewards2 = simulator.simulate_epoch(node_params);
let expected_operator2 = "1363843.413584609".parse().unwrap();
let expected_delegator_reward1 = "1795952.25874404".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward1);
compare_decimals(rewards2.operator, expected_operator2);
let rewards3 = simulator.simulate_epoch(node_params);
let expected_operator3 = "1364017.7824440491".parse().unwrap();
let expected_delegator_reward2 = "1796135.9269468693".parse().unwrap();
compare_decimals(rewards3.delegates, expected_delegator_reward2);
compare_decimals(rewards3.operator, expected_operator3);
let (delegation, reward) = simulator.undelegate(0).unwrap();
assert_eq!(delegation.amount.u128(), 18000_000000);
assert_eq!(
reward.amount,
(expected_delegator_reward1 + expected_delegator_reward2) * Uint128::new(1)
);
let base_op = Decimal::from_atomics(10000_000000u128, 0).unwrap();
compare_decimals(
simulator.node_rewarding_details.operator,
base_op + expected_operator1 + expected_operator2 + expected_operator3,
);
assert_eq!(Decimal::zero(), simulator.node_rewarding_details.delegates);
}
#[test]
fn withdrawing_operator_reward() {
// essentially all delegators' rewards (and the operator itself) are still correctly computed
let original_pledge = coin(10000_000000, "unym");
let mut simulator = base_simulator(original_pledge.amount.u128());
let node_params = NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work)
simulator.delegate(Coin::new(18000_000000, "unym"));
simulator.delegate(Coin::new(4000_000000, "unym"));
// "normal", sanity check rewarding
let rewards1 = simulator.simulate_epoch(node_params);
let expected_operator1 = "1411087.1007647323".parse().unwrap();
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
compare_decimals(rewards1.delegates, expected_delegator_reward1);
compare_decimals(rewards1.operator, expected_operator1);
check_rewarding_invariant(&simulator);
let reward = simulator
.node_rewarding_details
.withdraw_operator_reward(&original_pledge);
assert_eq!(reward.amount, truncate_reward_amount(expected_operator1));
assert_eq!(
simulator.node_rewarding_details.operator,
Decimal::from_atomics(original_pledge.amount, 0).unwrap()
);
let rewards2 = simulator.simulate_epoch(node_params);
let expected_operator2 = "1411113.0004067947".parse().unwrap();
let expected_delegator_reward2 = "2200183.3879084454".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward2);
compare_decimals(rewards2.operator, expected_operator2);
check_rewarding_invariant(&simulator);
}
#[test]
fn withdrawing_delegator_reward() {
// essentially all delegators' rewards (and the operator itself) are still correctly computed
let mut simulator = base_simulator(10000_000000);
let node_params = NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work)
simulator.delegate(Coin::new(18000_000000, "unym"));
simulator.delegate(Coin::new(4000_000000, "unym"));
// "normal", sanity check rewarding
let rewards1 = simulator.simulate_epoch(node_params);
let expected_operator1 = "1411087.1007647323".parse().unwrap();
let expected_delegator_reward1 = "2199961.032388664".parse().unwrap();
compare_decimals(rewards1.delegates, expected_delegator_reward1);
compare_decimals(rewards1.operator, expected_operator1);
check_rewarding_invariant(&simulator);
// reference to our `18000_000000` delegation
let delegation1 = &mut simulator.node_delegations[0];
let reward = simulator
.node_rewarding_details
.withdraw_delegator_reward(delegation1)
.unwrap();
let expected_del1_reward = "1799968.1174089068".parse().unwrap();
assert_eq!(reward.amount, truncate_reward_amount(expected_del1_reward));
// new reward after withdrawal
let rewards2 = simulator.simulate_epoch(node_params);
let expected_operator2 = "1411250.1907492676".parse().unwrap();
let expected_delegator_reward2 = "2200004.051009689".parse().unwrap();
compare_decimals(rewards2.delegates, expected_delegator_reward2);
compare_decimals(rewards2.operator, expected_operator2);
check_rewarding_invariant(&simulator);
// check final values
let reward_del1 = simulator
.node_rewarding_details
.withdraw_delegator_reward(&mut simulator.node_delegations[0])
.unwrap();
let expected_del1_reward = "1799970.5883041779".parse().unwrap();
assert_eq!(
reward_del1.amount,
truncate_reward_amount(expected_del1_reward)
);
let reward_del2 = simulator
.node_rewarding_details
.withdraw_delegator_reward(&mut simulator.node_delegations[1])
.unwrap();
let first: Decimal = "399992.91497975704".parse().unwrap();
let second: Decimal = "400033.4627055114".parse().unwrap();
let expected_del2_reward = first + second;
assert_eq!(
reward_del2.amount,
truncate_reward_amount(expected_del2_reward)
);
}
#[test]
fn simulating_multiple_epochs() {
let mut simulator = base_simulator(10000_000000);
let mut is_active = true;
let mut performance = Percent::from_percentage_value(100).unwrap();
for epoch in 0..720 {
if epoch == 0 {
simulator.delegate(Coin::new(18000_000000, "unym"))
}
if epoch == 42 {
simulator.delegate(Coin::new(2000_000000, "unym"))
}
if epoch == 89 {
is_active = false;
}
if epoch == 123 {
simulator.delegate(Coin::new(6666_000000, "unym"))
}
if epoch == 167 {
performance = Percent::from_percentage_value(90).unwrap();
}
if epoch == 245 {
simulator.delegate(Coin::new(2050_000000, "unym"))
}
if epoch == 264 {
let (delegation, _reward) = simulator.undelegate(1).unwrap();
// sanity check to make sure we undelegated what we wanted to undelegate : )
assert_eq!(delegation.amount.u128(), 2000_000000);
// TODO: figure out if there's a good way to verify whether `reward` is what we expect it to be
}
if epoch == 345 {
is_active = true;
}
if epoch == 358 {
performance = Percent::from_percentage_value(100).unwrap();
}
if epoch == 458 {
let (delegation, _reward) = simulator.undelegate(0).unwrap();
// sanity check to make sure we undelegated what we wanted to undelegate : )
assert_eq!(delegation.amount.u128(), 18000_000000);
// TODO: figure out if there's a good way to verify whether `reward` is what we expect it to be
}
if epoch == 545 {
simulator.delegate(Coin::new(5000_000000, "unym"))
}
// this has to always hold
check_rewarding_invariant(&simulator);
let node_params = NodeRewardParams::new(performance, is_active);
simulator.simulate_epoch(node_params);
}
// after everyone undelegates, there should be nothing left in the delegates pool
let delegations = simulator.node_delegations.len();
for _ in 0..delegations {
simulator.undelegate(0).unwrap();
}
assert_eq!(Decimal::zero(), simulator.node_rewarding_details.delegates);
}
}
@@ -1,16 +1,111 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::DelegatorRewardParams;
use crate::error::MixnetContractError;
use crate::rewarding::helpers::truncate_decimal;
use crate::{Layer, RewardedSetNodeStatus};
use cosmwasm_std::{Addr, Uint128};
use cosmwasm_std::{Coin, Decimal};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize};
use std::fmt::{self, Display, Formatter};
use std::ops::{Index, Mul};
// type aliases for better reasoning about available data
pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
pub type SphinxKey = String;
pub type SphinxKeyRef<'a> = &'a str;
pub type EpochId = u32;
pub type IntervalId = u32;
pub type NodeId = u32;
pub type EpochEventId = u32;
pub type IntervalEventId = u32;
/// Percent represents a value between 0 and 100%
/// (i.e. between 0.0 and 1.0)
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Serialize, Deserialize, JsonSchema,
)]
pub struct Percent(#[serde(deserialize_with = "de_decimal_percent")] Decimal);
impl Percent {
pub fn new(value: Decimal) -> Result<Self, MixnetContractError> {
if value > Decimal::one() {
Err(MixnetContractError::InvalidPercent)
} else {
Ok(Percent(value))
}
}
pub fn is_zero(&self) -> bool {
self.0 == Decimal::zero()
}
pub fn from_percentage_value(value: u64) -> Result<Self, MixnetContractError> {
Percent::new(Decimal::percent(value))
}
pub fn value(&self) -> Decimal {
self.0
}
pub fn round_to_integer(&self) -> u8 {
let hundred = Decimal::from_ratio(100u32, 1u32);
// we know the cast from u128 to u8 is a safe one since the internal value must be within 0 - 1 range
truncate_decimal(hundred * self.0).u128() as u8
}
}
impl Display for Percent {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let adjusted = Decimal::from_atomics(100u32, 0).unwrap() * self.0;
write!(f, "{}%", adjusted)
}
}
impl Mul<Decimal> for Percent {
type Output = Decimal;
fn mul(self, rhs: Decimal) -> Self::Output {
self.0 * rhs
}
}
impl Mul<Percent> for Decimal {
type Output = Decimal;
fn mul(self, rhs: Percent) -> Self::Output {
rhs * self
}
}
impl Mul<Uint128> for Percent {
type Output = Uint128;
fn mul(self, rhs: Uint128) -> Self::Output {
self.0 * rhs
}
}
// implement custom Deserialize because we want to validate Percent has the correct range
fn de_decimal_percent<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
where
D: Deserializer<'de>,
{
let v = Decimal::deserialize(deserializer)?;
if v > Decimal::one() {
Err(D::Error::custom(
"provided decimal percent is larger than 100%",
))
} else {
Ok(v)
}
}
#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone, Eq, PartialEq)]
pub struct LayerDistribution {
pub gateways: u64,
pub layer1: u64,
pub layer2: u64,
pub layer3: u64,
@@ -25,121 +120,133 @@ impl LayerDistribution {
];
layers.iter().min_by_key(|x| x.1).unwrap().0
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct ContractStateParams {
// so currently interval_length is being unused and validator API performs rewarding
// based on its own interval length config value. I guess that's fine for time being
// however, in the future, the contract constant should be controlling it instead.
// pub interval_length: u32, // length of a rewarding interval/interval, expressed in hours
pub minimum_mixnode_pledge: Uint128, // minimum amount a mixnode must pledge to get into the system
pub minimum_gateway_pledge: Uint128, // minimum amount a gateway must pledge to get into the system
pub fn increment_layer_count(&mut self, layer: Layer) {
match layer {
Layer::One => self.layer1 += 1,
Layer::Two => self.layer2 += 1,
Layer::Three => self.layer3 += 1,
}
}
// number of mixnode that are going to get rewarded during current rewarding interval (k_m)
// based on overall demand for private bandwidth-
pub mixnode_rewarded_set_size: u32,
pub fn decrement_layer_count(&mut self, layer: Layer) -> Result<(), MixnetContractError> {
match layer {
Layer::One => {
self.layer1 =
self.layer1
.checked_sub(1)
.ok_or(MixnetContractError::OverflowSubtraction {
minuend: self.layer1,
subtrahend: 1,
})?
}
Layer::Two => {
self.layer2 =
self.layer2
.checked_sub(1)
.ok_or(MixnetContractError::OverflowSubtraction {
minuend: self.layer2,
subtrahend: 1,
})?
}
Layer::Three => {
self.layer3 =
self.layer3
.checked_sub(1)
.ok_or(MixnetContractError::OverflowSubtraction {
minuend: self.layer3,
subtrahend: 1,
})?
}
}
// subset of rewarded mixnodes that are actively receiving mix traffic
// used to handle shorter-term (e.g. hourly) fluctuations of demand
pub mixnode_active_set_size: u32,
pub staking_supply: Uint128,
}
impl Display for ContractStateParams {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Contract state parameters: [ ")?;
write!(
f,
"minimum mixnode pledge: {}; ",
self.minimum_mixnode_pledge
)?;
write!(
f,
"minimum gateway pledge: {}; ",
self.minimum_gateway_pledge
)?;
write!(
f,
"mixnode rewarded set size: {}",
self.mixnode_rewarded_set_size
)?;
write!(
f,
"mixnode active set size: {}",
self.mixnode_active_set_size
)
Ok(())
}
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct RewardingResult {
pub node_reward: Uint128,
impl Index<Layer> for LayerDistribution {
type Output = u64;
fn index(&self, index: Layer) -> &Self::Output {
match index {
Layer::One => &self.layer1,
Layer::Two => &self.layer2,
Layer::Three => &self.layer3,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PendingDelegatorRewarding {
// keep track of the running rewarding results so we'd known how much was the operator and its delegators rewarded
pub running_results: RewardingResult,
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ContractState {
pub owner: Addr, // only the owner account can update state
pub rewarding_validator_address: Addr,
pub next_start: Addr,
pub rewarding_params: DelegatorRewardParams,
/// Address of the vesting contract to which the mixnet contract would be sending all
/// track-related messages.
pub vesting_contract_address: Addr,
pub rewarding_denom: String,
pub params: ContractStateParams,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum RewardingStatus {
Complete(RewardingResult),
PendingNextDelegatorPage(PendingDelegatorRewarding),
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ContractStateParams {
/// Minimum amount a delegator must stake in orders for his delegation to get accepted.
pub minimum_mixnode_delegation: Option<Coin>,
/// Minimum amount a mixnode must pledge to get into the system.
pub minimum_mixnode_pledge: Coin,
/// Minimum amount a gateway must pledge to get into the system.
pub minimum_gateway_pledge: Coin,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MixnodeRewardingStatusResponse {
pub status: Option<RewardingStatus>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MixnetContractVersion {
// VERGEN_BUILD_TIMESTAMP
pub build_timestamp: String,
// VERGEN_BUILD_SEMVER
pub build_version: String,
// VERGEN_GIT_SHA
pub commit_sha: String,
// VERGEN_GIT_COMMIT_TIMESTAMP
pub commit_timestamp: String,
// VERGEN_GIT_BRANCH
pub commit_branch: String,
// VERGEN_RUSTC_SEMVER
pub rustc_version: String,
}
// type aliases for better reasoning about available data
pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
pub type SphinxKey = String;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub struct PagedRewardedSetResponse {
pub identities: Vec<(IdentityKey, RewardedSetNodeStatus)>,
pub start_next_after: Option<IdentityKey>,
pub at_height: u64,
pub nodes: Vec<(NodeId, RewardedSetNodeStatus)>,
pub start_next_after: Option<NodeId>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct RewardedSetUpdateDetails {
pub refresh_rate_blocks: u64,
pub last_refreshed_block: u64,
pub current_height: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct IntervalRewardedSetHeightsResponse {
pub interval_id: u32,
pub heights: Vec<u64>,
#[test]
fn percent_serde() {
let valid_value = Percent::from_percentage_value(80).unwrap();
let serialized = serde_json::to_string(&valid_value).unwrap();
println!("{}", serialized);
let deserialized: Percent = serde_json::from_str(&serialized).unwrap();
assert_eq!(valid_value, deserialized);
let invalid_values = vec!["\"42\"", "\"1.1\"", "\"1.00000001\"", "\"foomp\"", "\"1a\""];
for invalid_value in invalid_values {
assert!(serde_json::from_str::<'_, Percent>(invalid_value).is_err())
}
assert_eq!(
serde_json::from_str::<'_, Percent>("\"0.95\"").unwrap(),
Percent::from_percentage_value(95).unwrap()
)
}
#[test]
fn percent_to_absolute_integer() {
let p = serde_json::from_str::<'_, Percent>("\"0.0001\"").unwrap();
assert_eq!(p.round_to_integer(), 0);
let p = serde_json::from_str::<'_, Percent>("\"0.0099\"").unwrap();
assert_eq!(p.round_to_integer(), 0);
let p = serde_json::from_str::<'_, Percent>("\"0.0199\"").unwrap();
assert_eq!(p.round_to_integer(), 1);
let p = serde_json::from_str::<'_, Percent>("\"0.45123\"").unwrap();
assert_eq!(p.round_to_integer(), 45);
let p = serde_json::from_str::<'_, Percent>("\"0.999999999\"").unwrap();
assert_eq!(p.round_to_integer(), 99);
let p = serde_json::from_str::<'_, Percent>("\"1.00\"").unwrap();
assert_eq!(p.round_to_integer(), 100);
}
}
@@ -16,6 +16,8 @@ pub const VESTING_GATEWAY_UNBONDING_EVENT_TYPE: &str = "vesting_gateway_unbondin
pub const VESTING_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding";
pub const VESTING_MIXNODE_UNBONDING_EVENT_TYPE: &str = "vesting_mixnode_unbonding";
pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixnode_config";
pub const VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE: &str =
"vesting_update_mixnode_cost_params";
pub const TRACK_MIXNODE_UNBOND_EVENT_TYPE: &str = "track_mixnode_unbond";
pub const TRACK_GATEWAY_UNBOND_EVENT_TYPE: &str = "track_gateway_unbond";
@@ -114,6 +116,10 @@ pub fn new_vesting_update_mixnode_config_event() -> Event {
Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE)
}
pub fn new_vesting_update_mixnode_cost_params_event() -> Event {
Event::new(VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE)
}
pub fn new_vesting_mixnode_unbonding_event() -> Event {
Event::new(VESTING_MIXNODE_UNBONDING_EVENT_TYPE)
}
@@ -1,19 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Addr, Coin, Timestamp, Uint128};
use mixnet_contract_common::NodeId;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
use mixnet_contract_common::IdentityKey;
pub mod events;
pub mod messages;
pub fn one_ucoin(denom: String) -> Coin {
Coin::new(1, denom)
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
@@ -78,18 +74,14 @@ impl OriginalVestingResponse {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, JsonSchema)]
pub struct VestingDelegation {
pub account_id: u32,
pub mix_identity: IdentityKey,
pub mix_id: NodeId,
pub block_timestamp: u64,
pub amount: Uint128,
}
impl VestingDelegation {
pub fn storage_key(&self) -> (u32, IdentityKey, u64) {
(
self.account_id,
self.mix_identity.clone(),
self.block_timestamp,
)
pub fn storage_key(&self) -> (u32, NodeId, u64) {
(self.account_id, self.mix_id, self.block_timestamp)
}
}
@@ -97,12 +89,12 @@ impl VestingDelegation {
pub struct DelegationTimesResponse {
pub owner: Addr,
pub account_id: u32,
pub mix_identity: IdentityKey,
pub mix_id: NodeId,
pub delegation_timestamps: Vec<u64>,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, JsonSchema)]
pub struct AllDelegationsResponse {
pub delegations: Vec<VestingDelegation>,
pub start_next_after: Option<(u32, IdentityKey, u64)>,
pub start_next_after: Option<(u32, NodeId, u64)>,
}
@@ -1,5 +1,8 @@
use cosmwasm_std::{Coin, Timestamp, Uint128};
use mixnet_contract_common::{Gateway, IdentityKey, MixNode};
use mixnet_contract_common::{
mixnode::{MixNodeConfigUpdate, MixNodeCostParams},
Gateway, MixNode, NodeId,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -12,7 +15,9 @@ pub struct InitMsg {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
pub struct MigrateMsg {
pub v2_mixnet_contract_address: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)]
pub struct VestingSpecification {
@@ -56,24 +61,23 @@ pub enum ExecuteMsg {
},
ClaimOperatorReward {},
ClaimDelegatorReward {
mix_identity: String,
mix_id: NodeId,
},
CompoundDelegatorReward {
mix_identity: String,
UpdateMixnodeCostParams {
new_costs: MixNodeCostParams,
},
CompoundOperatorReward {},
UpdateMixnodeConfig {
profit_margin_percent: u8,
new_config: MixNodeConfigUpdate,
},
UpdateMixnetAddress {
address: String,
},
DelegateToMixnode {
mix_identity: IdentityKey,
mix_id: NodeId,
amount: Coin,
},
UndelegateFromMixnode {
mix_identity: IdentityKey,
mix_id: NodeId,
},
CreateAccount {
owner_address: String,
@@ -85,11 +89,12 @@ pub enum ExecuteMsg {
},
TrackUndelegation {
owner: String,
mix_identity: IdentityKey,
mix_id: NodeId,
amount: Coin,
},
BondMixnode {
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
amount: Coin,
},
@@ -119,6 +124,35 @@ pub enum ExecuteMsg {
},
}
impl ExecuteMsg {
pub fn name(&self) -> &str {
match self {
ExecuteMsg::TrackReward { .. } => "VestingExecuteMsg::TrackReward",
ExecuteMsg::ClaimOperatorReward { .. } => "VestingExecuteMsg::ClaimOperatorReward",
ExecuteMsg::ClaimDelegatorReward { .. } => "VestingExecuteMsg::ClaimDelegatorReward",
ExecuteMsg::UpdateMixnodeConfig { .. } => "VestingExecuteMsg::UpdateMixnodeConfig",
ExecuteMsg::UpdateMixnodeCostParams { .. } => {
"VestingExecuteMsg::UpdateMixnodeCostParams"
}
ExecuteMsg::UpdateMixnetAddress { .. } => "VestingExecuteMsg::UpdateMixnetAddress",
ExecuteMsg::DelegateToMixnode { .. } => "VestingExecuteMsg::DelegateToMixnode",
ExecuteMsg::UndelegateFromMixnode { .. } => "VestingExecuteMsg::UndelegateFromMixnode",
ExecuteMsg::CreateAccount { .. } => "VestingExecuteMsg::CreateAccount",
ExecuteMsg::WithdrawVestedCoins { .. } => "VestingExecuteMsg::WithdrawVestedCoins",
ExecuteMsg::TrackUndelegation { .. } => "VestingExecuteMsg::TrackUndelegation",
ExecuteMsg::BondMixnode { .. } => "VestingExecuteMsg::BondMixnode",
ExecuteMsg::UnbondMixnode { .. } => "VestingExecuteMsg::UnbondMixnode",
ExecuteMsg::TrackUnbondMixnode { .. } => "VestingExecuteMsg::TrackUnbondMixnode",
ExecuteMsg::BondGateway { .. } => "VestingExecuteMsg::BondGateway",
ExecuteMsg::UnbondGateway { .. } => "VestingExecuteMsg::UnbondGateway",
ExecuteMsg::TrackUnbondGateway { .. } => "VestingExecuteMsg::TrackUnbondGateway",
ExecuteMsg::TransferOwnership { .. } => "VestingExecuteMsg::TransferOwnership",
ExecuteMsg::UpdateStakingAddress { .. } => "VestingExecuteMsg::UpdateStakingAddress",
ExecuteMsg::UpdateLockedPledgeCap { .. } => "VestingExecuteMsg::UpdateLockedPledgeCap",
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
@@ -170,10 +204,10 @@ pub enum QueryMsg {
GetLockedPledgeCap {},
GetDelegationTimes {
address: String,
mix_identity: IdentityKey,
mix_id: NodeId,
},
GetAllDelegations {
start_after: Option<(u32, IdentityKey, u64)>,
start_after: Option<(u32, NodeId, u64)>,
limit: Option<u32>,
},
}
+1 -1
View File
@@ -11,7 +11,7 @@ async fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let database_path = format!("{}/coconut-credential-example.sqlite", out_dir);
let mut conn = SqliteConnection::connect(&*format!("sqlite://{}?mode=rwc", database_path))
let mut conn = SqliteConnection::connect(&format!("sqlite://{}?mode=rwc", database_path))
.await
.expect("Failed to create SQLx database connection");
+1 -1
View File
@@ -144,7 +144,7 @@ impl Tau {
let tau_mem = self.0.as_raw_slice();
assert_eq!(tau_mem.len(), 1, "tau length invariant was broken");
random_oracle_builder.update(&tau_mem[0].to_be_bytes());
random_oracle_builder.update(tau_mem[0].to_be_bytes());
let oracle_output = random_oracle_builder.finalize();
debug_assert_eq!(oracle_output.len() * 8, HASH_SECURITY_PARAM);
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "ledger"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bip32 = "0.3.0"
k256 = "0.10.4"
ledger-transport = "0.10.0"
ledger-transport-hid = "0.10.0"
thiserror = "1"
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use bip32::{PublicKey, PublicKeyBytes};
use ledger_transport::APDUAnswer;
/// SECP265K1 address of the device.
pub struct AddrSecp265k1Response {
/// SECP265K1 public key.
pub public_key: k256::PublicKey,
/// String representation of the Cosmos address.
pub address: String,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for AddrSecp265k1Response {
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
if bytes.len() < 33 {
return Err(Self::Error::InvalidAnswerLength {
expected: 33,
received: bytes.len(),
});
}
let (pub_key, addr) = bytes.split_at(33);
let public_key = k256::PublicKey::from_bytes(
PublicKeyBytes::try_from(pub_key).expect("Public key should be 33 bytes"),
)?;
let address = String::from_utf8(addr.to_vec()).unwrap();
Ok(AddrSecp265k1Response {
public_key,
address,
})
}
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;
pub(crate) type Result<T> = std::result::Result<T, LedgerError>;
/// Ledger specific errors.
#[derive(Debug, Error)]
pub enum LedgerError {
#[error("HID API - {0}")]
HidAPI(#[from] ledger_transport_hid::hidapi::HidError),
#[error("HID transport - {0}")]
HidTransport(#[from] ledger_transport_hid::LedgerHIDError),
#[error("Unknown error code - {err_code}")]
UnknownErrorCode { err_code: u16 },
#[error("APDU error - {reason}")]
APDU { reason: String },
#[error("Not enough bytes in answer. Expected at least {expected}, received {received}")]
InvalidAnswerLength { expected: usize, received: usize },
#[error("Not enough components in derivation path. Expected {expected}, received {received}")]
InvalidDerivationPath { expected: usize, received: usize },
#[error("Bip32 - {0}")]
Bip32(#[from] bip32::Error),
#[error("Signature error - {0}")]
Signature(#[from] k256::ecdsa::Error),
#[error("No message found for signing transaction")]
NoMessageFound,
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::{LedgerError, Result};
use bip32::DerivationPath;
use ledger_transport::{APDUAnswer, APDUErrorCode};
pub(crate) fn answer_bytes(answer: &APDUAnswer<Vec<u8>>) -> Result<&[u8]> {
let error_code = answer
.error_code()
.map_err(|err_code| LedgerError::UnknownErrorCode { err_code })?;
match error_code {
APDUErrorCode::NoError => Ok(answer.data()),
e => Err(LedgerError::APDU {
reason: e.description(),
}),
}
}
pub(crate) fn path_bytes(path: DerivationPath) -> Result<[[u8; 4]; 5]> {
let received = path.len();
let components: Vec<[u8; 4]> = path.into_iter().map(|c| c.0.to_le_bytes()).collect();
if components.len() != 5 {
Err(LedgerError::InvalidDerivationPath {
expected: 5,
received,
})
} else {
Ok([
components[0],
components[1],
components[2],
components[3],
components[4],
])
}
}
+140
View File
@@ -0,0 +1,140 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod addr_secp265k1;
pub mod error;
pub(crate) mod helpers;
mod sign_secp265k1;
pub mod version;
use crate::addr_secp265k1::AddrSecp265k1Response;
use crate::error::LedgerError;
use crate::helpers::path_bytes;
use crate::sign_secp265k1::SignSecp265k1Response;
use crate::version::VersionResponse;
use bip32::DerivationPath;
use error::Result;
use ledger_transport::APDUCommand;
use ledger_transport_hid::hidapi::HidApi;
use ledger_transport_hid::TransportNativeHID;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::sync::Arc;
const CLA: u8 = 0x55;
const INS_GET_VERSION: u8 = 0x00;
const INS_SIGN_SECP256K1: u8 = 0x02;
const INS_GET_ADDR_SECP256K1: u8 = 0x04;
const PAYLOAD_TYPE_INIT: u8 = 0x00;
const PAYLOAD_TYPE_ADD: u8 = 0x01;
const PAYLOAD_TYPE_LAST: u8 = 0x02;
const CHUNK_SIZE: usize = 250;
/// Manage hardware Ledger device with Cosmos specific operations, as described in the
/// specification: https://github.com/cosmos/ledger-cosmos/blob/main/docs/APDUSPEC.md
#[derive(Clone)]
pub struct CosmosLedger {
path: DerivationPath,
prefix: String,
transport: Arc<TransportNativeHID>,
}
impl Debug for CosmosLedger {
fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(f, "()")
}
}
impl CosmosLedger {
/// Create the connection to the first Ledger device that we can find.
pub fn new(path: DerivationPath, prefix: String) -> Result<Self> {
let api = HidApi::new()?;
let transport = Arc::new(TransportNativeHID::new(&api)?);
Ok(CosmosLedger {
path,
prefix,
transport,
})
}
/// Get the version of the device.
pub fn get_version(&self) -> Result<VersionResponse> {
let command = APDUCommand {
cla: CLA,
ins: INS_GET_VERSION,
p1: 0,
p2: 0,
data: vec![],
};
let response = self.transport.exchange(&command)?;
VersionResponse::try_from(response)
}
/// Get the SECP265K1 address of the device.
pub fn get_addr_secp265k1(&self, display: bool) -> Result<AddrSecp265k1Response> {
let display = u8::from(display);
let components = path_bytes(self.path.clone())?;
let data: Vec<u8> = vec![
[self.prefix.len() as u8].as_slice(),
self.prefix.as_bytes(),
components[0].as_slice(),
components[1].as_slice(),
components[2].as_slice(),
components[3].as_slice(),
components[4].as_slice(),
]
.into_iter()
.flatten()
.copied()
.collect();
let command = APDUCommand {
cla: CLA,
ins: INS_GET_ADDR_SECP256K1,
p1: display,
p2: 0,
data,
};
let response = self.transport.exchange(&command)?;
AddrSecp265k1Response::try_from(response)
}
pub fn sign_secp265k1(&self, message: String) -> Result<SignSecp265k1Response> {
let serialized_path: Vec<u8> = path_bytes(self.path.clone())?
.into_iter()
.flatten()
.collect();
let mut chunks = vec![serialized_path];
if message.is_empty() {
return Err(LedgerError::NoMessageFound);
}
for chunk in message.into_bytes().chunks(CHUNK_SIZE) {
chunks.push(chunk.to_vec());
}
let length = chunks.len();
for (idx, chunk) in chunks.into_iter().enumerate() {
let payload_desc = if idx == 0 {
PAYLOAD_TYPE_INIT
} else if idx + 1 == length {
PAYLOAD_TYPE_LAST
} else {
PAYLOAD_TYPE_ADD
};
let command = APDUCommand {
cla: CLA,
ins: INS_SIGN_SECP256K1,
p1: payload_desc,
p2: 0,
data: chunk,
};
let sign_response = self.transport.exchange(&command)?;
if payload_desc == PAYLOAD_TYPE_LAST {
return SignSecp265k1Response::try_from(sign_response);
}
}
// It should never reach this, as the message is not empty
Err(LedgerError::NoMessageFound)
}
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use k256::ecdsa::Signature;
use ledger_transport::APDUAnswer;
/// Version and status data of the device.
pub struct SignSecp265k1Response {
/// DER encoded signature data
pub signature: Signature,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for SignSecp265k1Response {
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
Ok(SignSecp265k1Response {
signature: Signature::from_der(bytes)?,
})
}
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use ledger_transport::APDUAnswer;
/// Version and status data of the device.
pub struct VersionResponse {
/// Activation status of test mode.
pub test_mode: bool,
/// Major part of Cosmos application version.
pub major: u8,
/// Minor part of Cosmos application version.
pub minor: u8,
/// Patch part of Cosmos application version.
pub patch: u8,
/// PIN locked status.
pub device_locked: bool,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for VersionResponse {
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
if bytes.len() != 5 {
return Err(Self::Error::InvalidAnswerLength {
expected: 5,
received: bytes.len(),
});
}
Ok(VersionResponse {
test_mode: bytes[0] != 0,
major: bytes[1],
minor: bytes[2],
patch: bytes[3],
device_locked: bytes[4] != 0,
})
}
}
+4 -3
View File
@@ -321,17 +321,18 @@ impl VerlocMeasurer {
let tested_nodes = all_mixes
.into_iter()
.filter_map(|node| {
let mix_node = node.bond_information.mix_node;
// check if the node has sufficient version to be able to understand the packets
let node_version = parse_version(&node.mix_node.version).ok()?;
let node_version = parse_version(&mix_node.version).ok()?;
if node_version < self.config.minimum_compatible_node_version {
return None;
}
// try to parse the identity and host
let node_identity =
identity::PublicKey::from_base58_string(node.mix_node.identity_key).ok()?;
identity::PublicKey::from_base58_string(mix_node.identity_key).ok()?;
let verloc_host = (&*node.mix_node.host, node.mix_node.verloc_port)
let verloc_host = (&*mix_node.host, mix_node.verloc_port)
.to_socket_addrs()
.ok()?
.next()?;
+3 -6
View File
@@ -203,9 +203,8 @@ mod message_receiver {
mixes.insert(
1,
vec![mix::Node {
mix_id: 123,
owner: "foomp1".to_string(),
stake: 123,
delegation: 456,
host: "10.20.30.40".parse().unwrap(),
mix_host: "10.20.30.40:1789".parse().unwrap(),
identity_key: identity::PublicKey::from_base58_string(
@@ -224,9 +223,8 @@ mod message_receiver {
mixes.insert(
2,
vec![mix::Node {
mix_id: 234,
owner: "foomp2".to_string(),
stake: 123,
delegation: 456,
host: "11.21.31.41".parse().unwrap(),
mix_host: "11.21.31.41:1789".parse().unwrap(),
identity_key: identity::PublicKey::from_base58_string(
@@ -245,9 +243,8 @@ mod message_receiver {
mixes.insert(
3,
vec![mix::Node {
mix_id: 456,
owner: "foomp3".to_string(),
stake: 123,
delegation: 456,
host: "12.22.32.42".parse().unwrap(),
mix_host: "12.22.32.42:1789".parse().unwrap(),
identity_key: identity::PublicKey::from_base58_string(
+12 -8
View File
@@ -3,7 +3,8 @@
use crate::filter::VersionFilterable;
use log::warn;
use mixnet_contract_common::{GatewayBond, MixNodeBond};
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::GatewayBond;
use nymsphinx_addressing::nodes::NodeIdentity;
use nymsphinx_types::Node as SphinxNode;
use rand::Rng;
@@ -224,12 +225,15 @@ impl NymTopology {
}
}
pub fn nym_topology_from_bonds(
mix_bonds: Vec<MixNodeBond>,
pub fn nym_topology_from_detailed(
mix_details: Vec<MixNodeDetails>,
gateway_bonds: Vec<GatewayBond>,
) -> NymTopology {
let mut mixes = HashMap::new();
for bond in mix_bonds.into_iter() {
for bond in mix_details
.into_iter()
.map(|details| details.bond_information)
{
let layer = bond.layer as MixLayer;
if layer == 0 || layer > 3 {
warn!(
@@ -238,13 +242,14 @@ pub fn nym_topology_from_bonds(
);
continue;
}
let mix_id = bond.mix_node.identity_key.clone();
let mix_id = bond.id;
let mix_identity = bond.mix_node.identity_key.clone();
let layer_entry = mixes.entry(layer).or_insert_with(Vec::new);
match bond.try_into() {
Ok(mix) => layer_entry.push(mix),
Err(err) => {
warn!("Mix {} is malformed - {}", mix_id, err);
warn!("Mix {} / {} is malformed - {}", mix_id, mix_identity, err);
continue;
}
}
@@ -279,9 +284,8 @@ mod converting_mixes_to_vec {
#[test]
fn returns_a_vec_with_hashmap_values() {
let node1 = mix::Node {
mix_id: 42,
owner: "N/A".to_string(),
stake: 0,
delegation: 0,
host: "3.3.3.3".parse().unwrap(),
mix_host: "3.3.3.3:1789".parse().unwrap(),
identity_key: identity::PublicKey::from_base58_string(
+3 -7
View File
@@ -3,7 +3,7 @@
use crate::{filter, NetworkAddress};
use crypto::asymmetric::{encryption, identity};
use mixnet_contract_common::{Layer, MixNodeBond};
use mixnet_contract_common::{Layer, MixNodeBond, NodeId};
use nymsphinx_addressing::nodes::NymNodeRoutingAddress;
use nymsphinx_types::Node as SphinxNode;
use std::convert::{TryFrom, TryInto};
@@ -68,11 +68,8 @@ impl Display for MixnodeConversionError {
#[derive(Debug, Clone)]
pub struct Node {
pub mix_id: NodeId,
pub owner: String,
// somebody correct me if I'm wrong, but we should only ever have a single denom of currency
// on the network at a type, right?
pub stake: u128,
pub delegation: u128,
pub host: NetworkAddress,
// we're keeping this as separate resolved field since we do not want to be resolving the potential
// hostname every time we want to construct a path via this node
@@ -116,9 +113,8 @@ impl<'a> TryFrom<&'a MixNodeBond> for Node {
})?[0];
Ok(Node {
mix_id: bond.id,
owner: bond.owner.as_str().to_owned(),
stake: bond.pledge_amount.amount.into(),
delegation: bond.total_delegation.amount.into(),
host,
mix_host,
identity_key: identity::PublicKey::from_base58_string(&bond.mix_node.identity_key)?,
+2 -2
View File
@@ -19,7 +19,7 @@ thiserror = "1.0"
url = "2.2"
ts-rs = "6.1.2"
cosmwasm-std = "1.0.0-beta8"
cosmwasm-std = "1.0.0"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
validator-client = { path = "../../common/client-libs/validator-client", features = [
@@ -37,4 +37,4 @@ tempfile = "3.3.0"
[features]
default = []
generate-ts = []
generate-ts = ["mixnet-contract-common/generate-ts"]
+44
View File
@@ -71,6 +71,50 @@ impl RegisteredCoins {
self.0.remove(denom)
}
pub fn attempt_create_display_coin_from_base_dec_amount(
&self,
denom: &Denom,
base_amount: Decimal,
) -> Result<DecCoin, TypesError> {
for registered_coin in self.0.values() {
if let Some(exponent) = registered_coin.get_exponent(denom) {
// if this fails it means we haven't registered our display denom which honestly should never be the case
// unless somebody is rocking their own custom network
let display_exponent = registered_coin
.get_exponent(&registered_coin.display)
.ok_or_else(|| TypesError::UnknownCoinDenom(denom.clone()))?;
return match exponent.cmp(&display_exponent) {
Ordering::Greater => {
// we need to scale up, unlikely to ever be needed but included for completion sake,
// for example if we decided to created knym with exponent 9 and wanted to convert to nym with exponent 6
Ok(DecCoin {
denom: denom.into(),
amount: try_scale_up_decimal(base_amount, exponent - display_exponent)?,
})
}
// we're already in the display denom
Ordering::Equal => Ok(DecCoin {
denom: denom.into(),
amount: base_amount,
}),
Ordering::Less => {
// we need to scale down, the most common case, for example we're in base unym with exponent 0 and want to convert to nym with exponent 6
Ok(DecCoin {
denom: denom.into(),
amount: try_scale_down_decimal(
base_amount,
display_exponent - exponent,
)?,
})
}
};
}
}
Err(TypesError::UnknownCoinDenom(denom.clone()))
}
pub fn attempt_convert_to_base_coin(&self, coin: DecCoin) -> Result<Coin, TypesError> {
// check if this is already in the base denom
if self.0.contains_key(&coin.denom) {
+20 -103
View File
@@ -1,8 +1,9 @@
use crate::currency::{DecCoin, RegisteredCoins};
use crate::deprecated::DelegationEvent;
use crate::error::TypesError;
use mixnet_contract_common::mixnode::DelegationEvent as ContractDelegationEvent;
use mixnet_contract_common::mixnode::PendingUndelegate as ContractPendingUndelegate;
use mixnet_contract_common::Delegation as MixnetContractDelegation;
use crate::mixnode::MixNodeCostParams;
use cosmwasm_std::Decimal;
use mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -14,9 +15,9 @@ use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub struct Delegation {
pub owner: String,
pub node_identity: String,
pub mix_id: NodeId,
pub amount: DecCoin,
pub block_height: u64,
pub height: u64,
pub proxy: Option<String>, // proxy address used to delegate the funds on behalf of another address
}
@@ -27,48 +28,40 @@ impl Delegation {
) -> Result<Self, TypesError> {
Ok(Delegation {
owner: delegation.owner.to_string(),
node_identity: delegation.node_identity,
mix_id: delegation.node_id,
amount: reg.attempt_convert_to_display_dec_coin(delegation.amount.into())?,
block_height: delegation.block_height,
height: delegation.height,
proxy: delegation.proxy.map(|d| d.to_string()),
})
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/DelegationRecord.ts")
)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub struct DelegationRecord {
pub amount: DecCoin,
pub block_height: u64,
pub delegated_on_iso_datetime: String,
pub uses_vesting_contract_tokens: bool,
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/DelegationWithEverything.ts")
)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub struct DelegationWithEverything {
pub owner: String,
pub mix_id: NodeId,
pub node_identity: String,
pub amount: DecCoin,
pub total_delegation: Option<DecCoin>,
pub pledge_amount: Option<DecCoin>,
pub accumulated_by_delegates: Option<DecCoin>,
pub accumulated_by_operator: Option<DecCoin>,
pub block_height: u64,
pub delegated_on_iso_datetime: String,
pub profit_margin_percent: Option<u8>,
pub cost_params: Option<MixNodeCostParams>,
pub avg_uptime_percent: Option<u8>,
pub stake_saturation: Option<f32>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub stake_saturation: Option<Decimal>,
pub uses_vesting_contract_tokens: bool,
pub accumulated_rewards: Option<DecCoin>,
pub unclaimed_rewards: Option<DecCoin>,
// DEPRECATED, IF POSSIBLE TRY TO DISCONTINUE USE OF IT!
pub pending_events: Vec<DelegationEvent>,
pub history: Vec<DelegationRecord>,
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
@@ -83,82 +76,6 @@ pub struct DelegationResult {
amount: Option<DecCoin>,
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/DelegationEventKind.ts")
)]
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Debug)]
pub enum DelegationEventKind {
Delegate,
Undelegate,
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/DelegationEvent.ts")
)]
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Debug)]
pub struct DelegationEvent {
pub kind: DelegationEventKind,
pub node_identity: String,
pub address: String,
pub amount: Option<DecCoin>,
pub block_height: u64,
pub proxy: Option<String>,
}
impl DelegationEvent {
pub fn from_mixnet_contract(
event: ContractDelegationEvent,
reg: &RegisteredCoins,
) -> Result<Self, TypesError> {
Ok(match event {
ContractDelegationEvent::Delegate(delegation) => DelegationEvent {
kind: DelegationEventKind::Delegate,
block_height: delegation.block_height,
address: delegation.owner.into_string(),
node_identity: delegation.node_identity,
amount: Some(reg.attempt_convert_to_display_dec_coin(delegation.amount.into())?),
proxy: delegation.proxy.map(|p| p.into_string()),
},
ContractDelegationEvent::Undelegate(pending_undelegate) => DelegationEvent {
kind: DelegationEventKind::Undelegate,
block_height: pending_undelegate.block_height(),
address: pending_undelegate.delegate().into_string(),
node_identity: pending_undelegate.mix_identity(),
amount: None,
proxy: pending_undelegate.proxy().map(|p| p.into_string()),
},
})
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/PendingUndelegate.ts")
)]
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
pub struct PendingUndelegate {
mix_identity: String,
delegate: String,
proxy: Option<String>,
block_height: u64,
}
impl From<ContractPendingUndelegate> for PendingUndelegate {
fn from(pending_undelegate: ContractPendingUndelegate) -> Self {
PendingUndelegate {
mix_identity: pending_undelegate.mix_identity(),
delegate: pending_undelegate.delegate().to_string(),
proxy: pending_undelegate.proxy().map(|p| p.to_string()),
block_height: pending_undelegate.block_height(),
}
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
+109
View File
@@ -0,0 +1,109 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::currency::DecCoin;
use crate::error::TypesError;
use crate::pending_events::{PendingEpochEvent, PendingEpochEventData};
use mixnet_contract_common::{IdentityKey, NodeId};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/DelegationEventKind.ts")
)]
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Debug)]
pub enum DelegationEventKind {
Delegate,
Undelegate,
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/DelegationEvent.ts")
)]
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Debug)]
pub struct DelegationEvent {
pub kind: DelegationEventKind,
pub mix_id: NodeId,
pub address: String,
pub amount: Option<DecCoin>,
pub proxy: Option<String>,
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/WrappedDelegationEvent.ts")
)]
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Debug)]
pub struct WrappedDelegationEvent {
pub event: DelegationEvent,
pub node_identity: IdentityKey,
}
impl WrappedDelegationEvent {
pub fn new(event: DelegationEvent, node_identity: IdentityKey) -> Self {
Self {
event,
node_identity,
}
}
}
impl DelegationEvent {
pub fn address_matches(&self, address: &str) -> bool {
self.address == address
}
}
pub fn convert_to_delegation_events(epoch_events: Vec<PendingEpochEvent>) -> Vec<DelegationEvent> {
epoch_events
.into_iter()
// filter out all events that are not delegation-related
.filter_map(|e| e.try_into().ok())
.collect()
}
impl TryFrom<PendingEpochEvent> for DelegationEvent {
type Error = TypesError;
fn try_from(value: PendingEpochEvent) -> Result<Self, Self::Error> {
value.event.try_into()
}
}
impl TryFrom<PendingEpochEventData> for DelegationEvent {
type Error = TypesError;
fn try_from(value: PendingEpochEventData) -> Result<Self, Self::Error> {
match value {
PendingEpochEventData::Delegate {
owner,
mix_id,
amount,
proxy,
} => Ok(DelegationEvent {
kind: DelegationEventKind::Delegate,
address: owner,
mix_id,
proxy,
amount: Some(amount),
}),
PendingEpochEventData::Undelegate {
owner,
mix_id,
proxy,
} => Ok(DelegationEvent {
kind: DelegationEventKind::Undelegate,
address: owner,
mix_id,
proxy,
amount: None,
}),
_ => Err(TypesError::NotADelegationEvent),
}
}
}
+2
View File
@@ -70,6 +70,8 @@ pub enum TypesError {
LossyCoinConversion,
#[error("The provided coin has an unknown denomination - {0}")]
UnknownCoinDenom(String),
#[error("Provided event is not a delegation event")]
NotADelegationEvent,
}
impl Serialize for TypesError {
+5
View File
@@ -1,10 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod account;
pub mod currency;
pub mod delegation;
pub mod deprecated;
pub mod error;
pub mod fees;
pub mod gas;
pub mod gateway;
pub mod mixnode;
pub mod pending_events;
pub mod transaction;
pub mod vesting;
+123 -56
View File
@@ -1,53 +1,44 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::currency::{DecCoin, RegisteredCoins};
use crate::error::TypesError;
use cosmwasm_std::Decimal;
use mixnet_contract_common::{
MixNode as MixnetContractMixNode, MixNodeBond as MixnetContractMixNodeBond,
EpochId, MixNode, MixNodeBond as MixnetContractMixNodeBond,
MixNodeCostParams as MixnetContractMixNodeCostParams,
MixNodeDetails as MixnetContractMixNodeDetails,
MixNodeRewarding as MixnetContractMixNodeRewarding, NodeId, Percent,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use validator_client::nymd::Coin;
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/Mixnode.ts")
ts(export_to = "ts-packages/types/src/types/rust/MixNodeDetails.ts")
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)]
pub struct MixNode {
pub host: String,
pub mix_port: u16,
pub verloc_port: u16,
pub http_api_port: u16,
pub sphinx_key: String,
/// Base58 encoded ed25519 EdDSA public key.
pub identity_key: String,
pub version: String,
pub profit_margin_percent: u8,
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeDetails {
pub bond_information: MixNodeBond,
pub rewarding_details: MixNodeRewarding,
}
impl From<MixnetContractMixNode> for MixNode {
fn from(value: MixnetContractMixNode) -> Self {
let MixnetContractMixNode {
host,
mix_port,
verloc_port,
http_api_port,
sphinx_key,
identity_key,
version,
profit_margin_percent,
} = value;
Self {
host,
mix_port,
verloc_port,
http_api_port,
sphinx_key,
identity_key,
version,
profit_margin_percent,
}
impl MixNodeDetails {
pub fn from_mixnet_contract_mixnode_details(
details: MixnetContractMixNodeDetails,
reg: &RegisteredCoins,
) -> Result<MixNodeDetails, TypesError> {
Ok(MixNodeDetails {
bond_information: MixNodeBond::from_mixnet_contract_mixnode_bond(
details.bond_information,
reg,
)?,
rewarding_details: MixNodeRewarding::from_mixnet_contract_mixnode_rewarding(
details.rewarding_details,
reg,
)?,
})
}
}
@@ -58,14 +49,14 @@ impl From<MixnetContractMixNode> for MixNode {
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeBond {
pub pledge_amount: DecCoin,
pub total_delegation: DecCoin,
pub id: NodeId,
pub owner: String,
pub original_pledge: DecCoin,
pub layer: String,
pub block_height: u64,
pub mix_node: MixNode,
pub proxy: Option<String>,
pub accumulated_rewards: Option<DecCoin>,
pub bonding_height: u64,
pub is_unbonding: bool,
}
impl MixNodeBond {
@@ -73,24 +64,100 @@ impl MixNodeBond {
bond: MixnetContractMixNodeBond,
reg: &RegisteredCoins,
) -> Result<MixNodeBond, TypesError> {
let denom = bond.pledge_amount.denom.clone();
Ok(MixNodeBond {
pledge_amount: reg.attempt_convert_to_display_dec_coin(bond.pledge_amount.into())?,
total_delegation: reg
.attempt_convert_to_display_dec_coin(bond.total_delegation.into())?,
id: bond.id,
owner: bond.owner.into_string(),
original_pledge: reg
.attempt_convert_to_display_dec_coin(bond.original_pledge.into())?,
layer: bond.layer.into(),
block_height: bond.block_height,
mix_node: bond.mix_node.into(),
proxy: bond.proxy.map(|p| p.to_string()),
accumulated_rewards: bond
.accumulated_rewards
.map(|reward| {
// here we're making an assumption that rewards always use the same denom as the pledge
// (which I think is a reasonable assumption)
reg.attempt_convert_to_display_dec_coin(Coin::new(reward.u128(), denom))
})
.transpose()?,
mix_node: bond.mix_node,
proxy: bond.proxy.map(|p| p.into_string()),
bonding_height: bond.bonding_height,
is_unbonding: bond.is_unbonding,
})
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/MixNodeRewarding.ts")
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeRewarding {
pub cost_params: MixNodeCostParams,
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub operator: Decimal,
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub delegates: Decimal,
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub total_unit_reward: Decimal,
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub unit_delegation: Decimal,
pub last_rewarded_epoch: EpochId,
pub unique_delegations: u32,
}
impl MixNodeRewarding {
pub fn from_mixnet_contract_mixnode_rewarding(
mix_rewarding: MixnetContractMixNodeRewarding,
reg: &RegisteredCoins,
) -> Result<MixNodeRewarding, TypesError> {
Ok(MixNodeRewarding {
cost_params: MixNodeCostParams::from_mixnet_contract_mixnode_cost_params(
mix_rewarding.cost_params,
reg,
)?,
operator: mix_rewarding.operator,
delegates: mix_rewarding.delegates,
total_unit_reward: mix_rewarding.total_unit_reward,
unit_delegation: mix_rewarding.unit_delegation,
last_rewarded_epoch: mix_rewarding.last_rewarded_epoch,
unique_delegations: mix_rewarding.unique_delegations,
})
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/MixNodeCostParams.ts")
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct MixNodeCostParams {
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub profit_margin_percent: Percent,
pub interval_operating_cost: DecCoin,
}
impl MixNodeCostParams {
pub fn from_mixnet_contract_mixnode_cost_params(
cost_params: MixnetContractMixNodeCostParams,
reg: &RegisteredCoins,
) -> Result<MixNodeCostParams, TypesError> {
Ok(MixNodeCostParams {
profit_margin_percent: cost_params.profit_margin_percent,
interval_operating_cost: reg
.attempt_convert_to_display_dec_coin(cost_params.interval_operating_cost.into())?,
})
}
pub fn try_convert_to_mixnet_contract_cost_params(
self,
reg: &RegisteredCoins,
) -> Result<MixnetContractMixNodeCostParams, TypesError> {
Ok(MixnetContractMixNodeCostParams {
profit_margin_percent: self.profit_margin_percent,
interval_operating_cost: reg
.attempt_convert_to_base_coin(self.interval_operating_cost)?
.into(),
})
}
}
+172
View File
@@ -0,0 +1,172 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::currency::{DecCoin, RegisteredCoins};
use crate::error::TypesError;
use crate::mixnode::MixNodeCostParams;
use mixnet_contract_common::{
EpochEventId, IntervalEventId, IntervalRewardingParamsUpdate, NodeId,
PendingEpochEvent as MixnetContractPendingEpochEvent,
PendingEpochEventData as MixnetContractPendingEpochEventData,
PendingIntervalEvent as MixnetContractPendingIntervalEvent,
PendingIntervalEventData as MixnetContractPendingIntervalEventData,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/PendingEpochEvent.ts")
)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub struct PendingEpochEvent {
pub id: EpochEventId,
pub event: PendingEpochEventData,
}
impl PendingEpochEvent {
pub fn try_from_mixnet_contract(
pending_event: MixnetContractPendingEpochEvent,
reg: &RegisteredCoins,
) -> Result<Self, TypesError> {
Ok(PendingEpochEvent {
id: pending_event.id,
event: PendingEpochEventData::try_from_mixnet_contract(pending_event.event, reg)?,
})
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/PendingEpochEventData.ts")
)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub enum PendingEpochEventData {
Delegate {
owner: String,
mix_id: NodeId,
amount: DecCoin,
proxy: Option<String>,
},
Undelegate {
owner: String,
mix_id: NodeId,
proxy: Option<String>,
},
UnbondMixnode {
mix_id: NodeId,
},
UpdateActiveSetSize {
new_size: u32,
},
}
impl PendingEpochEventData {
pub fn try_from_mixnet_contract(
pending_event: MixnetContractPendingEpochEventData,
reg: &RegisteredCoins,
) -> Result<Self, TypesError> {
match pending_event {
MixnetContractPendingEpochEventData::Delegate {
owner,
mix_id,
amount,
proxy,
} => Ok(PendingEpochEventData::Delegate {
owner: owner.into_string(),
mix_id,
amount: reg.attempt_convert_to_display_dec_coin(amount.into())?,
proxy: proxy.map(|p| p.into_string()),
}),
MixnetContractPendingEpochEventData::Undelegate {
owner,
mix_id,
proxy,
} => Ok(PendingEpochEventData::Undelegate {
owner: owner.into_string(),
mix_id,
proxy: proxy.map(|p| p.into_string()),
}),
MixnetContractPendingEpochEventData::UnbondMixnode { mix_id } => {
Ok(PendingEpochEventData::UnbondMixnode { mix_id })
}
MixnetContractPendingEpochEventData::UpdateActiveSetSize { new_size } => {
Ok(PendingEpochEventData::UpdateActiveSetSize { new_size })
}
}
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/PendingIntervalEvent.ts")
)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub struct PendingIntervalEvent {
pub id: IntervalEventId,
pub event: PendingIntervalEventData,
}
impl PendingIntervalEvent {
pub fn try_from_mixnet_contract(
pending_event: MixnetContractPendingIntervalEvent,
reg: &RegisteredCoins,
) -> Result<Self, TypesError> {
Ok(PendingIntervalEvent {
id: pending_event.id,
event: PendingIntervalEventData::try_from_mixnet_contract(pending_event.event, reg)?,
})
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/PendingIntervalEventData.ts")
)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub enum PendingIntervalEventData {
ChangeMixCostParams {
mix_id: NodeId,
new_costs: MixNodeCostParams,
},
UpdateRewardingParams {
update: IntervalRewardingParamsUpdate,
},
UpdateIntervalConfig {
epochs_in_interval: u32,
epoch_duration_secs: u64,
},
}
impl PendingIntervalEventData {
pub fn try_from_mixnet_contract(
pending_event: MixnetContractPendingIntervalEventData,
reg: &RegisteredCoins,
) -> Result<Self, TypesError> {
match pending_event {
MixnetContractPendingIntervalEventData::ChangeMixCostParams { mix_id, new_costs } => {
Ok(PendingIntervalEventData::ChangeMixCostParams {
mix_id,
new_costs: MixNodeCostParams::from_mixnet_contract_mixnode_cost_params(
new_costs, reg,
)?,
})
}
MixnetContractPendingIntervalEventData::UpdateRewardingParams { update } => {
Ok(PendingIntervalEventData::UpdateRewardingParams { update })
}
MixnetContractPendingIntervalEventData::UpdateIntervalConfig {
epochs_in_interval,
epoch_duration_secs,
} => Ok(PendingIntervalEventData::UpdateIntervalConfig {
epochs_in_interval,
epoch_duration_secs,
}),
}
}
}
+211 -473
View File
File diff suppressed because it is too large Load Diff
+2 -5
View File
@@ -18,25 +18,22 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
config = { path = "../../common/config"}
#config = { path = "../../common/config"}
cosmwasm-std = "1.0.0"
cosmwasm-storage = "1.0.0"
cw-storage-plus = "0.13.4"
az = "1.2.0"
bs58 = "0.4.0"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
time = { version = "0.3", features = ["macros"] }
hex = "0.4.3"
[dev-dependencies]
cosmwasm-schema = "1.0.0"
fixed = "1.1"
rand_chacha = "0.2"
rand = "0.7"
#rand = "0.7"
crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
[build-dependencies]
+66 -12
View File
@@ -1,17 +1,71 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// approximately 1 epoch (assuming 5s per block)
pub const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 720;
use cosmwasm_std::Uint128;
pub const INTERVAL_REWARD_PERCENT: u8 = 2; // Used to calculate interval reward pool
pub const SYBIL_RESISTANCE_PERCENT: u8 = 30;
pub const ACTIVE_SET_WORK_FACTOR: u8 = 10;
/// Constant specifying minimum of coin amount required to bond a gateway
pub const INITIAL_GATEWAY_PLEDGE_AMOUNT: Uint128 = Uint128::new(100_000_000);
// TODO: this, in theory, represents "epoch" length.
// However, since the blocktime is not EXACTLY 5s, we can't really guarantee 720 epochs in interval
// and we can't change this easily to `Duration`, because then the entire rewarded set storage
// would be messed up... (as we look up stuff "by blocks")
pub const REWARDED_SET_REFRESH_BLOCKS: u64 = 720; // with blocktime being approximately 5s, it should be roughly 1h
pub const INTERVAL_SECONDS: u64 = 86400 * 30; // 30 days
pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000;
/// Constant specifying minimum of coin amount required to bond a mixnode
pub const INITIAL_MIXNODE_PLEDGE_AMOUNT: Uint128 = Uint128::new(100_000_000);
// retrieval limits
// TODO: those would need to be empirically verified whether they're not way too small or way too high
pub const GATEWAY_BOND_DEFAULT_RETRIEVAL_LIMIT: u32 = 100;
pub const GATEWAY_BOND_MAX_RETRIEVAL_LIMIT: u32 = 150;
pub const MIXNODE_BOND_DEFAULT_RETRIEVAL_LIMIT: u32 = 100;
pub const MIXNODE_BOND_MAX_RETRIEVAL_LIMIT: u32 = 150;
pub const MIXNODE_DETAILS_DEFAULT_RETRIEVAL_LIMIT: u32 = 75;
pub const MIXNODE_DETAILS_MAX_RETRIEVAL_LIMIT: u32 = 100;
pub const UNBONDED_MIXNODES_DEFAULT_RETRIEVAL_LIMIT: u32 = 250;
pub const UNBONDED_MIXNODES_MAX_RETRIEVAL_LIMIT: u32 = 300;
pub const DELEGATION_PAGE_DEFAULT_RETRIEVAL_LIMIT: u32 = 250;
pub const DELEGATION_PAGE_MAX_RETRIEVAL_LIMIT: u32 = 300;
pub const EPOCH_EVENTS_DEFAULT_RETRIEVAL_LIMIT: u32 = 200;
pub const EPOCH_EVENTS_MAX_RETRIEVAL_LIMIT: u32 = 250;
pub const INTERVAL_EVENTS_DEFAULT_RETRIEVAL_LIMIT: u32 = 200;
pub const INTERVAL_EVENTS_MAX_RETRIEVAL_LIMIT: u32 = 250;
pub const REWARDED_SET_DEFAULT_RETRIEVAL_LIMIT: u32 = 500;
pub const REWARDED_SET_MAX_RETRIEVAL_LIMIT: u32 = 1000;
// storage keys
pub(crate) const DELEGATION_PK_NAMESPACE: &str = "dl";
pub(crate) const DELEGATION_OWNER_IDX_NAMESPACE: &str = "dlo";
pub(crate) const DELEGATION_MIXNODE_IDX_NAMESPACE: &str = "dlm";
pub(crate) const GATEWAYS_PK_NAMESPACE: &str = "gt";
pub(crate) const GATEWAYS_OWNER_IDX_NAMESPACE: &str = "gto";
pub(crate) const REWARDED_SET_KEY: &str = "rs";
pub(crate) const CURRENT_INTERVAL_KEY: &str = "ci";
pub(crate) const EPOCH_EVENT_ID_COUNTER_KEY: &str = "eic";
pub(crate) const INTERVAL_EVENT_ID_COUNTER_KEY: &str = "iic";
pub(crate) const PENDING_EPOCH_EVENTS_NAMESPACE: &str = "pee";
pub(crate) const PENDING_INTERVAL_EVENTS_NAMESPACE: &str = "pie";
pub(crate) const LAST_EPOCH_EVENT_ID_KEY: &str = "lee";
pub(crate) const LAST_INTERVAL_EVENT_ID_KEY: &str = "lie";
pub(crate) const CONTRACT_STATE_KEY: &str = "state";
pub(crate) const LAYER_DISTRIBUTION_KEY: &str = "layers";
pub(crate) const NODE_ID_COUNTER_KEY: &str = "nic";
pub(crate) const MIXNODES_PK_NAMESPACE: &str = "mnn";
pub(crate) const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno";
pub(crate) const MIXNODES_IDENTITY_IDX_NAMESPACE: &str = "mni";
pub(crate) const MIXNODES_SPHINX_IDX_NAMESPACE: &str = "mns";
pub(crate) const UNBONDED_MIXNODES_PK_NAMESPACE: &str = "ubm";
pub(crate) const UNBONDED_MIXNODES_OWNER_IDX_NAMESPACE: &str = "umo";
pub(crate) const UNBONDED_MIXNODES_IDENTITY_IDX_NAMESPACE: &str = "umi";
pub(crate) const REWARDING_PARAMS_KEY: &str = "rparams";
pub(crate) const PENDING_REWARD_POOL_KEY: &str = "prp";
pub(crate) const MIXNODES_REWARDING_PK_NAMESPACE: &str = "mnr";
+411 -414
View File
@@ -1,82 +1,40 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::constants::{ACTIVE_SET_WORK_FACTOR, INTERVAL_REWARD_PERCENT, SYBIL_RESISTANCE_PERCENT};
use crate::delegations::queries::query_delegator_delegations_paged;
use crate::delegations::queries::query_mixnode_delegation;
use crate::delegations::queries::{
query_mixnode_delegations_paged, query_pending_delegation_events,
};
use crate::error::ContractError;
use crate::gateways::queries::query_owns_gateway;
use crate::gateways::queries::{query_gateway_bond, query_gateways_paged};
use crate::interval::queries::query_current_epoch;
use crate::interval::queries::{
query_current_rewarded_set_height, query_rewarded_set,
query_rewarded_set_refresh_minimum_blocks, query_rewarded_set_update_details,
};
use crate::interval::transactions::{init_epoch, try_init_epoch};
use crate::mixnet_contract_settings::models::ContractState;
use crate::mixnet_contract_settings::queries::{
query_contract_settings_params, query_contract_version, query_rewarding_validator_address,
};
use crate::constants::{INITIAL_GATEWAY_PLEDGE_AMOUNT, INITIAL_MIXNODE_PLEDGE_AMOUNT};
use crate::interval::storage as interval_storage;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnet_contract_settings::transactions::try_update_rewarding_validator_address;
use crate::mixnodes::bonding_queries as mixnode_queries;
use crate::mixnodes::bonding_queries::{
query_checkpoints_for_mixnode, query_mixnode_at_height, query_mixnodes_paged,
};
use crate::mixnodes::layer_queries::query_layer_distribution;
use crate::mixnodes::transactions::_try_remove_mixnode;
use crate::rewards::queries::{
query_circulating_supply, query_reward_pool, query_rewarding_status, query_staking_supply,
};
use crate::mixnodes::storage as mixnode_storage;
use crate::rewards::storage as rewards_storage;
use cosmwasm_std::{
entry_point, to_binary, Addr, Api, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
Storage, Uint128,
entry_point, to_binary, Addr, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{
ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, NodeToRemove, QueryMsg,
ContractState, ContractStateParams, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg, QueryMsg,
};
use time::OffsetDateTime;
/// Constant specifying minimum of coin required to bond a gateway
pub const INITIAL_GATEWAY_PLEDGE: Uint128 = Uint128::new(100_000_000);
/// Constant specifying minimum of coin required to bond a mixnode
pub const INITIAL_MIXNODE_PLEDGE: Uint128 = Uint128::new(100_000_000);
pub const INITIAL_MIXNODE_REWARDED_SET_SIZE: u32 = 200;
pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100;
pub const INITIAL_REWARD_POOL: u128 = 250_000_000_000_000;
pub const INITIAL_ACTIVE_SET_WORK_FACTOR: u8 = 10;
pub const DEFAULT_FIRST_INTERVAL_START: OffsetDateTime =
time::macros::datetime!(2022-01-01 12:00 UTC);
pub const INITIAL_STAKING_SUPPLY: Uint128 = Uint128::new(100_000_000_000_000);
pub fn debug_with_visibility<S: Into<String>>(api: &dyn Api, msg: S) {
api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into()));
}
fn default_initial_state(
owner: Addr,
mix_denom: String,
rewarding_validator_address: Addr,
rewarding_denom: String,
vesting_contract_address: Addr,
) -> ContractState {
ContractState {
owner,
mix_denom,
rewarding_validator_address,
vesting_contract_address,
rewarding_denom: rewarding_denom.clone(),
params: ContractStateParams {
minimum_mixnode_pledge: INITIAL_MIXNODE_PLEDGE,
minimum_gateway_pledge: INITIAL_GATEWAY_PLEDGE,
mixnode_rewarded_set_size: INITIAL_MIXNODE_REWARDED_SET_SIZE,
mixnode_active_set_size: INITIAL_MIXNODE_ACTIVE_SET_SIZE,
staking_supply: INITIAL_STAKING_SUPPLY,
minimum_mixnode_delegation: None,
minimum_mixnode_pledge: Coin {
denom: rewarding_denom.clone(),
amount: INITIAL_MIXNODE_PLEDGE_AMOUNT,
},
minimum_gateway_pledge: Coin {
denom: rewarding_denom,
amount: INITIAL_GATEWAY_PLEDGE_AMOUNT,
},
},
}
}
@@ -92,14 +50,25 @@ pub fn instantiate(
env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
) -> Result<Response, MixnetContractError> {
let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?;
let state = default_initial_state(info.sender, msg.mixnet_denom, rewarding_validator_address);
init_epoch(deps.storage, env)?;
let vesting_contract_address = deps.api.addr_validate(&msg.vesting_contract_address)?;
let state = default_initial_state(
info.sender,
rewarding_validator_address,
msg.rewarding_denom,
vesting_contract_address,
);
let starting_interval =
Interval::init_interval(msg.epochs_in_interval, msg.epoch_duration, &env);
let reward_params = msg
.initial_rewarding_params
.into_rewarding_params(msg.epochs_in_interval);
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &state)?;
mixnet_params_storage::LAYERS.save(deps.storage, &Default::default())?;
rewards_storage::REWARD_POOL.save(deps.storage, &Uint128::new(INITIAL_REWARD_POOL))?;
interval_storage::initialise_storage(deps.storage, starting_interval)?;
mixnet_params_storage::initialise_storage(deps.storage, state)?;
mixnode_storage::initialise_storage(deps.storage)?;
rewards_storage::initialise_storage(deps.storage, reward_params)?;
Ok(Response::default())
}
@@ -111,56 +80,118 @@ pub fn execute(
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
) -> Result<Response, MixnetContractError> {
match msg {
ExecuteMsg::CompoundReward {
operator,
delegator,
mix_identity,
proxy,
} => crate::rewards::transactions::try_compound_reward(
// state/sys-params-related
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
crate::mixnet_contract_settings::transactions::try_update_rewarding_validator_address(
deps, info, address,
)
}
ExecuteMsg::UpdateContractStateParams { updated_parameters } => {
crate::mixnet_contract_settings::transactions::try_update_contract_settings(
deps,
info,
updated_parameters,
)
}
ExecuteMsg::UpdateActiveSetSize {
active_set_size,
force_immediately,
} => crate::rewards::transactions::try_update_active_set_size(
deps,
env,
operator,
delegator,
mix_identity,
proxy,
info,
active_set_size,
force_immediately,
),
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
try_update_rewarding_validator_address(deps, info, address)
ExecuteMsg::UpdateRewardingParams {
updated_params,
force_immediately,
} => crate::rewards::transactions::try_update_rewarding_params(
deps,
env,
info,
updated_params,
force_immediately,
),
ExecuteMsg::UpdateIntervalConfig {
epochs_in_interval,
epoch_duration_secs,
force_immediately,
} => crate::interval::transactions::try_update_interval_config(
deps,
env,
info,
epochs_in_interval,
epoch_duration_secs,
force_immediately,
),
ExecuteMsg::AdvanceCurrentEpoch {
new_rewarded_set,
expected_active_set_size,
} => crate::interval::transactions::try_advance_epoch(
deps,
env,
info,
new_rewarded_set,
expected_active_set_size,
),
ExecuteMsg::ReconcileEpochEvents { limit } => {
crate::interval::transactions::try_reconcile_epoch_events(deps, env, limit)
}
ExecuteMsg::InitEpoch {} => try_init_epoch(info, deps.storage, env),
// mixnode-related:
ExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
} => crate::mixnodes::transactions::try_add_mixnode(
deps,
env,
info,
mix_node,
cost_params,
owner_signature,
),
ExecuteMsg::BondMixnodeOnBehalf {
mix_node,
cost_params,
owner,
owner_signature,
} => crate::mixnodes::transactions::try_add_mixnode_on_behalf(
deps,
env,
info,
mix_node,
cost_params,
owner,
owner_signature,
),
ExecuteMsg::UnbondMixnode {} => {
crate::mixnodes::transactions::try_remove_mixnode(&env, deps.storage, deps.api, info)
crate::mixnodes::transactions::try_remove_mixnode(deps, info)
}
ExecuteMsg::UpdateMixnodeConfig {
profit_margin_percent,
} => crate::mixnodes::transactions::try_update_mixnode_config(
deps,
env,
info,
profit_margin_percent,
),
ExecuteMsg::UpdateMixnodeConfigOnBehalf {
profit_margin_percent,
owner,
} => crate::mixnodes::transactions::try_update_mixnode_config_on_behalf(
deps,
env,
info,
profit_margin_percent,
owner,
),
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(deps, info, owner)
}
ExecuteMsg::UpdateMixnodeCostParams { new_costs } => {
crate::mixnodes::transactions::try_update_mixnode_cost_params(deps, info, new_costs)
}
ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { new_costs, owner } => {
crate::mixnodes::transactions::try_update_mixnode_cost_params_on_behalf(
deps, info, new_costs, owner,
)
}
ExecuteMsg::UpdateMixnodeConfig { new_config } => {
crate::mixnodes::transactions::try_update_mixnode_config(deps, info, new_config)
}
ExecuteMsg::UpdateMixnodeConfigOnBehalf { new_config, owner } => {
crate::mixnodes::transactions::try_update_mixnode_config_on_behalf(
deps, info, new_config, owner,
)
}
// gateway-related:
ExecuteMsg::BondGateway {
gateway,
owner_signature,
@@ -171,78 +202,6 @@ pub fn execute(
gateway,
owner_signature,
),
ExecuteMsg::UnbondGateway {} => {
crate::gateways::transactions::try_remove_gateway(deps, info)
}
ExecuteMsg::UpdateContractStateParams(params) => {
crate::mixnet_contract_settings::transactions::try_update_contract_settings(
deps, info, params,
)
}
ExecuteMsg::RewardMixnode { identity, params } => {
crate::rewards::transactions::try_reward_mixnode(deps, env, info, identity, params)
}
ExecuteMsg::DelegateToMixnode { mix_identity } => {
crate::delegations::transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
}
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
crate::delegations::transactions::try_remove_delegation_from_mixnode(
deps,
env,
info,
mix_identity,
)
}
// ExecuteMsg::RewardNextMixDelegators {
// mix_identity,
// interval_id,
// } => crate::rewards::transactions::try_reward_next_mixnode_delegators(
// deps,
// info,
// mix_identity,
// interval_id,
// ),
ExecuteMsg::DelegateToMixnodeOnBehalf {
mix_identity,
delegate,
} => crate::delegations::transactions::try_delegate_to_mixnode_on_behalf(
deps,
env,
info,
mix_identity,
delegate,
),
ExecuteMsg::UndelegateFromMixnodeOnBehalf {
mix_identity,
delegate,
} => crate::delegations::transactions::try_remove_delegation_from_mixnode_on_behalf(
deps,
env,
info,
mix_identity,
delegate,
),
ExecuteMsg::BondMixnodeOnBehalf {
mix_node,
owner,
owner_signature,
} => crate::mixnodes::transactions::try_add_mixnode_on_behalf(
deps,
env,
info,
mix_node,
owner,
owner_signature,
),
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(
&env,
deps.storage,
deps.api,
info,
owner,
)
}
ExecuteMsg::BondGatewayOnBehalf {
gateway,
owner,
@@ -255,303 +214,341 @@ pub fn execute(
owner,
owner_signature,
),
ExecuteMsg::UnbondGateway {} => {
crate::gateways::transactions::try_remove_gateway(deps, info)
}
ExecuteMsg::UnbondGatewayOnBehalf { owner } => {
crate::gateways::transactions::try_remove_gateway_on_behalf(deps, info, owner)
}
ExecuteMsg::WriteRewardedSet {
rewarded_set,
expected_active_set_size,
} => crate::interval::transactions::try_write_rewarded_set(
deps,
env,
info,
rewarded_set,
expected_active_set_size,
),
ExecuteMsg::AdvanceCurrentEpoch {} => crate::interval::transactions::try_advance_epoch(
env,
deps.storage,
info.sender.to_string(),
),
ExecuteMsg::CompoundDelegatorReward { mix_identity } => {
crate::rewards::transactions::try_compound_delegator_reward(
deps,
env,
info,
mix_identity,
// delegation-related:
ExecuteMsg::DelegateToMixnode { mix_id } => {
crate::delegations::transactions::try_delegate_to_mixnode(deps, info, mix_id)
}
ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, delegate } => {
crate::delegations::transactions::try_delegate_to_mixnode_on_behalf(
deps, info, mix_id, delegate,
)
}
ExecuteMsg::CompoundOperatorReward {} => {
crate::rewards::transactions::try_compound_operator_reward(deps, env, info)
ExecuteMsg::UndelegateFromMixnode { mix_id } => {
crate::delegations::transactions::try_remove_delegation_from_mixnode(deps, info, mix_id)
}
ExecuteMsg::CompoundDelegatorRewardOnBehalf {
owner,
mix_identity,
} => crate::rewards::transactions::try_compound_delegator_reward_on_behalf(
deps,
env,
info,
owner,
mix_identity,
),
ExecuteMsg::CompoundOperatorRewardOnBehalf { owner } => {
crate::rewards::transactions::try_compound_operator_reward_on_behalf(
deps, env, info, owner,
ExecuteMsg::UndelegateFromMixnodeOnBehalf { mix_id, delegate } => {
crate::delegations::transactions::try_remove_delegation_from_mixnode_on_behalf(
deps, info, mix_id, delegate,
)
}
ExecuteMsg::ReconcileDelegations {} => {
crate::delegations::transactions::try_reconcile_all_delegation_events(deps)
// reward-related
ExecuteMsg::RewardMixnode {
mix_id,
performance,
} => crate::rewards::transactions::try_reward_mixnode(deps, env, info, mix_id, performance),
ExecuteMsg::WithdrawOperatorReward {} => {
crate::rewards::transactions::try_withdraw_operator_reward(deps, info)
}
ExecuteMsg::CheckpointMixnodes {} => {
crate::mixnodes::transactions::try_checkpoint_mixnodes(
deps.storage,
env.block.height,
info,
ExecuteMsg::WithdrawOperatorRewardOnBehalf { owner } => {
crate::rewards::transactions::try_withdraw_operator_reward_on_behalf(deps, info, owner)
}
ExecuteMsg::WithdrawDelegatorReward { mix_id } => {
crate::rewards::transactions::try_withdraw_delegator_reward(deps, info, mix_id)
}
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => {
crate::rewards::transactions::try_withdraw_delegator_reward_on_behalf(
deps, info, mix_id, owner,
)
}
ExecuteMsg::ClaimOperatorReward {} => {
crate::rewards::transactions::try_claim_operator_reward(deps, &env, &info)
}
ExecuteMsg::ClaimOperatorRewardOnBehalf { owner } => {
crate::rewards::transactions::try_claim_operator_reward_on_behalf(
deps, &env, &info, owner,
)
}
ExecuteMsg::ClaimDelegatorReward { mix_identity } => {
crate::rewards::transactions::try_claim_delegator_reward(
deps,
&env,
&info,
&mix_identity,
)
}
ExecuteMsg::ClaimDelegatorRewardOnBehalf {
mix_identity,
owner,
} => crate::rewards::transactions::try_claim_delegator_reward_on_behalf(
deps,
&env,
&info,
owner,
&mix_identity,
),
}
}
#[entry_point]
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
pub fn query(
deps: Deps<'_>,
env: Env,
msg: QueryMsg,
) -> Result<QueryResponse, MixnetContractError> {
let query_res = match msg {
QueryMsg::GetBlacklistedNodes {} => to_binary(
&crate::mixnodes::bonding_queries::get_blacklisted_nodes(deps),
QueryMsg::GetContractVersion {} => {
to_binary(&crate::mixnet_contract_settings::queries::query_contract_version())
}
QueryMsg::GetStateParams {} => to_binary(
&crate::mixnet_contract_settings::queries::query_contract_settings_params(deps)?,
),
QueryMsg::GetRewardingValidatorAddress {} => {
to_binary(&query_rewarding_validator_address(deps)?)
QueryMsg::GetRewardingValidatorAddress {} => to_binary(
&crate::mixnet_contract_settings::queries::query_rewarding_validator_address(deps)?,
),
QueryMsg::GetState {} => {
to_binary(&crate::mixnet_contract_settings::queries::query_contract_state(deps)?)
}
QueryMsg::GetContractVersion {} => to_binary(&query_contract_version()),
QueryMsg::GetMixNodes { start_after, limit } => {
to_binary(&query_mixnodes_paged(deps, start_after, limit)?)
QueryMsg::GetRewardingParams {} => {
to_binary(&crate::rewards::queries::query_rewarding_params(deps)?)
}
QueryMsg::GetGateways { limit, start_after } => {
to_binary(&query_gateways_paged(deps, start_after, limit)?)
QueryMsg::GetCurrentIntervalDetails {} => to_binary(
&crate::interval::queries::query_current_interval_details(deps, env)?,
),
QueryMsg::GetRewardedSet { limit, start_after } => to_binary(
&crate::interval::queries::query_rewarded_set_paged(deps, start_after, limit)?,
),
// mixnode-related:
QueryMsg::GetMixNodeBonds { start_after, limit } => to_binary(
&crate::mixnodes::queries::query_mixnode_bonds_paged(deps, start_after, limit)?,
),
QueryMsg::GetMixNodesDetailed { start_after, limit } => to_binary(
&crate::mixnodes::queries::query_mixnodes_details_paged(deps, start_after, limit)?,
),
QueryMsg::GetUnbondedMixNodes { limit, start_after } => to_binary(
&crate::mixnodes::queries::query_unbonded_mixnodes_paged(deps, start_after, limit)?,
),
QueryMsg::GetUnbondedMixNodesByOwner {
owner,
limit,
start_after,
} => to_binary(
&crate::mixnodes::queries::query_unbonded_mixnodes_by_owner_paged(
deps,
owner,
start_after,
limit,
)?,
),
QueryMsg::GetUnbondedMixNodesByIdentityKey {
identity_key,
limit,
start_after,
} => to_binary(
&crate::mixnodes::queries::query_unbonded_mixnodes_by_identity_paged(
deps,
identity_key,
start_after,
limit,
)?,
),
QueryMsg::GetOwnedMixnode { address } => to_binary(
&crate::mixnodes::queries::query_owned_mixnode(deps, address)?,
),
QueryMsg::GetMixnodeDetails { mix_id } => to_binary(
&crate::mixnodes::queries::query_mixnode_details(deps, mix_id)?,
),
QueryMsg::GetMixnodeRewardingDetails { mix_id } => to_binary(
&crate::mixnodes::queries::query_mixnode_rewarding_details(deps, mix_id)?,
),
QueryMsg::GetStakeSaturation { mix_id } => to_binary(
&crate::mixnodes::queries::query_stake_saturation(deps, mix_id)?,
),
QueryMsg::GetUnbondedMixNodeInformation { mix_id } => to_binary(
&crate::mixnodes::queries::query_unbonded_mixnode(deps, mix_id)?,
),
QueryMsg::GetBondedMixnodeDetailsByIdentity { mix_identity } => to_binary(
&crate::mixnodes::queries::query_mixnode_details_by_identity(deps, mix_identity)?,
),
QueryMsg::GetLayerDistribution {} => {
to_binary(&crate::mixnodes::queries::query_layer_distribution(deps)?)
}
QueryMsg::OwnsMixnode { address } => {
to_binary(&mixnode_queries::query_owns_mixnode(deps, address)?)
}
QueryMsg::GetMixnodeBond { identity } => {
to_binary(&mixnode_queries::query_mixnode_bond(deps, identity)?)
}
QueryMsg::GetGatewayBond { identity } => to_binary(&query_gateway_bond(deps, identity)?),
QueryMsg::OwnsGateway { address } => to_binary(&query_owns_gateway(deps, address)?),
QueryMsg::StateParams {} => to_binary(&query_contract_settings_params(deps)?),
QueryMsg::LayerDistribution {} => to_binary(&query_layer_distribution(deps)?),
// gateway-related:
QueryMsg::GetGateways { limit, start_after } => to_binary(
&crate::gateways::queries::query_gateways_paged(deps, start_after, limit)?,
),
QueryMsg::GetGatewayBond { identity } => to_binary(
&crate::gateways::queries::query_gateway_bond(deps, identity)?,
),
QueryMsg::GetOwnedGateway { address } => to_binary(
&crate::gateways::queries::query_owned_gateway(deps, address)?,
),
// delegation-related:
QueryMsg::GetMixnodeDelegations {
mix_identity,
mix_id,
start_after,
limit,
} => to_binary(&query_mixnode_delegations_paged(
deps,
mix_identity,
start_after,
limit,
)?),
} => to_binary(
&crate::delegations::queries::query_mixnode_delegations_paged(
deps,
mix_id,
start_after,
limit,
)?,
),
QueryMsg::GetDelegatorDelegations {
delegator: delegation_owner,
delegator,
start_after,
limit,
} => to_binary(&query_delegator_delegations_paged(
deps,
delegation_owner,
start_after,
limit,
)?),
} => to_binary(
&crate::delegations::queries::query_delegator_delegations_paged(
deps,
delegator,
start_after,
limit,
)?,
),
QueryMsg::GetDelegationDetails {
mix_identity,
delegator,
proxy,
} => to_binary(&query_mixnode_delegation(
deps.storage,
deps.api,
mix_identity,
mix_id,
delegator,
proxy,
} => to_binary(&crate::delegations::queries::query_mixnode_delegation(
deps, mix_id, delegator, proxy,
)?),
QueryMsg::GetRewardPool {} => to_binary(&query_reward_pool(deps)?),
QueryMsg::GetCirculatingSupply {} => to_binary(&query_circulating_supply(deps)?),
QueryMsg::GetStakingSupply {} => to_binary(&query_staking_supply(deps)?),
QueryMsg::GetIntervalRewardPercent {} => to_binary(&INTERVAL_REWARD_PERCENT),
QueryMsg::GetSybilResistancePercent {} => to_binary(&SYBIL_RESISTANCE_PERCENT),
QueryMsg::GetActiveSetWorkFactor {} => to_binary(&ACTIVE_SET_WORK_FACTOR),
QueryMsg::GetRewardingStatus {
mix_identity,
interval_id,
} => to_binary(&query_rewarding_status(deps, mix_identity, interval_id)?),
QueryMsg::GetRewardedSet {
height,
start_after,
limit,
} => to_binary(&query_rewarded_set(
deps.storage,
height,
start_after,
limit,
)?),
QueryMsg::GetRewardedSetUpdateDetails {} => {
to_binary(&query_rewarded_set_update_details(env, deps.storage)?)
}
QueryMsg::GetCurrentRewardedSetHeight {} => {
to_binary(&query_current_rewarded_set_height(deps.storage)?)
}
// QueryMsg::GetCurrentInterval {} => to_binary(&query_current_interval(deps.storage)?),
QueryMsg::GetRewardedSetRefreshBlocks {} => {
to_binary(&query_rewarded_set_refresh_minimum_blocks())
}
QueryMsg::GetEpochsInInterval {} => {
to_binary(&crate::support::helpers::epochs_in_interval(deps.storage)?)
}
QueryMsg::GetCurrentOperatorCost {} => to_binary(
&crate::support::helpers::current_operator_epoch_cost(deps.storage)?,
QueryMsg::GetAllDelegations { start_after, limit } => to_binary(
&crate::delegations::queries::query_all_delegations_paged(deps, start_after, limit)?,
),
QueryMsg::GetCurrentEpoch {} => to_binary(&query_current_epoch(deps.storage)?),
QueryMsg::QueryOperatorReward { address } => to_binary(
&crate::rewards::queries::query_operator_reward(deps, address)?,
// rewards related
QueryMsg::GetPendingOperatorReward { address } => to_binary(
&crate::rewards::queries::query_pending_operator_reward(deps, address)?,
),
QueryMsg::QueryDelegatorReward {
QueryMsg::GetPendingMixNodeOperatorReward { mix_id } => to_binary(
&crate::rewards::queries::query_pending_mixnode_operator_reward(deps, mix_id)?,
),
QueryMsg::GetPendingDelegatorReward {
address,
mix_identity,
mix_id,
proxy,
} => to_binary(&crate::rewards::queries::query_delegator_reward(
deps,
} => to_binary(&crate::rewards::queries::query_pending_delegator_reward(
deps, address, mix_id, proxy,
)?),
QueryMsg::GetEstimatedCurrentEpochOperatorReward {
mix_id,
estimated_performance,
} => to_binary(
&crate::rewards::queries::query_estimated_current_epoch_operator_reward(
deps,
mix_id,
estimated_performance,
)?,
),
QueryMsg::GetEstimatedCurrentEpochDelegatorReward {
address,
mix_identity,
mix_id,
proxy,
)?),
QueryMsg::GetPendingDelegationEvents {
owner_address,
proxy_address,
} => to_binary(&query_pending_delegation_events(
deps,
owner_address,
proxy_address,
)?),
QueryMsg::GetAllDelegationKeys {} => to_binary(
&crate::delegations::queries::query_all_delegation_keys(deps.storage)?,
estimated_performance,
} => to_binary(
&crate::rewards::queries::query_estimated_current_epoch_delegator_reward(
deps,
address,
mix_id,
proxy,
estimated_performance,
)?,
),
QueryMsg::DebugGetAllDelegationValues {} => to_binary(
&crate::delegations::queries::debug_query_all_delegation_values(deps.storage)?,
),
QueryMsg::GetCheckpointsForMixnode { mix_identity } => {
to_binary(&query_checkpoints_for_mixnode(deps, mix_identity)?)
// interval-related
QueryMsg::GetPendingEpochEvents { limit, start_after } => {
to_binary(&crate::interval::queries::query_pending_epoch_events_paged(
deps,
env,
start_after,
limit,
)?)
}
QueryMsg::GetMixnodeAtHeight {
mix_identity,
height,
} => to_binary(&query_mixnode_at_height(deps, mix_identity, height)?),
QueryMsg::GetPendingIntervalEvents { limit, start_after } => to_binary(
&crate::interval::queries::query_pending_interval_events_paged(
deps,
env,
start_after,
limit,
)?,
),
};
Ok(query_res?)
}
#[allow(unused)]
fn blacklist_malicious_node(storage: &mut dyn Storage, owner: &Addr) -> Result<(), ContractError> {
let mixnode_bond = match crate::mixnodes::storage::mixnodes()
.idx
.owner
.item(storage, owner.clone())?
{
Some(record) => record.1,
None => {
return Err(ContractError::NoAssociatedMixNodeBond {
owner: owner.to_owned(),
})
}
};
crate::mixnodes::storage::MIXNODES_BOND_BLACKLIST.save(storage, mixnode_bond.identity(), &0)?;
Ok(())
}
// Removes nodes we've deemed malicious, returns the pledge to the owners, but does not send any rewards
#[allow(unused)]
fn remove_malicious_node(
storage: &mut dyn Storage,
api: &dyn Api,
env: &Env,
node: &NodeToRemove,
) -> Result<Response, ContractError> {
let proxy = node.proxy().map(|p| {
api.addr_validate(p)
.unwrap_or_else(|_| panic!("Invalid address: {}", p))
});
let owner_addr = api.addr_validate(node.owner())?;
blacklist_malicious_node(storage, &owner_addr)?;
_try_remove_mixnode(env, storage, api, node.owner(), proxy, false)
}
#[entry_point]
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
Ok(Response::default())
pub fn migrate(
_deps: DepsMut<'_>,
_env: Env,
_msg: MigrateMsg,
) -> Result<Response, MixnetContractError> {
Ok(Default::default())
}
#[cfg(test)]
pub mod tests {
mod tests {
use super::*;
use crate::support::tests;
use crate::support::tests::fixtures::{TEST_COIN_DENOM, TEST_REWARDING_VALIDATOR_ADDRESS};
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, from_binary};
use mixnet_contract_common::PagedMixnodeResponse;
use cosmwasm_std::Decimal;
use mixnet_contract_common::reward_params::{IntervalRewardParams, RewardingParams};
use mixnet_contract_common::{InitialRewardingParams, Percent};
use std::time::Duration;
#[test]
fn initialize_contract() {
let mut deps = mock_dependencies();
let env = mock_env();
let msg = InstantiateMsg {
rewarding_validator_address: TEST_REWARDING_VALIDATOR_ADDRESS.to_string(),
mixnet_denom: TEST_COIN_DENOM.to_string(),
};
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),
let init_msg = InstantiateMsg {
rewarding_validator_address: "foomp123".to_string(),
vesting_contract_address: "bar456".to_string(),
rewarding_denom: "uatom".to_string(),
epochs_in_interval: 1234,
epoch_duration: Duration::from_secs(4321),
initial_rewarding_params: InitialRewardingParams {
initial_reward_pool: Decimal::from_atomics(100_000_000_000_000u128, 0).unwrap(),
initial_staking_supply: Decimal::from_atomics(123_456_000_000_000u128, 0).unwrap(),
sybil_resistance: Percent::from_percentage_value(23).unwrap(),
active_set_work_factor: Decimal::from_atomics(10u32, 0).unwrap(),
interval_pool_emission: Percent::from_percentage_value(1).unwrap(),
rewarded_set_size: 543,
active_set_size: 123,
},
)
.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, TEST_COIN_DENOM),
tests::queries::query_contract_balance(env.contract.address, deps)
);
let sender = mock_info("sender", &[]);
let res = instantiate(deps.as_mut(), env, sender, init_msg);
assert!(res.is_ok());
let expected_state = ContractState {
owner: Addr::unchecked("sender"),
rewarding_validator_address: Addr::unchecked("foomp123"),
vesting_contract_address: Addr::unchecked("bar456"),
rewarding_denom: "uatom".into(),
params: ContractStateParams {
minimum_mixnode_delegation: None,
minimum_mixnode_pledge: Coin {
denom: "uatom".into(),
amount: INITIAL_MIXNODE_PLEDGE_AMOUNT,
},
minimum_gateway_pledge: Coin {
denom: "uatom".into(),
amount: INITIAL_GATEWAY_PLEDGE_AMOUNT,
},
},
};
let expected_epoch_reward_budget =
Decimal::from_ratio(100_000_000_000_000u128, 1234u32) * Decimal::percent(1);
let expected_stake_saturation_point = Decimal::from_ratio(123_456_000_000_000u128, 543u32);
let expected_rewarding_params = RewardingParams {
interval: IntervalRewardParams {
reward_pool: Decimal::from_atomics(100_000_000_000_000u128, 0).unwrap(),
staking_supply: Decimal::from_atomics(123_456_000_000_000u128, 0).unwrap(),
epoch_reward_budget: expected_epoch_reward_budget,
stake_saturation_point: expected_stake_saturation_point,
sybil_resistance: Percent::from_percentage_value(23).unwrap(),
active_set_work_factor: Decimal::from_atomics(10u32, 0).unwrap(),
interval_pool_emission: Percent::from_percentage_value(1).unwrap(),
},
rewarded_set_size: 543,
active_set_size: 123,
};
let state = mixnet_params_storage::CONTRACT_STATE
.load(deps.as_ref().storage)
.unwrap();
assert_eq!(state, expected_state);
let rewarding_params = rewards_storage::REWARDING_PARAMS
.load(deps.as_ref().storage)
.unwrap();
assert_eq!(rewarding_params, expected_rewarding_params);
let interval = interval_storage::current_interval(deps.as_ref().storage).unwrap();
assert_eq!(interval.epochs_in_interval(), 1234);
assert_eq!(interval.epoch_length(), Duration::from_secs(4321));
assert_eq!(interval.current_interval_id(), 0);
assert_eq!(interval.current_epoch_id(), 0);
}
}
@@ -0,0 +1,65 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::delegations::storage;
use crate::rewards::storage as rewards_storage;
use cosmwasm_std::{Coin, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::mixnode::MixNodeRewarding;
use mixnet_contract_common::Delegation;
pub(crate) fn undelegate(
store: &mut dyn Storage,
delegation: Delegation,
mut mix_rewarding: MixNodeRewarding,
) -> Result<Coin, MixnetContractError> {
let tokens = mix_rewarding.undelegate(&delegation)?;
rewards_storage::MIXNODE_REWARDING.save(store, delegation.node_id, &mix_rewarding)?;
storage::delegations().replace(store, delegation.storage_key(), None, Some(&delegation))?;
Ok(tokens)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::support::tests::test_helpers::{performance, TestSetup};
use cosmwasm_std::{Addr, Decimal, Uint128};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
#[test]
fn undelegation_updates_mix_rewarding_storage_and_deletes_delegation() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", Some(Uint128::new(100_000_000_000)));
let delegator = "delegator";
let og_amount = Uint128::new(200_000_000);
test.add_immediate_delegation(delegator, og_amount, mix_id);
test.skip_to_next_epoch_end();
test.update_rewarded_set(vec![mix_id]);
let dist1 = test.reward_with_distribution(mix_id, performance(100.0));
test.skip_to_next_epoch_end();
let dist2 = test.reward_with_distribution(mix_id, performance(100.0));
let mix_rewarding = test.mix_rewarding(mix_id);
let delegation = test.delegation(mix_id, delegator, &None);
let expected_amount = og_amount + truncate_reward_amount(dist1.delegates + dist2.delegates);
let res = undelegate(test.deps_mut().storage, delegation, mix_rewarding).unwrap();
assert_eq!(res.amount, expected_amount);
let mix_rewarding = test.mix_rewarding(mix_id);
assert_eq!(mix_rewarding.delegates, Decimal::zero());
assert_eq!(mix_rewarding.unique_delegations, 0);
let storage_key =
Delegation::generate_storage_key(mix_id, &Addr::unchecked(delegator), None);
assert!(storage::delegations()
.may_load(test.deps().storage, storage_key)
.unwrap()
.is_none());
}
}

Some files were not shown because too many files have changed in this diff Show More