diff --git a/.gitignore b/.gitignore index 0298a1606d..b81c85f5e8 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,5 @@ contracts/mixnet/code_id contracts/mixnet/Justfile contracts/mixnet/Makefile validator-config -*.patch \ No newline at end of file +*.patch +validator-api-config.toml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8b9e98a4d2..d9715ae15b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -901,8 +901,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9" dependencies = [ "digest 0.9.0", "ed25519-zebra", @@ -913,16 +914,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2" dependencies = [ "syn", ] [[package]] name = "cosmwasm-std" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6" dependencies = [ "base64", "cosmwasm-crypto", diff --git a/Cargo.toml b/Cargo.toml index bcd28c5d55..b6993dff6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ default-members = [ "service-providers/network-requester", "mixnode", "validator-api", + "explorer-api", ] exclude = ["explorer", "contracts", "tokenomics-py"] diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index e2f7add411..9053439d81 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -31,7 +31,7 @@ prost = { version = "0.9", default-features = false, optional = true } flate2 = { version = "1.0.20", optional = true } sha2 = { version = "0.9.5", optional = true } itertools = { version = "0.10", optional = true } -cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true } +cosmwasm-std = { version = "1.0.0-beta2", optional = true } ts-rs = {version = "5.1", optional = true} [features] diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index f3d6a2b9a3..049418431a 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -10,11 +10,10 @@ use mixnet_contract::ContractSettingsParams; use crate::{validator_api, ValidatorClientError}; use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse}; -use mixnet_contract::{GatewayBond, MixNodeBond}; +use mixnet_contract::{Delegation, GatewayBond, MixNodeBond}; #[cfg(feature = "nymd-client")] use mixnet_contract::{ - MixnetContractVersion, MixnodeRewardingStatusResponse, RawDelegationData, - RewardingIntervalResponse, + MixnetContractVersion, MixnodeRewardingStatusResponse, RewardingIntervalResponse, }; use url::Url; @@ -312,9 +311,7 @@ impl Client { Ok(delegations) } - pub async fn get_all_nymd_mixnode_delegations( - &self, - ) -> Result>, ValidatorClientError> + pub async fn get_all_network_delegations(&self) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync, { @@ -323,7 +320,7 @@ impl Client { loop { let mut paged_response = self .nymd - .get_all_mix_delegations_paged( + .get_all_network_delegations_paged( start_after.take(), self.mixnode_delegations_page_limit, ) @@ -340,10 +337,10 @@ impl Client { Ok(delegations) } - pub async fn get_all_nymd_reverse_mixnode_delegations( + pub async fn get_all_delegator_delegations( &self, delegation_owner: &cosmrs::AccountId, - ) -> Result, ValidatorClientError> + ) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync, { @@ -352,13 +349,13 @@ impl Client { loop { let mut paged_response = self .nymd - .get_reverse_mix_delegations_paged( - mixnet_contract::Addr::unchecked(delegation_owner.as_ref()), + .get_delegator_delegations_paged( + delegation_owner.to_string(), start_after.take(), self.mixnode_delegations_page_limit, ) .await?; - delegations.append(&mut paged_response.delegated_nodes); + delegations.append(&mut paged_response.delegations); if let Some(start_after_res) = paged_response.start_next_after { start_after = Some(start_after_res) @@ -370,28 +367,6 @@ impl Client { Ok(delegations) } - pub async fn get_all_nymd_mixnode_delegations_of_owner( - &self, - delegation_owner: &cosmrs::AccountId, - ) -> Result, ValidatorClientError> - where - C: CosmWasmClient + Sync, - { - let mut delegations = Vec::new(); - for node_identity in self - .get_all_nymd_reverse_mixnode_delegations(delegation_owner) - .await? - { - let delegation = self - .nymd - .get_mix_delegation(node_identity, delegation_owner) - .await?; - delegations.push(delegation); - } - - Ok(delegations) - } - pub async fn blind_sign( &self, request_body: &BlindSignRequestBody, diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs index 462b5bb580..961deffadc 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs @@ -29,7 +29,7 @@ pub(crate) fn find_attribute<'a>( ) -> Option<&'a cosmwasm_std::Attribute> { logs.iter() .flat_map(|log| log.events.iter()) - .find(|event| event.kind == event_type)? + .find(|event| event.ty == event_type)? .attributes .iter() .find(|attr| attr.key == attribute_key) @@ -61,7 +61,7 @@ mod tests { assert_eq!(parsed.len(), 1); assert_eq!(parsed[0].msg_index, 0); assert_eq!(parsed[0].events.len(), 1); - assert_eq!(parsed[0].events[0].kind, "message"); + assert_eq!(parsed[0].events[0].ty, "message"); assert_eq!(parsed[0].events[0].attributes[3].key, "code_id"); assert_eq!(parsed[0].events[0].attributes[3].value, "1"); } @@ -76,12 +76,12 @@ mod tests { assert_eq!(parsed[2].msg_index, 2); assert_eq!(parsed[0].events.len(), 1); - assert_eq!(parsed[0].events[0].kind, "message"); + assert_eq!(parsed[0].events[0].ty, "message"); assert_eq!(parsed[0].events[0].attributes[3].key, "code_id"); assert_eq!(parsed[0].events[0].attributes[3].value, "9"); assert_eq!(parsed[2].events.len(), 1); - assert_eq!(parsed[2].events[0].kind, "message"); + assert_eq!(parsed[2].events[0].ty, "message"); assert_eq!(parsed[2].events[0].attributes[2].key, "signer"); assert_eq!( parsed[2].events[0].attributes[2].value, diff --git a/common/client-libs/validator-client/src/nymd/gas_price.rs b/common/client-libs/validator-client/src/nymd/gas_price.rs index 7e412b08b7..1924454ddf 100644 --- a/common/client-libs/validator-client/src/nymd/gas_price.rs +++ b/common/client-libs/validator-client/src/nymd/gas_price.rs @@ -38,7 +38,7 @@ impl<'a> Mul for &'a GasPrice { // however, realistically that is impossible to happen as the resultant value // would have to be way higher than our token limit of 10^15 (1 billion of tokens * 1 million for denomination) // and max value of u128 is approximately 10^38 - if limit_uint128.u128() * gas_price_numerator > amount.u128() * gas_price_denominator { + if limit_uint128 * gas_price_numerator > amount * gas_price_denominator { amount += Uint128::new(1); } diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index faf8800eaf..2d2902aea8 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -13,11 +13,11 @@ use cosmrs::rpc::endpoint::broadcast; use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl}; use cosmwasm_std::{Coin, Uint128}; use mixnet_contract::{ - Addr, ContractSettingsParams, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, - IdentityKey, LayerDistribution, MixNode, MixOwnershipResponse, MixnetContractVersion, - MixnodeRewardingStatusResponse, PagedAllDelegationsResponse, PagedGatewayResponse, - PagedMixDelegationsResponse, PagedMixnodeResponse, PagedReverseMixDelegationsResponse, - QueryMsg, RawDelegationData, RewardingIntervalResponse, + ContractSettingsParams, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey, + LayerDistribution, MixNode, MixOwnershipResponse, MixnetContractVersion, + MixnodeRewardingStatusResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, + PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse, QueryMsg, + RewardingIntervalResponse, }; use serde::Serialize; use std::collections::HashMap; @@ -313,7 +313,7 @@ impl NymdClient { C: CosmWasmClient + Sync, { let request = QueryMsg::OwnsMixnode { - address: Addr::unchecked(address.as_ref()), + address: address.to_string(), }; let response: MixOwnershipResponse = self .client @@ -328,7 +328,7 @@ impl NymdClient { C: CosmWasmClient + Sync, { let request = QueryMsg::OwnsGateway { - address: Addr::unchecked(address.as_ref()), + address: address.to_string(), }; let response: GatewayOwnershipResponse = self .client @@ -375,14 +375,13 @@ impl NymdClient { pub async fn get_mix_delegations_paged( &self, mix_identity: IdentityKey, - // I really hate mixing cosmwasm and cosmos-sdk types here... - start_after: Option, + start_after: Option, page_limit: Option, ) -> Result where C: CosmWasmClient + Sync, { - let request = QueryMsg::GetMixDelegations { + let request = QueryMsg::GetMixnodeDelegations { mix_identity: mix_identity.to_owned(), start_after, limit: page_limit, @@ -393,16 +392,15 @@ impl NymdClient { } /// Gets list of all mixnode delegations on particular page. - pub async fn get_all_mix_delegations_paged( + pub async fn get_all_network_delegations_paged( &self, - // I really hate mixing cosmwasm and cosmos-sdk types here... - start_after: Option>, + start_after: Option<(IdentityKey, String)>, page_limit: Option, - ) -> Result, NymdError> + ) -> Result where C: CosmWasmClient + Sync, { - let request = QueryMsg::GetAllMixDelegations { + let request = QueryMsg::GetAllNetworkDelegations { start_after, limit: page_limit, }; @@ -412,17 +410,17 @@ impl NymdClient { } /// Gets list of all the mixnodes on which a particular address delegated. - pub async fn get_reverse_mix_delegations_paged( + pub async fn get_delegator_delegations_paged( &self, - delegation_owner: Addr, + delegator: String, start_after: Option, page_limit: Option, - ) -> Result + ) -> Result where C: CosmWasmClient + Sync, { - let request = QueryMsg::GetReverseMixDelegations { - delegation_owner, + let request = QueryMsg::GetDelegatorDelegations { + delegator, start_after, limit: page_limit, }; @@ -432,7 +430,7 @@ impl NymdClient { } /// Checks value of delegation of given client towards particular mixnode. - pub async fn get_mix_delegation( + pub async fn get_delegation_details( &self, mix_identity: IdentityKey, delegator: &AccountId, @@ -440,9 +438,9 @@ impl NymdClient { where C: CosmWasmClient + Sync, { - let request = QueryMsg::GetMixDelegation { + let request = QueryMsg::GetDelegationDetails { mix_identity, - address: Addr::unchecked(delegator.as_ref()), + delegator: delegator.to_string(), }; self.client .query_contract_smart(self.contract_address()?, &request) diff --git a/common/mixnet-contract/Cargo.toml b/common/mixnet-contract/Cargo.toml index adc5d7beab..b7007a2666 100644 --- a/common/mixnet-contract/Cargo.toml +++ b/common/mixnet-contract/Cargo.toml @@ -7,9 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version -cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch = "0.14.1-updatedk256" } -#cosmwasm-std = { version = "0.14.1" } +cosmwasm-std = "1.0.0-beta2" serde = { version = "1.0", features = ["derive"] } serde_repr = "0.1" diff --git a/common/mixnet-contract/src/delegation.rs b/common/mixnet-contract/src/delegation.rs index 87d6e63a51..dbdce208f2 100644 --- a/common/mixnet-contract/src/delegation.rs +++ b/common/mixnet-contract/src/delegation.rs @@ -7,51 +7,41 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt::Display; -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] -pub struct UnpackedDelegation { +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] +pub struct Delegation { pub owner: Addr, pub node_identity: IdentityKey, - pub delegation_data: T, -} - -impl UnpackedDelegation { - pub fn new(owner: Addr, node_identity: IdentityKey, delegation_data: T) -> Self { - UnpackedDelegation { - owner, - node_identity, - delegation_data, - } - } -} - -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] -pub struct RawDelegationData { - pub amount: Uint128, + pub amount: Coin, pub block_height: u64, -} - -impl RawDelegationData { - pub fn new(amount: Uint128, block_height: u64) -> Self { - RawDelegationData { - amount, - block_height, - } - } -} - -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] -pub struct Delegation { - owner: Addr, - amount: Coin, - block_height: u64, + pub proxy: Option, // proxy address used to delegate the funds on behalf of anouther address } impl Delegation { - pub fn new(owner: Addr, amount: Coin, block_height: u64) -> Self { + pub fn new( + owner: Addr, + node_identity: IdentityKey, + amount: Coin, + block_height: u64, + proxy: Option, + ) -> Self { Delegation { owner, + node_identity, amount, block_height, + proxy, + } + } + + // TODO: change that to use .joined_key() and return Vec + pub fn storage_key(&self) -> (IdentityKey, Addr) { + (self.node_identity(), self.owner()) + } + + pub fn increment_amount(&mut self, amount: Uint128, at_height: Option) { + self.amount.amount += amount; + if let Some(at_height) = at_height { + self.block_height = at_height; } } @@ -59,6 +49,10 @@ impl Delegation { &self.amount } + pub fn node_identity(&self) -> IdentityKey { + self.node_identity.clone() + } + pub fn owner(&self) -> Addr { self.owner.clone() } @@ -72,65 +66,56 @@ impl Display for Delegation { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, - "{} {} delegated by {} at block {}", - self.amount.amount, self.amount.denom, self.owner, self.block_height + "{} delegated towards {} by {} at block {}", + self.amount, self.node_identity, self.owner, self.block_height ) } } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct PagedMixDelegationsResponse { - pub node_identity: IdentityKey, pub delegations: Vec, - pub start_next_after: Option, + pub start_next_after: Option, } impl PagedMixDelegationsResponse { - pub fn new( - node_identity: IdentityKey, - delegations: Vec, - start_next_after: Option, - ) -> Self { + pub fn new(delegations: Vec, start_next_after: Option) -> Self { PagedMixDelegationsResponse { - node_identity, delegations, - start_next_after, + start_next_after: start_next_after.map(|s| s.to_string()), } } } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] -pub struct PagedReverseMixDelegationsResponse { - pub delegation_owner: Addr, - pub delegated_nodes: Vec, +pub struct PagedDelegatorDelegationsResponse { + pub delegations: Vec, pub start_next_after: Option, } -impl PagedReverseMixDelegationsResponse { - pub fn new( - delegation_owner: Addr, - delegated_nodes: Vec, - start_next_after: Option, - ) -> Self { - PagedReverseMixDelegationsResponse { - delegation_owner, - delegated_nodes, +impl PagedDelegatorDelegationsResponse { + pub fn new(delegations: Vec, start_next_after: Option) -> Self { + PagedDelegatorDelegationsResponse { + delegations, start_next_after, } } } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] -pub struct PagedAllDelegationsResponse { - pub delegations: Vec>, - pub start_next_after: Option>, +pub struct PagedAllDelegationsResponse { + pub delegations: Vec, + pub start_next_after: Option<(IdentityKey, String)>, } -impl PagedAllDelegationsResponse { - pub fn new(delegations: Vec>, start_next_after: Option>) -> Self { +impl PagedAllDelegationsResponse { + pub fn new( + delegations: Vec, + start_next_after: Option<(IdentityKey, Addr)>, + ) -> Self { PagedAllDelegationsResponse { delegations, - start_next_after, + start_next_after: start_next_after.map(|(id, addr)| (id, addr.to_string())), } } } diff --git a/common/mixnet-contract/src/lib.rs b/common/mixnet-contract/src/lib.rs index aad3cefdc2..813fa3b894 100644 --- a/common/mixnet-contract/src/lib.rs +++ b/common/mixnet-contract/src/lib.rs @@ -12,8 +12,8 @@ pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250; pub use cosmwasm_std::{Addr, Coin}; pub use delegation::{ - Delegation, PagedAllDelegationsResponse, PagedMixDelegationsResponse, - PagedReverseMixDelegationsResponse, RawDelegationData, UnpackedDelegation, + Delegation, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, + PagedMixDelegationsResponse, }; pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse}; pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse}; diff --git a/common/mixnet-contract/src/mixnode.rs b/common/mixnet-contract/src/mixnode.rs index f38ec10255..a237742069 100644 --- a/common/mixnet-contract/src/mixnode.rs +++ b/common/mixnet-contract/src/mixnode.rs @@ -72,11 +72,11 @@ impl NodeRewardParams { sybil_resistance_percent: u8, ) -> NodeRewardParams { NodeRewardParams { - period_reward_pool: Uint128(period_reward_pool), - k: Uint128(k), + period_reward_pool: Uint128::new(period_reward_pool), + k: Uint128::new(k), reward_blockstamp, - circulating_supply: Uint128(circulating_supply), - uptime: Uint128(uptime), + circulating_supply: Uint128::new(circulating_supply), + uptime: Uint128::new(uptime), sybil_resistance_percent, } } @@ -236,6 +236,7 @@ pub struct MixNodeBond { pub block_height: u64, pub mix_node: MixNode, pub profit_margin_percent: Option, + pub proxy: Option, } impl MixNodeBond { @@ -246,6 +247,7 @@ impl MixNodeBond { block_height: u64, mix_node: MixNode, profit_margin_percent: Option, + proxy: Option, ) -> Self { MixNodeBond { total_delegation: coin(0, &bond_amount.denom), @@ -255,6 +257,7 @@ impl MixNodeBond { block_height, mix_node, profit_margin_percent, + proxy, } } @@ -511,6 +514,7 @@ mod tests { block_height: 100, mix_node: mixnode_fixture(), profit_margin_percent: Some(10), + proxy: None, }; let mix2 = MixNodeBond { @@ -521,6 +525,7 @@ mod tests { block_height: 120, mix_node: mixnode_fixture(), profit_margin_percent: Some(10), + proxy: None, }; let mix3 = MixNodeBond { @@ -531,6 +536,7 @@ mod tests { block_height: 120, mix_node: mixnode_fixture(), profit_margin_percent: Some(10), + proxy: None, }; let mix4 = MixNodeBond { @@ -541,6 +547,7 @@ mod tests { block_height: 120, mix_node: mixnode_fixture(), profit_margin_percent: Some(10), + proxy: None, }; let mix5 = MixNodeBond { @@ -551,6 +558,7 @@ mod tests { block_height: 120, mix_node: mixnode_fixture(), profit_margin_percent: Some(10), + proxy: None, }; // summary: diff --git a/common/mixnet-contract/src/msg.rs b/common/mixnet-contract/src/msg.rs index 4dfe6603ce..0d2c451673 100644 --- a/common/mixnet-contract/src/msg.rs +++ b/common/mixnet-contract/src/msg.rs @@ -4,7 +4,6 @@ use crate::mixnode::NodeRewardParams; use crate::ContractSettingsParams; use crate::{Gateway, IdentityKey, MixNode}; -use cosmwasm_std::Addr; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -50,12 +49,26 @@ pub enum ExecuteMsg { // nonce of the current rewarding interval rewarding_interval_nonce: u32, }, - RewardNextMixDelegators { mix_identity: IdentityKey, // nonce of the current rewarding interval rewarding_interval_nonce: u32, }, + DelegateToMixnodeOnBehalf { + mix_identity: IdentityKey, + delegate: String, + }, + UndelegateFromMixnodeOnBehalf { + mix_identity: IdentityKey, + delegate: String, + }, + BondMixnodeOnBehalf { + mix_node: MixNode, + owner: String, + }, + UnbondMixnodeOnBehalf { + owner: String, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -71,30 +84,39 @@ pub enum QueryMsg { limit: Option, }, OwnsMixnode { - address: Addr, + address: String, }, OwnsGateway { - address: Addr, + address: String, }, StateParams {}, CurrentRewardingInterval {}, - GetMixDelegations { + // gets all [paged] delegations in the entire network + // TODO: do we even want that? + GetAllNetworkDelegations { + start_after: Option<(IdentityKey, String)>, + limit: Option, + }, + // gets all [paged] delegations associated with particular mixnode + GetMixnodeDelegations { mix_identity: IdentityKey, - start_after: Option, + // since `start_after` is user-provided input, we can't use `Addr` as we + // can't guarantee it's validated. + start_after: Option, limit: Option, }, - GetAllMixDelegations { - start_after: Option>, - limit: Option, - }, - GetReverseMixDelegations { - delegation_owner: Addr, + // gets all [paged] delegations associated with particular delegator + GetDelegatorDelegations { + // since `delegator` is user-provided input, we can't use `Addr` as we + // can't guarantee it's validated. + delegator: String, start_after: Option, limit: Option, }, - GetMixDelegation { + // gets delegation associated with particular mixnode, delegator pair + GetDelegationDetails { mix_identity: IdentityKey, - address: Addr, + delegator: String, }, LayerDistribution {}, GetRewardPool {}, diff --git a/common/mixnet-contract/src/types.rs b/common/mixnet-contract/src/types.rs index dee5ff374e..c0c41340d4 100644 --- a/common/mixnet-contract/src/types.rs +++ b/common/mixnet-contract/src/types.rs @@ -3,7 +3,7 @@ use crate::mixnode::DelegatorRewardParams; use crate::Layer; -use cosmwasm_std::Uint128; +use cosmwasm_std::{Addr, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; @@ -81,7 +81,7 @@ 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, - pub next_start: String, + pub next_start: Addr, pub rewarding_params: DelegatorRewardParams, } diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock new file mode 100644 index 0000000000..5c27990a96 --- /dev/null +++ b/contracts/Cargo.lock @@ -0,0 +1,1219 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a03e93e97a28fbc9f42fbc5ba0886a3c67eb637b476dbee711f80a6ffe8223d" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "az" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "bytemuck" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time 0.1.43", + "winapi", +] + +[[package]] +name = "config" +version = "0.1.0" +dependencies = [ + "handlebars", + "humantime-serde", + "network-defaults", + "serde", + "toml", + "url", +] + +[[package]] +name = "const-oid" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" + +[[package]] +name = "cosmos_contract" +version = "0.1.0" +dependencies = [ + "config", + "cosmwasm-std", + "cosmwasm-storage", + "erc20-bridge-contract", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cosmwasm-crypto" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9" +dependencies = [ + "digest 0.9.0", + "ed25519-zebra", + "k256", + "rand_core 0.5.1", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2" +dependencies = [ + "syn", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe52b19d45fe3f8359db6cc24df44dbe05e5ae32539afc0f5b7f790a21aa6fd0" +dependencies = [ + "schemars", + "serde_json", +] + +[[package]] +name = "cosmwasm-std" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6" +dependencies = [ + "base64", + "cosmwasm-crypto", + "cosmwasm-derive", + "schemars", + "serde", + "serde-json-wasm", + "thiserror", + "uint", +] + +[[package]] +name = "cosmwasm-storage" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3b4efe3b4f86df668520a02e9a29c23eea99b64dfcacb0e59b98346418af7f" +dependencies = [ + "cosmwasm-std", + "serde", +] + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" +dependencies = [ + "generic-array 0.14.4", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "cw-storage-plus" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b8b840947313c1a1cccf056836cd79a60b4526bdcd6582995be37dc97be4ae" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "der" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2" +dependencies = [ + "const-oid", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "dyn-clone" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" + +[[package]] +name = "ecdsa" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" +dependencies = [ + "der", + "elliptic-curve", + "hmac", + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409" +dependencies = [ + "curve25519-dalek", + "hex", + "rand_core 0.5.1", + "serde", + "sha2", + "thiserror", +] + +[[package]] +name = "elliptic-curve" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b" +dependencies = [ + "crypto-bigint", + "ff", + "generic-array 0.14.4", + "group", + "pkcs8", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-iterator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "erc20-bridge-contract" +version = "0.1.0" +dependencies = [ + "schemars", + "serde", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "ff" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "fixed" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d333a26ec13a023c6dff4b7584de4d323cfee2e508f5dd2bbee6669e4f7efdf" +dependencies = [ + "az", + "bytemuck", + "half", + "serde", + "typenum", +] + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "getset" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b328c01a4d71d2d8173daa93562a73ab0fe85616876f02500f53d82948c504" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "git2" +version = "0.13.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845e007a28f1fcac035715988a234e8ec5458fd825b20a20c7dec74237ef341f" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "group" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "handlebars" +version = "3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error", + "serde", + "serde_json", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "humantime-serde" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058" +dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "k256" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "libc" +version = "0.2.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" + +[[package]] +name = "libgit2-sys" +version = "0.12.25+1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68169ef08d6519b2fe133ecc637408d933c0174b23b80bb2f79828966fbaab" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libz-sys" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "mixnet-contract" +version = "0.1.0" +dependencies = [ + "az", + "cosmwasm-std", + "fixed", + "log", + "network-defaults", + "schemars", + "serde", + "serde_repr", + "thiserror", +] + +[[package]] +name = "mixnet-contracts" +version = "0.1.0" +dependencies = [ + "config", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus", + "fixed", + "mixnet-contract", + "schemars", + "serde", + "thiserror", + "vergen", + "vesting-contract", +] + +[[package]] +name = "network-defaults" +version = "0.1.0" +dependencies = [ + "hex-literal", + "serde", + "time 0.3.5", + "url", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] + +[[package]] +name = "pkcs8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.3", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "schemars" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271ac0c667b8229adf70f0f957697c96fafd7486ab7481e15dc5e45e3e6a4368" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ebda811090b257411540779860bc09bf321bc587f58d2c5864309d1566214e7" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "semver" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50eef3672ec8fa45f3457fd423ba131117786784a895548021976117c1ded449" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "signature" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.3", +] + +[[package]] +name = "spki" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" +dependencies = [ + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" +dependencies = [ + "libc", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "typenum" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "uint" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vergen" +version = "5.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d48696c0fbbdafd9553e14c4584b4b9583931e9474a3ae506f1872b890d0b47" +dependencies = [ + "anyhow", + "cfg-if", + "chrono", + "enum-iterator", + "getset", + "git2", + "rustc_version", + "rustversion", + "thiserror", +] + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "vesting-contract" +version = "0.1.0" +dependencies = [ + "config", + "cosmwasm-std", + "cw-storage-plus", + "mixnet-contract", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zeroize" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml new file mode 100644 index 0000000000..3d0043fb88 --- /dev/null +++ b/contracts/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] +members = ["erc20-bridge", "mixnet", "vesting"] + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true \ No newline at end of file diff --git a/contracts/erc20-bridge/Cargo.lock b/contracts/erc20-bridge/Cargo.lock index 9e51f6ed3e..adc969f5a1 100644 --- a/contracts/erc20-bridge/Cargo.lock +++ b/contracts/erc20-bridge/Cargo.lock @@ -89,8 +89,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9" dependencies = [ "digest 0.9.0", "ed25519-zebra", @@ -101,16 +102,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2" dependencies = [ "syn", ] [[package]] name = "cosmwasm-std" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6" dependencies = [ "base64", "cosmwasm-crypto", @@ -124,8 +127,9 @@ dependencies = [ [[package]] name = "cosmwasm-storage" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3b4efe3b4f86df668520a02e9a29c23eea99b64dfcacb0e59b98346418af7f" dependencies = [ "cosmwasm-std", "serde", diff --git a/contracts/erc20-bridge/Cargo.toml b/contracts/erc20-bridge/Cargo.toml index a487dbee61..00b3c94104 100644 --- a/contracts/erc20-bridge/Cargo.toml +++ b/contracts/erc20-bridge/Cargo.toml @@ -5,22 +5,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[workspace] # adding a blank workspace to keep it out of the global workspace. - [lib] crate-type = ["cdylib", "rlib"] -[profile.release] -opt-level = 3 -debug = false -rpath = false -lto = true -debug-assertions = false -codegen-units = 1 -panic = 'abort' -incremental = false -overflow-checks = true - [features] # for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] @@ -31,9 +18,8 @@ config = { path = "../../common/config"} [dependencies] erc20-bridge-contract = { path = "../../common/erc20-bridge-contract" } -# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version -cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] } -cosmwasm-storage = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] } +cosmwasm-std = "1.0.0-beta2" +cosmwasm-storage = "1.0.0-beta2" schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/contracts/erc20-bridge/src/lib.rs b/contracts/erc20-bridge/src/lib.rs index 02957f9e37..76659f108b 100644 --- a/contracts/erc20-bridge/src/lib.rs +++ b/contracts/erc20-bridge/src/lib.rs @@ -68,7 +68,7 @@ pub mod tests { #[test] fn initialize_contract() { - let mut deps = mock_dependencies(&[]); + let mut deps = mock_dependencies(); let env = mock_env(); let msg = InstantiateMsg {}; let info = mock_info("creator", &[]); diff --git a/contracts/erc20-bridge/src/support/tests.rs b/contracts/erc20-bridge/src/support/tests.rs index 7fb16b8e05..1c4dd5681c 100644 --- a/contracts/erc20-bridge/src/support/tests.rs +++ b/contracts/erc20-bridge/src/support/tests.rs @@ -11,7 +11,7 @@ pub mod helpers { use erc20_bridge_contract::payment::Payment; pub fn init_contract() -> OwnedDeps> { - let mut deps = mock_dependencies(&[]); + let mut deps = mock_dependencies(); let msg = InstantiateMsg {}; let env = mock_env(); let info = mock_info("creator", &[]); diff --git a/contracts/mixnet/Cargo.lock b/contracts/mixnet/Cargo.lock index cbacc26204..43cc7f0664 100644 --- a/contracts/mixnet/Cargo.lock +++ b/contracts/mixnet/Cargo.lock @@ -128,8 +128,9 @@ checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" [[package]] name = "cosmwasm-crypto" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9" dependencies = [ "digest 0.9.0", "ed25519-zebra", @@ -140,17 +141,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2" dependencies = [ "syn", ] [[package]] name = "cosmwasm-schema" -version = "0.14.1" +version = "1.0.0-beta2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04159eec9b583671db7923ff2b979736dfb8f0152347cab9fd02373c22e1a870" +checksum = "fe52b19d45fe3f8359db6cc24df44dbe05e5ae32539afc0f5b7f790a21aa6fd0" dependencies = [ "schemars", "serde_json", @@ -158,8 +160,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6" dependencies = [ "base64", "cosmwasm-crypto", @@ -173,8 +176,9 @@ dependencies = [ [[package]] name = "cosmwasm-storage" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3b4efe3b4f86df668520a02e9a29c23eea99b64dfcacb0e59b98346418af7f" dependencies = [ "cosmwasm-std", "serde", @@ -230,6 +234,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-storage-plus" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b8b840947313c1a1cccf056836cd79a60b4526bdcd6582995be37dc97be4ae" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "der" version = "0.4.4" @@ -611,6 +626,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", + "cw-storage-plus", "fixed", "mixnet-contract", "schemars", diff --git a/contracts/mixnet/Cargo.toml b/contracts/mixnet/Cargo.toml index 8b04e9d4f1..c48d44b6fd 100644 --- a/contracts/mixnet/Cargo.toml +++ b/contracts/mixnet/Cargo.toml @@ -12,22 +12,9 @@ exclude = [ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[workspace] # adding a blank workspace to keep it out of the global workspace. - [lib] crate-type = ["cdylib", "rlib"] -[profile.release] -opt-level = 3 -debug = false -rpath = false -lto = true -debug-assertions = false -codegen-units = 1 -panic = 'abort' -incremental = false -overflow-checks = true - [features] # for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] @@ -35,20 +22,18 @@ backtraces = ["cosmwasm-std/backtraces"] [dependencies] mixnet-contract = { path = "../../common/mixnet-contract" } config = { path = "../../common/config"} +vesting-contract = { path = "../vesting" } -# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version -cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] } -cosmwasm-storage = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] } - -#cosmwasm-std = { version = "0.14.1", features = ["iterator"] } -#cosmwasm-storage = { version = "0.14.1", features = ["iterator"] } +cosmwasm-std = "1.0.0-beta2" +cosmwasm-storage = "1.0.0-beta2" +cw-storage-plus = "0.10.3" schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } [dev-dependencies] -cosmwasm-schema = { version = "0.14.0" } +cosmwasm-schema = "1.0.0-beta2" fixed = "1.1" [build-dependencies] diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 8d4883252e..a5d87d43f9 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -1,6 +1,10 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::delegations::queries::query_all_network_delegations_paged; +use crate::delegations::queries::query_delegator_delegations_paged; +use crate::delegations::queries::query_mixnode_delegation; +use crate::delegations::queries::query_mixnode_delegations_paged; use crate::error::ContractError; use crate::gateways::queries::query_gateways_paged; use crate::gateways::queries::query_owns_gateway; @@ -11,14 +15,11 @@ use crate::mixnet_contract_settings::queries::{ }; use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::mixnodes::bonding_queries as mixnode_queries; -use crate::mixnodes::bonding_queries::query_mixnode_delegations_paged; use crate::mixnodes::bonding_queries::query_mixnodes_paged; -use crate::mixnodes::delegation_queries::query_all_mixnode_delegations_paged; -use crate::mixnodes::delegation_queries::query_mixnode_delegation; -use crate::mixnodes::delegation_queries::query_reverse_mixnode_delegations_paged; use crate::mixnodes::layer_queries::query_layer_distribution; use crate::rewards::queries::query_reward_pool; use crate::rewards::queries::{query_circulating_supply, query_rewarding_status}; +use crate::rewards::storage as rewards_storage; use config::defaults::REWARDING_VALIDATOR_ADDRESS; use cosmwasm_std::{ entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128, @@ -27,10 +28,10 @@ use mixnet_contract::{ContractSettingsParams, ExecuteMsg, InstantiateMsg, Migrat use std::u128; /// Constant specifying minimum of coin required to bond a gateway -pub const INITIAL_GATEWAY_BOND: Uint128 = Uint128(100_000_000); +pub const INITIAL_GATEWAY_BOND: Uint128 = Uint128::new(100_000_000); /// Constant specifying minimum of coin required to bond a mixnode -pub const INITIAL_MIXNODE_BOND: Uint128 = Uint128(100_000_000); +pub const INITIAL_MIXNODE_BOND: Uint128 = Uint128::new(100_000_000); pub const INITIAL_MIXNODE_REWARDED_SET_SIZE: u32 = 200; pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100; @@ -72,8 +73,10 @@ pub fn instantiate( ) -> Result { let state = default_initial_state(info.sender, env); - mixnet_params_storage::contract_settings(deps.storage).save(&state)?; - mixnet_params_storage::layer_distribution(deps.storage).save(&Default::default())?; + mixnet_params_storage::CONTRACT_SETTINGS.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))?; + Ok(Response::default()) } @@ -87,10 +90,10 @@ pub fn execute( ) -> Result { match msg { ExecuteMsg::BondMixnode { mix_node } => { - crate::mixnodes::bonding_transactions::try_add_mixnode(deps, env, info, mix_node) + crate::mixnodes::transactions::try_add_mixnode(deps, env, info, mix_node) } ExecuteMsg::UnbondMixnode {} => { - crate::mixnodes::bonding_transactions::try_remove_mixnode(deps, info) + crate::mixnodes::transactions::try_remove_mixnode(deps, info) } ExecuteMsg::BondGateway { gateway } => { crate::gateways::transactions::try_add_gateway(deps, env, info, gateway) @@ -116,15 +119,10 @@ pub fn execute( rewarding_interval_nonce, ), ExecuteMsg::DelegateToMixnode { mix_identity } => { - crate::mixnodes::delegation_transactions::try_delegate_to_mixnode( - deps, - env, - info, - mix_identity, - ) + crate::delegations::transactions::try_delegate_to_mixnode(deps, env, info, mix_identity) } ExecuteMsg::UndelegateFromMixnode { mix_identity } => { - crate::mixnodes::delegation_transactions::try_remove_delegation_from_mixnode( + crate::delegations::transactions::try_remove_delegation_from_mixnode( deps, info, mix_identity, @@ -154,6 +152,33 @@ pub fn execute( mix_identity, rewarding_interval_nonce, ), + 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, + info, + mix_identity, + delegate, + ), + ExecuteMsg::BondMixnodeOnBehalf { mix_node, owner } => { + crate::mixnodes::transactions::try_add_mixnode_on_behalf( + deps, env, info, mix_node, owner, + ) + } + ExecuteMsg::UnbondMixnodeOnBehalf { owner } => { + crate::mixnodes::transactions::try_remove_mixnode_on_behalf(deps, info, owner) + } } } @@ -171,10 +196,10 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result to_binary(&query_owns_gateway(deps, address)?), - QueryMsg::StateParams {} => to_binary(&query_contract_settings_params(deps)), - QueryMsg::CurrentRewardingInterval {} => to_binary(&query_rewarding_interval(deps)), - QueryMsg::LayerDistribution {} => to_binary(&query_layer_distribution(deps)), - QueryMsg::GetMixDelegations { + QueryMsg::StateParams {} => to_binary(&query_contract_settings_params(deps)?), + QueryMsg::CurrentRewardingInterval {} => to_binary(&query_rewarding_interval(deps)?), + QueryMsg::LayerDistribution {} => to_binary(&query_layer_distribution(deps)?), + QueryMsg::GetMixnodeDelegations { mix_identity, start_after, limit, @@ -184,25 +209,25 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result to_binary( - &query_all_mixnode_delegations_paged(deps, start_after, limit)?, + QueryMsg::GetAllNetworkDelegations { start_after, limit } => to_binary( + &query_all_network_delegations_paged(deps, start_after, limit)?, ), - QueryMsg::GetReverseMixDelegations { - delegation_owner, + QueryMsg::GetDelegatorDelegations { + delegator: delegation_owner, start_after, limit, - } => to_binary(&query_reverse_mixnode_delegations_paged( + } => to_binary(&query_delegator_delegations_paged( deps, delegation_owner, start_after, limit, )?), - QueryMsg::GetMixDelegation { + QueryMsg::GetDelegationDetails { mix_identity, - address, - } => to_binary(&query_mixnode_delegation(deps, mix_identity, address)?), - QueryMsg::GetRewardPool {} => to_binary(&query_reward_pool(deps)), - QueryMsg::GetCirculatingSupply {} => to_binary(&query_circulating_supply(deps)), + delegator, + } => to_binary(&query_mixnode_delegation(deps, mix_identity, delegator)?), + QueryMsg::GetRewardPool {} => to_binary(&query_reward_pool(deps)?), + QueryMsg::GetCirculatingSupply {} => to_binary(&query_circulating_supply(deps)?), QueryMsg::GetEpochRewardPercent {} => to_binary(&EPOCH_REWARD_PERCENT), QueryMsg::GetSybilResistancePercent {} => to_binary(&DEFAULT_SYBIL_RESISTANCE_PERCENT), QueryMsg::GetRewardingStatus { @@ -233,7 +258,7 @@ pub mod tests { #[test] fn initialize_contract() { - let mut deps = mock_dependencies(&[]); + let mut deps = mock_dependencies(); let env = mock_env(); let msg = InstantiateMsg {}; let info = mock_info("creator", &[]); diff --git a/contracts/mixnet/src/delegations/mod.rs b/contracts/mixnet/src/delegations/mod.rs new file mode 100644 index 0000000000..32fd774de7 --- /dev/null +++ b/contracts/mixnet/src/delegations/mod.rs @@ -0,0 +1,6 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) mod queries; +pub(crate) mod storage; +pub(crate) mod transactions; diff --git a/contracts/mixnet/src/mixnodes/delegation_queries.rs b/contracts/mixnet/src/delegations/queries.rs similarity index 64% rename from contracts/mixnet/src/mixnodes/delegation_queries.rs rename to contracts/mixnet/src/delegations/queries.rs index 78906e4b0a..27bc4b073e 100644 --- a/contracts/mixnet/src/mixnodes/delegation_queries.rs +++ b/contracts/mixnet/src/delegations/queries.rs @@ -1,62 +1,74 @@ -use super::delegation_helpers; +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use super::storage; use crate::error::ContractError; -use crate::query_support::calculate_start_value; -use config::defaults::DENOM; -use cosmwasm_std::coin; -use cosmwasm_std::Addr; use cosmwasm_std::Deps; use cosmwasm_std::Order; use cosmwasm_std::StdResult; -use mixnet_contract::Delegation; -use mixnet_contract::IdentityKey; +use cw_storage_plus::{Bound, PrimaryKey}; use mixnet_contract::PagedAllDelegationsResponse; -use mixnet_contract::PagedReverseMixDelegationsResponse; -use mixnet_contract::RawDelegationData; +use mixnet_contract::PagedDelegatorDelegationsResponse; +use mixnet_contract::PagedMixDelegationsResponse; +use mixnet_contract::{Delegation, IdentityKey}; -pub(crate) fn query_all_mixnode_delegations_paged( +pub(crate) fn query_all_network_delegations_paged( deps: Deps, - start_after: Option>, + start_after: Option<(IdentityKey, String)>, limit: Option, -) -> StdResult> { +) -> StdResult { let limit = limit .unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT) .min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize; - let bucket = storage::all_mix_delegations_read::(deps.storage); - let start = start_after.map(|mut v| { - v.push(0); - v - }); - delegation_helpers::get_all_delegations_paged::(&bucket, &start, limit) + let start = start_after + .map(|start| start.joined_key()) + .map(Bound::exclusive); + + let delegations = storage::delegations() + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|record| record.map(|r| r.1)) + .collect::>>()?; + + let start_next_after = delegations + .last() + .map(|delegation| delegation.storage_key()); + + Ok(PagedAllDelegationsResponse::new( + delegations, + start_next_after, + )) } -pub(crate) fn query_reverse_mixnode_delegations_paged( +pub(crate) fn query_delegator_delegations_paged( deps: Deps, - delegation_owner: Addr, + delegation_owner: String, start_after: Option, limit: Option, -) -> StdResult { +) -> StdResult { + let validated_owner = deps.api.addr_validate(&delegation_owner)?; + let limit = limit .unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT) .min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize; - let start = calculate_start_value(start_after); + let start = start_after + .map(|mix_identity| Bound::Exclusive((mix_identity, validated_owner.clone()).joined_key())); - let delegations = storage::reverse_mix_delegations_read(deps.storage, &delegation_owner) - .range(start.as_deref(), None, Order::Ascending) + let delegations = storage::delegations() + .idx + .owner + .prefix(validated_owner) + .range(deps.storage, start, None, Order::Ascending) .take(limit) - .map(|res| { - res.map(|entry| { - String::from_utf8(entry.0) - .expect("Non-UTF8 address used as key in bucket. The storage is corrupted!") - }) - }) - .collect::>>()?; + .map(|record| record.map(|r| r.1)) + .collect::>>()?; - let start_next_after = delegations.last().cloned(); + let start_next_after = delegations + .last() + .map(|delegation| delegation.node_identity()); - Ok(PagedReverseMixDelegationsResponse::new( - delegation_owner, + Ok(PagedDelegatorDelegationsResponse::new( delegations, start_next_after, )) @@ -66,44 +78,68 @@ pub(crate) fn query_reverse_mixnode_delegations_paged( pub(crate) fn query_mixnode_delegation( deps: Deps, mix_identity: IdentityKey, - address: Addr, + delegator: String, ) -> Result { - match storage::mix_delegations_read(deps.storage, &mix_identity).may_load(address.as_bytes())? { - Some(delegation_value) => Ok(Delegation::new( - address, - coin(delegation_value.amount.u128(), DENOM), - delegation_value.block_height, - )), - None => Err(ContractError::NoMixnodeDelegationFound { + let validated_delegator = deps.api.addr_validate(&delegator)?; + let storage_key = (mix_identity.clone(), validated_delegator.clone()).joined_key(); + + storage::delegations() + .may_load(deps.storage, storage_key)? + .ok_or(ContractError::NoMixnodeDelegationFound { identity: mix_identity, - address, - }), - } + address: validated_delegator, + }) +} + +pub(crate) fn query_mixnode_delegations_paged( + deps: Deps, + mix_identity: IdentityKey, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit + .unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT) + .min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize; + + let start = start_after + .map(|addr| deps.api.addr_validate(&addr)) + .transpose()? + .map(|addr| Bound::Exclusive((mix_identity.clone(), addr).joined_key())); + + let delegations = storage::delegations() + .idx + .mixnode + .prefix(mix_identity) + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|record| record.map(|r| r.1)) + .collect::>>()?; + + let start_next_after = delegations.last().map(|delegation| delegation.owner()); + + Ok(PagedMixDelegationsResponse::new( + delegations, + start_next_after, + )) } #[cfg(test)] pub(crate) mod tests { use super::*; use crate::support::tests::test_helpers; - use cosmwasm_std::{Addr, Storage}; - use storage::mix_delegations; + use config::defaults::DENOM; + use cosmwasm_std::{coin, Addr, Storage}; pub fn store_n_mix_delegations(n: u32, storage: &mut dyn Storage, node_identity: &str) { for i in 0..n { let address = format!("address{}", i); - mix_delegations(storage, node_identity) - .save( - address.as_bytes(), - &test_helpers::raw_delegation_fixture(42), - ) - .unwrap(); + test_helpers::save_dummy_delegation(storage, node_identity, address); } } #[cfg(test)] mod querying_for_mixnode_delegations_paged { use super::*; - use crate::mixnodes::bonding_queries::query_mixnode_delegations_paged; use mixnet_contract::IdentityKey; #[test] @@ -172,9 +208,7 @@ pub(crate) mod tests { let mut deps = test_helpers::init_contract(); let node_identity: IdentityKey = "foo".into(); - mix_delegations(&mut deps.storage, &node_identity) - .save("1".as_bytes(), &test_helpers::raw_delegation_fixture(42)) - .unwrap(); + test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "100"); let per_page = 2; let page1 = query_mixnode_delegations_paged( @@ -189,9 +223,7 @@ pub(crate) mod tests { assert_eq!(1, page1.delegations.len()); // save another - mix_delegations(&mut deps.storage, &node_identity) - .save("2".as_bytes(), &test_helpers::raw_delegation_fixture(42)) - .unwrap(); + test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "200"); // page1 should have 2 results on it let page1 = query_mixnode_delegations_paged( @@ -203,9 +235,7 @@ pub(crate) mod tests { .unwrap(); assert_eq!(2, page1.delegations.len()); - mix_delegations(&mut deps.storage, &node_identity) - .save("3".as_bytes(), &test_helpers::raw_delegation_fixture(42)) - .unwrap(); + test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "300"); // page1 still has 2 results let page1 = query_mixnode_delegations_paged( @@ -216,9 +246,10 @@ pub(crate) mod tests { ) .unwrap(); assert_eq!(2, page1.delegations.len()); + assert_eq!("200".to_string(), page1.start_next_after.unwrap()); // retrieving the next page should start after the last key on this page - let start_after = Addr::unchecked("2"); + let start_after = "200".to_string(); let page2 = query_mixnode_delegations_paged( deps.as_ref(), node_identity.clone(), @@ -230,11 +261,9 @@ pub(crate) mod tests { assert_eq!(1, page2.delegations.len()); // save another one - mix_delegations(&mut deps.storage, &node_identity) - .save("4".as_bytes(), &test_helpers::raw_delegation_fixture(42)) - .unwrap(); + test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "400"); - let start_after = Addr::unchecked("2"); + let start_after = "200".to_string(); let page2 = query_mixnode_delegations_paged( deps.as_ref(), node_identity, @@ -262,7 +291,7 @@ pub(crate) mod tests { store_n_mix_delegations(100, &mut deps.storage, &node_identity); let page1 = - query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(limit)) + query_all_network_delegations_paged(deps.as_ref(), None, Option::from(limit)) .unwrap(); assert_eq!(limit, page1.delegations.len() as u32); } @@ -278,7 +307,7 @@ pub(crate) mod tests { ); // query without explicitly setting a limit - let page1 = query_all_mixnode_delegations_paged(deps.as_ref(), None, None).unwrap(); + let page1 = query_all_network_delegations_paged(deps.as_ref(), None, None).unwrap(); assert_eq!( storage::DELEGATION_PAGE_DEFAULT_LIMIT, page1.delegations.len() as u32 @@ -298,7 +327,7 @@ pub(crate) mod tests { // query with a crazily high limit in an attempt to use too many resources let crazy_limit = 1000 * storage::DELEGATION_PAGE_DEFAULT_LIMIT; let page1 = - query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(crazy_limit)) + query_all_network_delegations_paged(deps.as_ref(), None, Option::from(crazy_limit)) .unwrap(); // we default to a decent sized upper bound instead @@ -311,43 +340,36 @@ pub(crate) mod tests { let mut deps = test_helpers::init_contract(); let node_identity: IdentityKey = "foo".into(); - mix_delegations(&mut deps.storage, &node_identity) - .save("1".as_bytes(), &test_helpers::raw_delegation_fixture(42)) - .unwrap(); + test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "100"); let per_page = 2; let page1 = - query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(per_page)) + query_all_network_delegations_paged(deps.as_ref(), None, Option::from(per_page)) .unwrap(); // page should have 1 result on it assert_eq!(1, page1.delegations.len()); // save another - mix_delegations(&mut deps.storage, &node_identity) - .save("2".as_bytes(), &test_helpers::raw_delegation_fixture(42)) - .unwrap(); + test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "200"); // page1 should have 2 results on it let page1 = - query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(per_page)) + query_all_network_delegations_paged(deps.as_ref(), None, Option::from(per_page)) .unwrap(); assert_eq!(2, page1.delegations.len()); - mix_delegations(&mut deps.storage, &node_identity) - .save("3".as_bytes(), &test_helpers::raw_delegation_fixture(42)) - .unwrap(); + test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "300"); // page1 still has 2 results let page1 = - query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(per_page)) + query_all_network_delegations_paged(deps.as_ref(), None, Option::from(per_page)) .unwrap(); assert_eq!(2, page1.delegations.len()); // retrieving the next page should start after the last key on this page - let start_after = - test_helpers::identity_and_owner_to_bytes(&node_identity, &Addr::unchecked("2")); - let page2 = query_all_mixnode_delegations_paged( + let start_after = page1.start_next_after.unwrap(); + let page2 = query_all_network_delegations_paged( deps.as_ref(), Option::from(start_after.clone()), Option::from(per_page), @@ -357,11 +379,9 @@ pub(crate) mod tests { assert_eq!(1, page2.delegations.len()); // save another one - mix_delegations(&mut deps.storage, &node_identity) - .save("4".as_bytes(), &test_helpers::raw_delegation_fixture(42)) - .unwrap(); + test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "400"); - let page2 = query_all_mixnode_delegations_paged( + let page2 = query_all_network_delegations_paged( deps.as_ref(), Option::from(start_after), Option::from(per_page), @@ -379,20 +399,25 @@ pub(crate) mod tests { let node_identity: IdentityKey = "foo".into(); let delegation_owner = Addr::unchecked("bar"); - mix_delegations(&mut deps.storage, &node_identity) + let delegation = Delegation::new( + delegation_owner.clone(), + node_identity.clone(), + coin(1234, DENOM), + 1234, + None, + ); + + storage::delegations() .save( - delegation_owner.as_bytes(), - &RawDelegationData::new(42u128.into(), 12_345), + deps.as_mut().storage, + delegation.storage_key().joined_key(), + &delegation, ) .unwrap(); assert_eq!( - Ok(Delegation::new( - delegation_owner.clone(), - coin(42, DENOM), - 12_345 - )), - query_mixnode_delegation(deps.as_ref(), node_identity, delegation_owner) + Ok(delegation), + query_mixnode_delegation(deps.as_ref(), node_identity, delegation_owner.to_string()) ) } @@ -413,15 +438,24 @@ pub(crate) mod tests { query_mixnode_delegation( deps.as_ref(), node_identity1.clone(), - delegation_owner1.clone() + delegation_owner1.to_string() ) ); // add delegation from a different address - mix_delegations(&mut deps.storage, &node_identity1) + let delegation = Delegation::new( + delegation_owner2.clone(), + node_identity1.clone(), + coin(1234, DENOM), + 1234, + None, + ); + + storage::delegations() .save( - delegation_owner2.as_bytes(), - &test_helpers::raw_delegation_fixture(42), + deps.as_mut().storage, + delegation.storage_key().joined_key(), + &delegation, ) .unwrap(); @@ -433,39 +467,48 @@ pub(crate) mod tests { query_mixnode_delegation( deps.as_ref(), node_identity1.clone(), - delegation_owner1.clone() + delegation_owner1.to_string() ) ); // add delegation for a different node - mix_delegations(&mut deps.storage, &node_identity2) + let delegation = Delegation::new( + delegation_owner1.clone(), + node_identity2.clone(), + coin(1234, DENOM), + 1234, + None, + ); + + storage::delegations() .save( - delegation_owner1.as_bytes(), - &test_helpers::raw_delegation_fixture(42), + deps.as_mut().storage, + delegation.storage_key().joined_key(), + &delegation, ) .unwrap(); assert_eq!( Err(ContractError::NoMixnodeDelegationFound { identity: node_identity1.clone(), - address: delegation_owner1.clone() + address: Addr::unchecked(delegation_owner1.clone()) }), - query_mixnode_delegation(deps.as_ref(), node_identity1.clone(), delegation_owner1) + query_mixnode_delegation( + deps.as_ref(), + node_identity1.clone(), + delegation_owner1.to_string() + ) ) } #[cfg(test)] mod querying_for_reverse_mixnode_delegations_paged { use super::*; - use crate::mixnodes::delegation_queries::query_reverse_mixnode_delegations_paged; - use storage::reverse_mix_delegations; - fn store_n_reverse_delegations(n: u32, storage: &mut dyn Storage, delegation_owner: &Addr) { + fn store_n_reverse_delegations(n: u32, storage: &mut dyn Storage, delegation_owner: &str) { for i in 0..n { let node_identity = format!("node{}", i); - reverse_mix_delegations(storage, delegation_owner) - .save(node_identity.as_bytes(), &()) - .unwrap(); + test_helpers::save_dummy_delegation(storage, node_identity, delegation_owner); } } @@ -473,23 +516,23 @@ pub(crate) mod tests { fn retrieval_obeys_limits() { let mut deps = test_helpers::init_contract(); let limit = 2; - let delegation_owner = Addr::unchecked("foo"); + let delegation_owner = "foo".to_string(); store_n_reverse_delegations(100, &mut deps.storage, &delegation_owner); - let page1 = query_reverse_mixnode_delegations_paged( + let page1 = query_delegator_delegations_paged( deps.as_ref(), delegation_owner, None, Option::from(limit), ) .unwrap(); - assert_eq!(limit, page1.delegated_nodes.len() as u32); + assert_eq!(limit, page1.delegations.len() as u32); } #[test] fn retrieval_has_default_limit() { let mut deps = test_helpers::init_contract(); - let delegation_owner = Addr::unchecked("foo"); + let delegation_owner = "foo".to_string(); store_n_reverse_delegations( storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10, &mut deps.storage, @@ -497,23 +540,19 @@ pub(crate) mod tests { ); // query without explicitly setting a limit - let page1 = query_reverse_mixnode_delegations_paged( - deps.as_ref(), - delegation_owner, - None, - None, - ) - .unwrap(); + let page1 = + query_delegator_delegations_paged(deps.as_ref(), delegation_owner, None, None) + .unwrap(); assert_eq!( storage::DELEGATION_PAGE_DEFAULT_LIMIT, - page1.delegated_nodes.len() as u32 + page1.delegations.len() as u32 ); } #[test] fn retrieval_has_max_limit() { let mut deps = test_helpers::init_contract(); - let delegation_owner = Addr::unchecked("foo"); + let delegation_owner = "foo".to_string(); store_n_reverse_delegations( storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10, &mut deps.storage, @@ -522,7 +561,7 @@ pub(crate) mod tests { // query with a crazy high limit in an attempt to use too many resources let crazy_limit = 1000 * storage::DELEGATION_PAGE_DEFAULT_LIMIT; - let page1 = query_reverse_mixnode_delegations_paged( + let page1 = query_delegator_delegations_paged( deps.as_ref(), delegation_owner, None, @@ -532,20 +571,18 @@ pub(crate) mod tests { // we default to a decent sized upper bound instead let expected_limit = storage::DELEGATION_PAGE_MAX_LIMIT; - assert_eq!(expected_limit, page1.delegated_nodes.len() as u32); + assert_eq!(expected_limit, page1.delegations.len() as u32); } #[test] fn pagination_works() { let mut deps = test_helpers::init_contract(); - let delegation_owner = Addr::unchecked("bar"); + let delegation_owner = "bar".to_string(); - reverse_mix_delegations(&mut deps.storage, &delegation_owner) - .save("1".as_bytes(), &()) - .unwrap(); + test_helpers::save_dummy_delegation(&mut deps.storage, "100", &delegation_owner); let per_page = 2; - let page1 = query_reverse_mixnode_delegations_paged( + let page1 = query_delegator_delegations_paged( deps.as_ref(), delegation_owner.clone(), None, @@ -554,40 +591,36 @@ pub(crate) mod tests { .unwrap(); // page should have 1 result on it - assert_eq!(1, page1.delegated_nodes.len()); + assert_eq!(1, page1.delegations.len()); // save another - reverse_mix_delegations(&mut deps.storage, &delegation_owner) - .save("2".as_bytes(), &()) - .unwrap(); + test_helpers::save_dummy_delegation(&mut deps.storage, "200", &delegation_owner); // page1 should have 2 results on it - let page1 = query_reverse_mixnode_delegations_paged( + let page1 = query_delegator_delegations_paged( deps.as_ref(), delegation_owner.clone(), None, Option::from(per_page), ) .unwrap(); - assert_eq!(2, page1.delegated_nodes.len()); + assert_eq!(2, page1.delegations.len()); - reverse_mix_delegations(&mut deps.storage, &delegation_owner) - .save("3".as_bytes(), &()) - .unwrap(); + test_helpers::save_dummy_delegation(&mut deps.storage, "300", &delegation_owner); // page1 still has 2 results - let page1 = query_reverse_mixnode_delegations_paged( + let page1 = query_delegator_delegations_paged( deps.as_ref(), delegation_owner.clone(), None, Option::from(per_page), ) .unwrap(); - assert_eq!(2, page1.delegated_nodes.len()); + assert_eq!(2, page1.delegations.len()); // retrieving the next page should start after the last key on this page - let start_after: IdentityKey = String::from("2"); - let page2 = query_reverse_mixnode_delegations_paged( + let start_after: IdentityKey = page1.start_next_after.unwrap(); + let page2 = query_delegator_delegations_paged( deps.as_ref(), delegation_owner.clone(), Option::from(start_after), @@ -595,15 +628,10 @@ pub(crate) mod tests { ) .unwrap(); - assert_eq!(1, page2.delegated_nodes.len()); - - // save another one - reverse_mix_delegations(&mut deps.storage, &delegation_owner) - .save("4".as_bytes(), &()) - .unwrap(); + assert_eq!(1, page2.delegations.len()); let start_after = String::from("2"); - let page2 = query_reverse_mixnode_delegations_paged( + let page2 = query_delegator_delegations_paged( deps.as_ref(), delegation_owner, Option::from(start_after), @@ -612,7 +640,7 @@ pub(crate) mod tests { .unwrap(); // now we have 2 pages, with 2 results on the second page - assert_eq!(2, page2.delegated_nodes.len()); + assert_eq!(2, page2.delegations.len()); } } } diff --git a/contracts/mixnet/src/delegations/storage.rs b/contracts/mixnet/src/delegations/storage.rs new file mode 100644 index 0000000000..46fd0cc7fd --- /dev/null +++ b/contracts/mixnet/src/delegations/storage.rs @@ -0,0 +1,180 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex}; +use mixnet_contract::{Addr, Delegation, IdentityKey}; + +// storage prefixes +const DELEGATION_PK_NAMESPACE: &str = "dl"; +const DELEGATION_OWNER_IDX_NAMESPACE: &str = "dlo"; +const DELEGATION_MIXNODE_IDX_NAMESPACE: &str = "dlm"; + +// paged retrieval limits for all queries and transactions +pub(crate) const DELEGATION_PAGE_MAX_LIMIT: u32 = 500; +pub(crate) const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 250; + +// It's a composite key on node's identity and delegator address +type PrimaryKey = Vec; + +pub(crate) struct DelegationIndex<'a> { + pub(crate) owner: MultiIndex<'a, (Addr, PrimaryKey), Delegation>, + + pub(crate) mixnode: MultiIndex<'a, (IdentityKey, PrimaryKey), Delegation>, +} + +impl<'a> IndexList for DelegationIndex<'a> { + fn get_indexes(&'_ self) -> Box> + '_> { + let v: Vec<&dyn Index> = vec![&self.owner, &self.mixnode]; + Box::new(v.into_iter()) + } +} + +// I was really going back and forth about the data stored on the disk vs primary key duplication. +// It was basically between convenience and bloat, but in the end I decided the convenience wins. +// +// Basically I had 2 approaches. a) store delegator address and mixnode identity only as primary key of delegation or +// b) store it both as primary key AND inside delegation data. +// For the longest time I was in favour of a), since that removed any data duplication. However..., +// that also required that during index creation I recovered delegator address and mixnode identity +// from the Vec. That doesn't sound that terrible. However, even though I'm 99.99% certain that +// conversion would be impossible to fail, I'd still have to call an `unwrap` here due to required +// type signature and I didn't feel super comfortable doing that in our smart contract... +// So to get rid of this uncertainty I went with the b) approach. Even though each stored delegation +// takes over ~250B (since the key has to be duplicated), in the grand blockchain scheme of things +// it's not that terrible. Say we had 100_000_000 delegations -> that's still only 25GB of data +// and as a nice by-product it cleans up code a little bit by only having a single Delegation type. +pub(crate) fn delegations<'a>() -> IndexedMap<'a, PrimaryKey, Delegation, DelegationIndex<'a>> { + let indexes = DelegationIndex { + owner: MultiIndex::new( + |d, pk| (d.owner.clone(), pk), + DELEGATION_PK_NAMESPACE, + DELEGATION_OWNER_IDX_NAMESPACE, + ), + mixnode: MultiIndex::new( + |d, pk| (d.node_identity.clone(), pk), + DELEGATION_PK_NAMESPACE, + DELEGATION_MIXNODE_IDX_NAMESPACE, + ), + }; + + IndexedMap::new(DELEGATION_PK_NAMESPACE, indexes) +} + +#[cfg(test)] +mod tests { + use crate::delegations::storage; + use cosmwasm_std::Addr; + use mixnet_contract::IdentityKey; + + #[cfg(test)] + mod reverse_mix_delegations { + use super::*; + use crate::support::tests::test_helpers; + use config::defaults::DENOM; + use cosmwasm_std::testing::mock_env; + use cosmwasm_std::{coin, Order}; + use cw_storage_plus::PrimaryKey; + use mixnet_contract::Delegation; + + #[test] + fn reverse_mix_delegation_exists() { + let mut deps = test_helpers::init_contract(); + let node_identity: IdentityKey = "foo".into(); + let delegation_owner = Addr::unchecked("bar"); + let delegation = coin(12345, DENOM); + + let dummy_data = Delegation::new( + delegation_owner.clone(), + node_identity.clone(), + delegation, + mock_env().block.height, + None, + ); + + storage::delegations() + .save( + &mut deps.storage, + (node_identity.clone(), delegation_owner.clone()).joined_key(), + &dummy_data, + ) + .unwrap(); + + let read = storage::delegations() + .idx + .owner + .prefix(delegation_owner) + .range(&deps.storage, None, None, Order::Ascending) + .map(|record| record.unwrap().1) + .collect::>(); + + assert_eq!(1, read.len()); + assert_eq!(dummy_data, read[0]); + } + + #[test] + fn reverse_mix_delegation_returns_none_if_delegation_doesnt_exist() { + let mut deps = test_helpers::init_contract(); + + let node_identity1: IdentityKey = "foo1".into(); + let node_identity2: IdentityKey = "foo2".into(); + let delegation_owner1 = Addr::unchecked("bar"); + let delegation_owner2 = Addr::unchecked("bar2"); + let delegation = coin(12345, DENOM); + + assert!(test_helpers::read_delegation( + deps.as_ref().storage, + &node_identity1, + &delegation_owner1 + ) + .is_none()); + + // add delegation for a different node + let dummy_data = Delegation::new( + delegation_owner1.clone(), + node_identity2.clone(), + delegation.clone(), + mock_env().block.height, + None, + ); + storage::delegations() + .save( + &mut deps.storage, + (node_identity1.clone(), delegation_owner1.clone()).joined_key(), + &dummy_data, + ) + .unwrap(); + + storage::delegations() + .idx + .owner + .prefix(delegation_owner1.clone()) + .range(&deps.storage, None, None, Order::Ascending) + .map(|record| record.unwrap().1) + .for_each(|delegation| assert_ne!(delegation.node_identity, node_identity1)); + + // add delegation from a different owner + let dummy_data = Delegation::new( + delegation_owner2.clone(), + node_identity1.clone(), + delegation.clone(), + mock_env().block.height, + None, + ); + storage::delegations() + .save( + &mut deps.storage, + (node_identity1.clone(), delegation_owner2.clone()).joined_key(), + &dummy_data, + ) + .unwrap(); + + storage::delegations() + .idx + .owner + .prefix(delegation_owner1.clone()) + .range(&deps.storage, None, None, Order::Ascending) + .map(|record| record.unwrap().1) + .for_each(|delegation| assert_ne!(delegation.node_identity, node_identity1)); + } + } +} diff --git a/contracts/mixnet/src/mixnodes/delegation_transactions.rs b/contracts/mixnet/src/delegations/transactions.rs similarity index 64% rename from contracts/mixnet/src/mixnodes/delegation_transactions.rs rename to contracts/mixnet/src/delegations/transactions.rs index 8dff9636ac..c5a8300048 100644 --- a/contracts/mixnet/src/mixnodes/delegation_transactions.rs +++ b/contracts/mixnet/src/delegations/transactions.rs @@ -1,12 +1,19 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use cosmwasm_std::{ + coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128, +}; +use cw_storage_plus::PrimaryKey; + +use crate::error::ContractError; +use crate::mixnodes::storage as mixnodes_storage; +use crate::support::helpers::generate_storage_key; +use config::defaults::DENOM; +use mixnet_contract::Delegation; +use mixnet_contract::IdentityKey; +use vesting_contract::messages::ExecuteMsg as VestingContractExecuteMsg; use super::storage; -use crate::error::ContractError; -use config::defaults::DENOM; -use cosmwasm_std::{coins, BankMsg, Coin, DepsMut, Env, MessageInfo, Response}; -use mixnet_contract::IdentityKey; -use mixnet_contract::RawDelegationData; fn validate_delegation_stake(delegation: &[Coin]) -> Result<(), ContractError> { // check if anything was put as delegation @@ -40,47 +47,89 @@ pub(crate) fn try_delegate_to_mixnode( // check if the delegation contains any funds of the appropriate denomination validate_delegation_stake(&info.funds)?; + let amount = info.funds[0].amount; + + _try_delegate_to_mixnode(deps, env, mix_identity, info.sender.as_str(), amount, None) +} + +pub(crate) fn try_delegate_to_mixnode_on_behalf( + deps: DepsMut, + env: Env, + info: MessageInfo, + mix_identity: IdentityKey, + delegate: String, +) -> Result { + // check if the delegation contains any funds of the appropriate denomination + validate_delegation_stake(&info.funds)?; + let amount = info.funds[0].amount; + + _try_delegate_to_mixnode( + deps, + env, + mix_identity, + &delegate, + amount, + Some(info.sender), + ) +} + +pub(crate) fn _try_delegate_to_mixnode( + deps: DepsMut, + env: Env, + mix_identity: IdentityKey, + delegate: &str, + amount: Uint128, + proxy: Option, +) -> Result { + let delegate = deps.api.addr_validate(delegate)?; // check if the target node actually exists - if storage::mixnodes_read(deps.storage) - .load(mix_identity.as_bytes()) - .is_err() + if mixnodes_storage::mixnodes() + .may_load(deps.storage, &mix_identity)? + .is_none() { return Err(ContractError::MixNodeBondNotFound { identity: mix_identity, }); } - // update delegation of this delegator - storage::mix_delegations(deps.storage, &mix_identity).update::<_, ContractError>( - info.sender.as_bytes(), - |existing_delegation| { - // if no delegation existed, use default, i.e. 0 - let existing_delegation_amount = existing_delegation - .map(|existing_delegation| existing_delegation.amount) - .unwrap_or_default(); - - // the block height is reset, if it existed - Ok(RawDelegationData::new( - existing_delegation_amount + info.funds[0].amount, - env.block.height, - )) - }, - )?; + let maybe_proxy_storage = generate_storage_key(&delegate, proxy.as_ref()); + let storage_key = (mix_identity.clone(), maybe_proxy_storage).joined_key(); // update total_delegation of this node - storage::total_delegation(deps.storage).update::<_, ContractError>( - mix_identity.as_bytes(), + mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>( + deps.storage, + &mix_identity, |total_delegation| { // since we know that the target node exists and because the total_delegation bucket // entry is created whenever the node itself is added, the unwrap here is fine // as the entry MUST exist - Ok(total_delegation.unwrap() + info.funds[0].amount) + Ok(total_delegation.unwrap() + amount) }, )?; - // save information about delegations of this sender - storage::reverse_mix_delegations(deps.storage, &info.sender) - .save(mix_identity.as_bytes(), &())?; + // update [or create new] delegation of this delegator + storage::delegations().update::<_, ContractError>( + deps.storage, + storage_key, + |existing_delegation| { + Ok(match existing_delegation { + Some(mut existing_delegation) => { + existing_delegation.increment_amount(amount, Some(env.block.height)); + existing_delegation + } + None => Delegation::new( + delegate.to_owned(), + mix_identity, + Coin { + amount, + denom: DENOM.to_string(), + }, + env.block.height, + proxy, + ), + }) + }, + )?; Ok(Response::default()) } @@ -90,25 +139,55 @@ pub(crate) fn try_remove_delegation_from_mixnode( info: MessageInfo, mix_identity: IdentityKey, ) -> Result { - let mut delegation_bucket = storage::mix_delegations(deps.storage, &mix_identity); - let sender_bytes = info.sender.as_bytes(); + _try_remove_delegation_from_mixnode(deps, mix_identity, info.sender.as_str(), None) +} - if let Some(delegation) = delegation_bucket.may_load(sender_bytes)? { +pub(crate) fn try_remove_delegation_from_mixnode_on_behalf( + deps: DepsMut, + info: MessageInfo, + mix_identity: IdentityKey, + delegate: String, +) -> Result { + _try_remove_delegation_from_mixnode(deps, mix_identity, &delegate, Some(info.sender)) +} + +pub(crate) fn _try_remove_delegation_from_mixnode( + deps: DepsMut, + mix_identity: IdentityKey, + delegate: &str, + proxy: Option, +) -> Result { + let delegate = deps.api.addr_validate(delegate)?; + let delegation_map = storage::delegations(); + let maybe_proxy_storage = generate_storage_key(&delegate, proxy.as_ref()); + let storage_key = (mix_identity.clone(), maybe_proxy_storage).joined_key(); + + if let Some(old_delegation) = delegation_map.may_load(deps.storage, storage_key.clone())? { // remove all delegation associated with this delegator - delegation_bucket.remove(sender_bytes); - storage::reverse_mix_delegations(deps.storage, &info.sender) - .remove(mix_identity.as_bytes()); + if proxy != old_delegation.proxy { + return Err(ContractError::ProxyMismatch { + existing: old_delegation + .proxy + .map_or_else(|| "None".to_string(), |a| a.as_str().to_string()), + incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()), + }); + } + + delegation_map.replace(deps.storage, storage_key, None, Some(&old_delegation))?; // send delegated funds back to the delegation owner - let messages = vec![BankMsg::Send { - to_address: info.sender.to_string(), - amount: coins(delegation.amount.u128(), DENOM), - } - .into()]; + let return_tokens = BankMsg::Send { + to_address: proxy.as_ref().unwrap_or(&delegate).to_string(), + amount: coins( + old_delegation.amount.amount.u128(), + old_delegation.amount.denom.clone(), + ), + }; // update total_delegation of this node - storage::total_delegation(deps.storage).update::<_, ContractError>( - mix_identity.as_bytes(), + mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>( + deps.storage, + &mix_identity, |total_delegation| { // the first unwrap is fine because the delegation information MUST exist, otherwise we would // have never gotten here in the first place @@ -116,37 +195,48 @@ pub(crate) fn try_remove_delegation_from_mixnode( // if we do, it means we have some serious error in our logic Ok(total_delegation .unwrap() - .checked_sub(delegation.amount) + .checked_sub(old_delegation.amount.amount) .unwrap()) }, )?; - Ok(Response { - submessages: Vec::new(), - messages, - attributes: Vec::new(), - data: None, - }) + let mut response = Response::new().add_message(return_tokens); + + if let Some(proxy) = &proxy { + let msg = Some(VestingContractExecuteMsg::TrackUndelegation { + owner: delegate.as_str().to_string(), + mix_identity: mix_identity.clone(), + amount: old_delegation.amount, + }); + + let track_undelegation_msg = wasm_execute(proxy, &msg, coins(0, DENOM))?; + + response = response.add_message(track_undelegation_msg); + } + Ok(response) } else { Err(ContractError::NoMixnodeDelegationFound { identity: mix_identity, - address: info.sender, + address: delegate, }) } } + #[cfg(test)] mod tests { + use cosmwasm_std::coins; + + use crate::support::tests::test_helpers; + use super::storage; use super::*; - use crate::mixnodes::delegation_transactions::try_delegate_to_mixnode; - use crate::support::tests::test_helpers; - use cosmwasm_std::coins; #[cfg(test)] mod delegation_stake_validation { - use super::*; - use crate::mixnodes::delegation_transactions::validate_delegation_stake; use cosmwasm_std::coin; + + use super::*; + #[test] fn stake_cant_be_empty() { assert_eq!( @@ -154,6 +244,7 @@ mod tests { validate_delegation_stake(&[]) ) } + #[test] fn stake_must_have_single_coin_type() { assert_eq!( @@ -161,6 +252,7 @@ mod tests { validate_delegation_stake(&[coin(123, DENOM), coin(123, "BTC"), coin(123, "DOGE")]) ) } + #[test] fn stake_coin_must_be_of_correct_type() { assert_eq!( @@ -168,6 +260,7 @@ mod tests { validate_delegation_stake(&[coin(123, "DOGE")]) ) } + #[test] fn stake_coin_must_have_value_greater_than_zero() { assert_eq!( @@ -175,6 +268,7 @@ mod tests { validate_delegation_stake(&[coin(0, DENOM)]) ) } + #[test] fn stake_can_have_any_positive_value() { // this might change in the future, but right now an arbitrary (positive) value can be delegated @@ -186,9 +280,8 @@ mod tests { #[cfg(test)] mod mix_stake_delegation { - use super::storage; use super::*; - use crate::mixnodes::bonding_transactions::try_remove_mixnode; + use crate::mixnodes::transactions::try_remove_mixnode; use crate::support::tests::test_helpers::good_mixnode_bond; use cosmwasm_std::coin; use cosmwasm_std::testing::mock_env; @@ -206,7 +299,7 @@ mod tests { deps.as_mut(), mock_env(), mock_info("sender", &coins(123, DENOM)), - "non-existent-mix-identity".into() + "non-existent-mix-identity".into(), ) ); } @@ -223,25 +316,28 @@ mod tests { deps.as_mut(), mock_env(), mock_info(delegation_owner.as_str(), &[delegation.clone()]), - identity.clone() + identity.clone(), ) .is_ok()); + + let expected = Delegation::new( + delegation_owner.clone(), + identity.clone(), + delegation.clone(), + mock_env().block.height, + None, + ); + assert_eq!( - RawDelegationData::new(delegation.amount, mock_env().block.height), - storage::mix_delegations_read(&deps.storage, &identity) - .load(delegation_owner.as_bytes()) - .unwrap() - ); - assert!( - storage::reverse_mix_delegations_read(&deps.storage, &delegation_owner) - .load(identity.as_bytes()) - .is_ok() + expected, + test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap() ); + // node's "total_delegation" is increased assert_eq!( delegation.amount, - storage::total_delegation_read(&deps.storage) - .load(identity.as_bytes()) + mixnodes_storage::TOTAL_DELEGATION + .load(&deps.storage, &identity) .unwrap() ) } @@ -262,7 +358,7 @@ mod tests { deps.as_mut(), mock_env(), mock_info(delegation_owner.as_str(), &coins(123, DENOM)), - identity + identity, ) ); } @@ -281,25 +377,28 @@ mod tests { deps.as_mut(), mock_env(), mock_info(delegation_owner.as_str(), &[delegation.clone()]), - identity.clone() + identity.clone(), ) .is_ok()); + + let expected = Delegation::new( + delegation_owner.clone(), + identity.clone(), + delegation.clone(), + mock_env().block.height, + None, + ); + assert_eq!( - RawDelegationData::new(delegation.amount, mock_env().block.height), - storage::mix_delegations_read(&deps.storage, &identity) - .load(delegation_owner.as_bytes()) - .unwrap() - ); - assert!( - storage::reverse_mix_delegations_read(&deps.storage, &delegation_owner) - .load(identity.as_bytes()) - .is_ok() + expected, + test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap() ); + // node's "total_delegation" is increased assert_eq!( delegation.amount, - storage::total_delegation_read(&deps.storage) - .load(identity.as_bytes()) + mixnodes_storage::TOTAL_DELEGATION + .load(&deps.storage, &identity) .unwrap() ) } @@ -327,28 +426,29 @@ mod tests { identity.clone(), ) .unwrap(); + + let expected = Delegation::new( + delegation_owner.clone(), + identity.clone(), + coin(delegation1.amount.u128() + delegation2.amount.u128(), DENOM), + mock_env().block.height, + None, + ); + assert_eq!( - RawDelegationData::new( - delegation1.amount + delegation2.amount, - mock_env().block.height - ), - storage::mix_delegations_read(&deps.storage, &identity) - .load(delegation_owner.as_bytes()) - .unwrap() - ); - assert!( - storage::reverse_mix_delegations_read(&deps.storage, &delegation_owner) - .load(identity.as_bytes()) - .is_ok() + expected, + test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap() ); + // node's "total_delegation" is sum of both assert_eq!( delegation1.amount + delegation2.amount, - storage::total_delegation_read(&deps.storage) - .load(identity.as_bytes()) + mixnodes_storage::TOTAL_DELEGATION + .load(&deps.storage, &identity) .unwrap() ) } + #[test] fn block_height_is_updated_on_new_delegation() { let mut deps = test_helpers::init_contract(); @@ -371,10 +471,10 @@ mod tests { ) .unwrap(); assert_eq!( - RawDelegationData::new(delegation.amount, initial_height), - storage::mix_delegations_read(&deps.storage, &identity) - .load(delegation_owner.as_bytes()) + initial_height, + test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner) .unwrap() + .block_height ); try_delegate_to_mixnode( deps.as_mut(), @@ -383,12 +483,12 @@ mod tests { identity.clone(), ) .unwrap(); - assert_eq!( - RawDelegationData::new(delegation.amount + delegation.amount, updated_height), - storage::mix_delegations_read(&deps.storage, &identity) - .load(delegation_owner.as_bytes()) - .unwrap() - ); + + let updated = + test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner).unwrap(); + + assert_eq!(delegation.amount + delegation.amount, updated.amount.amount); + assert_eq!(updated_height, updated.block_height); } #[test] @@ -414,11 +514,12 @@ mod tests { identity.clone(), ) .unwrap(); + assert_eq!( - RawDelegationData::new(delegation1.amount, initial_height), - storage::mix_delegations_read(&deps.storage, &identity) - .load(delegation_owner1.as_bytes()) + initial_height, + test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner1) .unwrap() + .block_height ); try_delegate_to_mixnode( deps.as_mut(), @@ -427,17 +528,18 @@ mod tests { identity.clone(), ) .unwrap(); + assert_eq!( - RawDelegationData::new(delegation1.amount, initial_height), - storage::mix_delegations_read(&deps.storage, &identity) - .load(delegation_owner1.as_bytes()) + initial_height, + test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner1) .unwrap() + .block_height ); assert_eq!( - RawDelegationData::new(delegation2.amount, second_height), - storage::mix_delegations_read(&deps.storage, &identity) - .load(delegation_owner2.as_bytes()) + second_height, + test_helpers::read_delegation(&deps.storage, identity, &delegation_owner2) .unwrap() + .block_height ); } @@ -464,7 +566,7 @@ mod tests { deps.as_mut(), mock_env(), mock_info(delegation_owner.as_str(), &coins(50, DENOM)), - identity + identity, ) ); } @@ -483,37 +585,40 @@ mod tests { deps.as_mut(), mock_env(), mock_info(delegation_owner.as_str(), &coins(123, DENOM)), - identity1.clone() + identity1.clone(), ) .is_ok()); assert!(try_delegate_to_mixnode( deps.as_mut(), mock_env(), mock_info(delegation_owner.as_str(), &coins(42, DENOM)), - identity2.clone() + identity2.clone(), ) .is_ok()); - assert_eq!( - RawDelegationData::new(123u128.into(), mock_env().block.height), - storage::mix_delegations_read(&deps.storage, &identity1) - .load(delegation_owner.as_bytes()) - .unwrap() + + let expected1 = Delegation::new( + delegation_owner.clone(), + identity1.clone(), + coin(123, DENOM), + mock_env().block.height, + None, ); - assert!( - storage::reverse_mix_delegations_read(&deps.storage, &delegation_owner) - .load(identity1.as_bytes()) - .is_ok() + + let expected2 = Delegation::new( + delegation_owner.clone(), + identity2.clone(), + coin(42, DENOM), + mock_env().block.height, + None, + ); + + assert_eq!( + expected1, + test_helpers::read_delegation(&deps.storage, identity1, &delegation_owner).unwrap() ); assert_eq!( - RawDelegationData::new(42u128.into(), mock_env().block.height), - storage::mix_delegations_read(&deps.storage, &identity2) - .load(delegation_owner.as_bytes()) - .unwrap() - ); - assert!( - storage::reverse_mix_delegations_read(&deps.storage, &delegation_owner) - .load(identity2.as_bytes()) - .is_ok() + expected2, + test_helpers::read_delegation(&deps.storage, identity2, &delegation_owner).unwrap() ); } @@ -529,24 +634,25 @@ mod tests { deps.as_mut(), mock_env(), mock_info("sender1", &[delegation1.clone()]), - identity.clone() + identity.clone(), ) .is_ok()); assert!(try_delegate_to_mixnode( deps.as_mut(), mock_env(), mock_info("sender2", &[delegation2.clone()]), - identity.clone() + identity.clone(), ) .is_ok()); // node's "total_delegation" is sum of both assert_eq!( delegation1.amount + delegation2.amount, - storage::total_delegation_read(&deps.storage) - .load(identity.as_bytes()) + mixnodes_storage::TOTAL_DELEGATION + .load(&deps.storage, &identity) .unwrap() ) } + #[test] fn delegation_is_not_removed_if_node_unbonded() { let mut deps = test_helpers::init_contract(); @@ -554,40 +660,45 @@ mod tests { let identity = test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut()); let delegation_owner = Addr::unchecked("sender"); + let delegation_amount = coin(100, DENOM); try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info(delegation_owner.as_str(), &coins(100, DENOM)), + mock_info(delegation_owner.as_str(), &vec![delegation_amount.clone()]), identity.clone(), ) .unwrap(); try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); + + let expected = Delegation::new( + delegation_owner.clone(), + identity.clone(), + delegation_amount, + mock_env().block.height, + None, + ); + assert_eq!( - RawDelegationData::new(100u128.into(), mock_env().block.height), - storage::mix_delegations_read(&deps.storage, &identity) - .load(delegation_owner.as_bytes()) - .unwrap() - ); - assert!( - storage::reverse_mix_delegations_read(&deps.storage, &delegation_owner) - .load(identity.as_bytes()) - .is_ok() - ); + expected, + test_helpers::read_delegation(&deps.storage, identity, delegation_owner).unwrap() + ) } } #[cfg(test)] mod removing_mix_stake_delegation { - use super::storage; - use super::*; - use crate::mixnodes::bonding_transactions::try_remove_mixnode; - use crate::support::tests::test_helpers::good_mixnode_bond; use cosmwasm_std::coin; use cosmwasm_std::testing::mock_env; use cosmwasm_std::testing::mock_info; use cosmwasm_std::Addr; use cosmwasm_std::Uint128; + use crate::mixnodes::transactions::try_remove_mixnode; + use crate::support::tests::test_helpers::good_mixnode_bond; + + use super::storage; + use super::*; + #[test] fn fails_if_delegation_never_existed() { let mut deps = test_helpers::init_contract(); @@ -607,6 +718,7 @@ mod tests { ) ); } + #[test] fn succeeds_if_delegation_existed() { let mut deps = test_helpers::init_contract(); @@ -622,40 +734,33 @@ mod tests { ) .unwrap(); assert_eq!( - Ok(Response { - submessages: vec![], - messages: vec![BankMsg::Send { - to_address: delegation_owner.clone().into(), - amount: coins(100, DENOM), - } - .into()], - attributes: Vec::new(), - data: None, - }), + Ok(Response::new().add_message(BankMsg::Send { + to_address: delegation_owner.clone().into(), + amount: coins(100, DENOM), + })), try_remove_delegation_from_mixnode( deps.as_mut(), mock_info(delegation_owner.as_str(), &[]), identity.clone(), ) ); - assert!(storage::mix_delegations_read(&deps.storage, &identity) - .may_load(delegation_owner.as_bytes()) + assert!(storage::delegations() + .may_load( + &deps.storage, + (identity.clone(), delegation_owner).joined_key(), + ) .unwrap() .is_none()); - assert!( - storage::reverse_mix_delegations_read(&deps.storage, &delegation_owner) - .may_load(identity.as_bytes()) - .unwrap() - .is_none() - ); + // and total delegation is cleared assert_eq!( Uint128::zero(), - storage::total_delegation_read(&deps.storage) - .load(identity.as_bytes()) + mixnodes_storage::TOTAL_DELEGATION + .load(&deps.storage, &identity) .unwrap() ) } + #[test] fn succeeds_if_delegation_existed_even_if_node_unbonded() { let mut deps = test_helpers::init_contract(); @@ -672,33 +777,22 @@ mod tests { .unwrap(); try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); assert_eq!( - Ok(Response { - submessages: vec![], - messages: vec![BankMsg::Send { - to_address: delegation_owner.clone().into(), - amount: coins(100, DENOM), - } - .into()], - attributes: Vec::new(), - data: None, - }), + Ok(Response::new().add_message(BankMsg::Send { + to_address: delegation_owner.clone().into(), + amount: coins(100, DENOM), + })), try_remove_delegation_from_mixnode( deps.as_mut(), mock_info(delegation_owner.as_str(), &[]), identity.clone(), ) ); - assert!(storage::mix_delegations_read(&deps.storage, &identity) - .may_load(delegation_owner.as_bytes()) - .unwrap() - .is_none()); + assert!( - storage::reverse_mix_delegations_read(&deps.storage, &delegation_owner) - .may_load(identity.as_bytes()) - .unwrap() - .is_none() + test_helpers::read_delegation(&deps.storage, identity, delegation_owner).is_none() ); } + #[test] fn total_delegation_is_preserved_if_only_some_undelegate() { let mut deps = test_helpers::init_contract(); @@ -713,14 +807,14 @@ mod tests { deps.as_mut(), mock_env(), mock_info(delegation_owner1.as_str(), &[delegation1.clone()]), - identity.clone() + identity.clone(), ) .is_ok()); assert!(try_delegate_to_mixnode( deps.as_mut(), mock_env(), mock_info(delegation_owner2.as_str(), &[delegation2.clone()]), - identity.clone() + identity.clone(), ) .is_ok()); // sender1 undelegates @@ -734,37 +828,37 @@ mod tests { // node's "total_delegation" is sum of both assert_eq!( delegation2.amount, - storage::total_delegation_read(&deps.storage) - .load(identity.as_bytes()) + mixnodes_storage::TOTAL_DELEGATION + .load(&deps.storage, &identity) .unwrap() ) } } - #[cfg(test)] - mod multi_delegations { - use super::*; - use crate::mixnodes::delegation_helpers; - use crate::mixnodes::delegation_queries::tests::store_n_mix_delegations; - use crate::support::tests::test_helpers; - use mixnet_contract::IdentityKey; - use mixnet_contract::RawDelegationData; - - #[test] - fn multiple_page_delegations() { - let mut deps = test_helpers::init_contract(); - let node_identity: IdentityKey = "foo".into(); - store_n_mix_delegations( - storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10, - &mut deps.storage, - &node_identity, - ); - let mix_bucket = storage::all_mix_delegations_read::(&deps.storage); - let mix_delegations = delegation_helpers::Delegations::new(mix_bucket); - assert_eq!( - storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10, - mix_delegations.count() as u32 - ); - } - } + // #[cfg(test)] + // mod multi_delegations { + // use super::*; + // use crate::delegations::helpers; + // use crate::delegations::queries::tests::store_n_mix_delegations; + // use crate::support::tests::test_helpers; + // use mixnet_contract::IdentityKey; + // use mixnet_contract::RawDelegationData; + // + // #[test] + // fn multiple_page_delegations() { + // let mut deps = test_helpers::init_contract(); + // let node_identity: IdentityKey = "foo".into(); + // store_n_mix_delegations( + // storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10, + // &mut deps.storage, + // &node_identity, + // ); + // let mix_bucket = storage::all_mix_delegations_read::(&deps.storage); + // let mix_delegations = helpers::Delegations::new(mix_bucket); + // assert_eq!( + // storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10, + // mix_delegations.count() as u32 + // ); + // } + // } } diff --git a/contracts/mixnet/src/error.rs b/contracts/mixnet/src/error.rs index c4131303a0..b2ef5bfdf0 100644 --- a/contracts/mixnet/src/error.rs +++ b/contracts/mixnet/src/error.rs @@ -101,4 +101,7 @@ pub enum ContractError { #[error("Mixnode's {identity} operator has not been rewarded yet - cannot perform delegator rewarding until that happens")] MixnodeOperatorNotRewarded { identity: IdentityKey }, + + #[error("Proxy address mismatch, expected {existing}, got {incoming}")] + ProxyMismatch { existing: String, incoming: String }, } diff --git a/contracts/mixnet/src/gateways/queries.rs b/contracts/mixnet/src/gateways/queries.rs index 245efddf31..48fc6e0328 100644 --- a/contracts/mixnet/src/gateways/queries.rs +++ b/contracts/mixnet/src/gateways/queries.rs @@ -1,7 +1,10 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use super::storage; use crate::mixnodes::storage::{BOND_PAGE_DEFAULT_LIMIT, BOND_PAGE_MAX_LIMIT}; // Keeps gateway and mixnode retrieval in sync by re-using the constant. Could be split into its own constant. -use crate::query_support::calculate_start_value; -use cosmwasm_std::{Addr, Deps, Order, StdResult}; +use cosmwasm_std::{Deps, Order, StdResult}; +use cw_storage_plus::Bound; use mixnet_contract::{GatewayBond, GatewayOwnershipResponse, IdentityKey, PagedGatewayResponse}; pub(crate) fn query_gateways_paged( @@ -12,10 +15,10 @@ pub(crate) fn query_gateways_paged( let limit = limit .unwrap_or(BOND_PAGE_DEFAULT_LIMIT) .min(BOND_PAGE_MAX_LIMIT) as usize; - let start = calculate_start_value(start_after); + let start = start_after.map(Bound::exclusive); - let nodes = storage::gateways_read(deps.storage) - .range(start.as_deref(), None, Order::Ascending) + let nodes = storage::gateways() + .range(deps.storage, start, None, Order::Ascending) .take(limit) .map(|res| res.map(|item| item.1)) .collect::>>()?; @@ -25,12 +28,19 @@ pub(crate) fn query_gateways_paged( Ok(PagedGatewayResponse::new(nodes, limit, start_next_after)) } -pub(crate) fn query_owns_gateway(deps: Deps, address: Addr) -> StdResult { - let has_gateway = storage::gateways_owners_read(deps.storage) - .may_load(address.as_bytes())? +pub(crate) fn query_owns_gateway( + deps: Deps, + address: String, +) -> StdResult { + let validated_addr = deps.api.addr_validate(&address)?; + + let has_gateway = storage::gateways() + .idx + .owner + .item(deps.storage, validated_addr.clone())? .is_some(); Ok(GatewayOwnershipResponse { - address, + address: validated_addr, has_gateway, }) } @@ -38,11 +48,8 @@ pub(crate) fn query_owns_gateway(deps: Deps, address: Addr) -> StdResult +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_std::Addr; +use cw_storage_plus::{Index, IndexList, IndexedMap, UniqueIndex}; +use mixnet_contract::{GatewayBond, IdentityKeyRef}; // storage prefixes -const PREFIX_GATEWAYS: &[u8] = b"gt"; -const PREFIX_GATEWAYS_OWNERS: &[u8] = b"go"; +const GATEWAYS_PK_NAMESPACE: &str = "gt"; +const GATEWAYS_OWNER_IDX_NAMESPACE: &str = "gto"; -pub fn gateways(storage: &mut dyn Storage) -> Bucket { - bucket(storage, PREFIX_GATEWAYS) +pub(crate) struct GatewayBondIndex<'a> { + pub(crate) owner: UniqueIndex<'a, Addr, GatewayBond>, } -pub fn gateways_read(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, PREFIX_GATEWAYS) +// IndexList is just boilerplate code for fetching a struct's indexes +// note that from my understanding this will be converted into a macro at some point in the future +impl<'a> IndexList for GatewayBondIndex<'a> { + fn get_indexes(&'_ self) -> Box> + '_> { + let v: Vec<&dyn Index> = vec![&self.owner]; + Box::new(v.into_iter()) + } } -// owner address -> node identity -pub fn gateways_owners(storage: &mut dyn Storage) -> Bucket { - bucket(storage, PREFIX_GATEWAYS_OWNERS) -} - -pub fn gateways_owners_read(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, PREFIX_GATEWAYS_OWNERS) +// gateways() is the storage access function. +pub(crate) fn gateways<'a>() -> IndexedMap<'a, IdentityKeyRef<'a>, GatewayBond, GatewayBondIndex<'a>> +{ + let indexes = GatewayBondIndex { + owner: UniqueIndex::new(|d| d.owner.clone(), GATEWAYS_OWNER_IDX_NAMESPACE), + }; + IndexedMap::new(GATEWAYS_PK_NAMESPACE, indexes) } // currently not used outside tests #[cfg(test)] mod tests { - use cosmwasm_std::StdResult; - use cosmwasm_std::Storage; - use super::super::storage; use crate::support::tests::test_helpers; use config::defaults::DENOM; use cosmwasm_std::testing::MockStorage; + use cosmwasm_std::StdResult; + use cosmwasm_std::Storage; use cosmwasm_std::{coin, Addr, Uint128}; - use mixnet_contract::Gateway; use mixnet_contract::GatewayBond; use mixnet_contract::IdentityKey; + use mixnet_contract::{Gateway, IdentityKeyRef}; // currently this is only used in tests but may become useful later on - pub(crate) fn read_gateway_bond( + pub(crate) fn read_gateway_bond_amount( storage: &dyn Storage, - identity: &[u8], + identity: IdentityKeyRef, ) -> StdResult { - let bucket = storage::gateways_read(storage); - let node = bucket.load(identity)?; + let node = storage::gateways().load(storage, identity)?; Ok(node.bond_amount.amount) } #[test] fn gateway_single_read_retrieval() { let mut storage = MockStorage::new(); - let bond1 = test_helpers::gateway_bond_fixture(); - let bond2 = test_helpers::gateway_bond_fixture(); - storage::gateways(&mut storage) - .save(b"bond1", &bond1) + let bond1 = test_helpers::gateway_bond_fixture("owner1"); + let bond2 = test_helpers::gateway_bond_fixture("owner2"); + storage::gateways() + .save(&mut storage, "bond1", &bond1) .unwrap(); - storage::gateways(&mut storage) - .save(b"bond2", &bond2) + storage::gateways() + .save(&mut storage, "bond2", &bond2) .unwrap(); - let res1 = storage::gateways_read(&storage).load(b"bond1").unwrap(); - let res2 = storage::gateways_read(&storage).load(b"bond2").unwrap(); + let res1 = storage::gateways().load(&storage, "bond1").unwrap(); + let res2 = storage::gateways().load(&storage, "bond2").unwrap(); assert_eq!(bond1, res1); assert_eq!(bond2, res2); } @@ -77,7 +79,7 @@ mod tests { let node_identity: IdentityKey = "nodeidentity".into(); // produces an error if target gateway doesn't exist - let res = read_gateway_bond(&mock_storage, node_owner.as_bytes()); + let res = read_gateway_bond_amount(&mock_storage, &node_identity); assert!(res.is_err()); // returns appropriate value otherwise @@ -93,13 +95,13 @@ mod tests { }, }; - storage::gateways(&mut mock_storage) - .save(node_identity.as_bytes(), &gateway_bond) + storage::gateways() + .save(&mut mock_storage, &node_identity, &gateway_bond) .unwrap(); assert_eq!( - Uint128(bond_value), - read_gateway_bond(&mock_storage, node_identity.as_bytes()).unwrap() + Uint128::new(bond_value), + read_gateway_bond_amount(&mock_storage, &node_identity).unwrap() ); } } diff --git a/contracts/mixnet/src/gateways/transactions.rs b/contracts/mixnet/src/gateways/transactions.rs index db3d2bf605..1253f7c5c9 100644 --- a/contracts/mixnet/src/gateways/transactions.rs +++ b/contracts/mixnet/src/gateways/transactions.rs @@ -1,10 +1,13 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use super::storage; use crate::error::ContractError; use crate::mixnet_contract_settings::storage as mixnet_params_storage; -use crate::mixnodes::storage as mixnodes_storage; +use crate::support::helpers::ensure_no_existing_bond; use config::defaults::DENOM; -use cosmwasm_std::{attr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128}; -use mixnet_contract::{Gateway, GatewayBond, Layer}; +use cosmwasm_std::{BankMsg, Coin, DepsMut, Env, MessageInfo, Response, StdError, Uint128}; +use mixnet_contract::{Gateway, GatewayBond, IdentityKey, Layer}; pub(crate) fn try_add_gateway( deps: DepsMut, @@ -12,27 +15,12 @@ pub(crate) fn try_add_gateway( info: MessageInfo, gateway: Gateway, ) -> Result { - let sender_bytes = info.sender.as_bytes(); - - // if the client has an active bonded mixnode, don't allow gateway bonding - if mixnodes_storage::mixnodes_owners_read(deps.storage) - .may_load(sender_bytes)? - .is_some() - { - return Err(ContractError::AlreadyOwnsMixnode); - } - - // if the client has an active bonded gateway, regardless of its identity, don't allow bonding - if storage::gateways_owners_read(deps.storage) - .may_load(sender_bytes)? - .is_some() - { - return Err(ContractError::AlreadyOwnsGateway); - } + // if the client has an active bonded mixnode or gateway, don't allow bonding + ensure_no_existing_bond(deps.storage, &info.sender)?; // check if somebody else has already bonded a gateway with this identity if let Some(existing_bond) = - storage::gateways_read(deps.storage).may_load(gateway.identity_key.as_bytes())? + storage::gateways().may_load(deps.storage, &gateway.identity_key)? { if existing_bond.owner != info.sender { return Err(ContractError::DuplicateGateway { @@ -41,8 +29,10 @@ pub(crate) fn try_add_gateway( } } - let minimum_bond = - mixnet_params_storage::read_contract_settings_params(deps.storage).minimum_gateway_bond; + let minimum_bond = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.storage)? + .params + .minimum_gateway_bond; validate_gateway_bond(&info.funds, minimum_bond)?; let bond = GatewayBond::new( @@ -52,9 +42,7 @@ pub(crate) fn try_add_gateway( gateway, ); - let identity = bond.identity(); - storage::gateways(deps.storage).save(identity.as_bytes(), &bond)?; - storage::gateways_owners(deps.storage).save(sender_bytes, identity)?; + storage::gateways().save(deps.storage, bond.identity(), &bond)?; mixnet_params_storage::increment_layer_count(deps.storage, Layer::Gateway)?; Ok(Response::new()) @@ -64,45 +52,38 @@ pub(crate) fn try_remove_gateway( deps: DepsMut, info: MessageInfo, ) -> Result { - let sender_bytes = info.sender.as_str().as_bytes(); - - // try to find the identity of the sender's node - let gateway_identity = - match storage::gateways_owners_read(deps.storage).may_load(sender_bytes)? { - Some(identity) => identity, - None => return Err(ContractError::NoAssociatedGatewayBond { owner: info.sender }), - }; - - // get the bond, since we found associated identity, the node MUST exist - let gateway_bond = storage::gateways_read(deps.storage).load(gateway_identity.as_bytes())?; + // try to find the node of the sender + let (raw_identity, gateway_bond) = match storage::gateways() + .idx + .owner + .item(deps.storage, info.sender.clone())? + { + Some(record) => (record.0, record.1), + None => return Err(ContractError::NoAssociatedGatewayBond { owner: info.sender }), + }; // send bonded funds back to the bond owner - let messages = vec![BankMsg::Send { + let return_tokens = BankMsg::Send { to_address: info.sender.as_str().to_owned(), amount: vec![gateway_bond.bond_amount()], - } - .into()]; + }; + + // Given that this Vec came directly from the storage and originated from a valid String before + // if this error is ever thrown it implies the entire storage got corrupted. + let gateway_identity = IdentityKey::from_utf8(raw_identity) + .map_err(|_| StdError::parse_err("IdentityKey", "Storage got corrupted"))?; + + // remove the bond + storage::gateways().remove(deps.storage, &gateway_identity)?; - // remove the bond from the list of bonded gateways - storage::gateways(deps.storage).remove(gateway_identity.as_bytes()); - // remove the node ownership - storage::gateways_owners(deps.storage).remove(sender_bytes); // decrement layer count mixnet_params_storage::decrement_layer_count(deps.storage, Layer::Gateway)?; - // log our actions - let attributes = vec![ - attr("action", "unbond"), - attr("address", info.sender), - attr("gateway_bond", gateway_bond), - ]; - - Ok(Response { - submessages: Vec::new(), - messages, - attributes, - data: None, - }) + Ok(Response::new() + .add_message(return_tokens) + .add_attribute("action", "unbond") + .add_attribute("address", info.sender) + .add_attribute("gateway_bond", gateway_bond.to_string())) } fn validate_gateway_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), ContractError> { @@ -284,8 +265,8 @@ pub mod tests { }; // before the execution the node had no associated owner - assert!(storage::gateways_owners_read(deps.as_ref().storage) - .may_load("gateway-owner".as_bytes()) + assert!(storage::gateways() + .may_load(deps.as_ref().storage, "gateway-owner") .unwrap() .is_none()); @@ -295,9 +276,14 @@ pub mod tests { assert_eq!( "myAwesomeGateway", - storage::gateways_owners_read(deps.as_ref().storage) - .load("gateway-owner".as_bytes()) + storage::gateways() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("gateway-owner")) .unwrap() + .unwrap() + .1 + .identity() ); } @@ -382,7 +368,7 @@ pub mod tests { ); // let's add a node owned by bob - test_helpers::add_gateway("bob", test_helpers::good_gateway_bond(), &mut deps); + test_helpers::add_gateway("bob", test_helpers::good_gateway_bond(), deps.as_mut()); // attempt to unbond fred's node, which doesn't exist let info = mock_info("fred", &[]); @@ -437,19 +423,16 @@ pub mod tests { ]; // we should see a funds transfer from the contract back to fred - let expected_messages = vec![BankMsg::Send { + let expected_message = BankMsg::Send { to_address: String::from(info.sender), amount: test_helpers::good_gateway_bond(), - } - .into()]; - - // run the executer and check that we got back the correct results - let expected = Response { - submessages: Vec::new(), - messages: expected_messages, - attributes: expected_attributes, - data: None, }; + + // run the executor and check that we got back the correct results + let expected = Response::new() + .add_attributes(expected_attributes) + .add_message(expected_message); + assert_eq!(remove_fred, expected); // only 1 node now exists, owned by bob: @@ -473,9 +456,14 @@ pub mod tests { execute(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!( "myAwesomeGateway", - storage::gateways_owners_read(deps.as_ref().storage) - .load("gateway-owner".as_bytes()) + storage::gateways() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("gateway-owner")) .unwrap() + .unwrap() + .1 + .identity() ); let info = mock_info("gateway-owner", &[]); @@ -483,8 +471,10 @@ pub mod tests { assert!(execute(deps.as_mut(), mock_env(), info, msg).is_ok()); - assert!(storage::gateways_owners_read(deps.as_ref().storage) - .may_load("gateway-owner".as_bytes()) + assert!(storage::gateways() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("gateway-owner")) .unwrap() .is_none()); @@ -500,9 +490,14 @@ pub mod tests { assert!(execute(deps.as_mut(), mock_env(), info, msg).is_ok()); assert_eq!( "myAwesomeGateway", - storage::gateways_owners_read(deps.as_ref().storage) - .load("gateway-owner".as_bytes()) + storage::gateways() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("gateway-owner")) .unwrap() + .unwrap() + .1 + .identity() ); } @@ -514,7 +509,7 @@ pub mod tests { // you must send at least 100 coins... let mut bond = test_helpers::good_gateway_bond(); - bond[0].amount = INITIAL_GATEWAY_BOND.checked_sub(Uint128(1)).unwrap(); + bond[0].amount = INITIAL_GATEWAY_BOND.checked_sub(Uint128::new(1)).unwrap(); let result = validate_gateway_bond(&bond, INITIAL_GATEWAY_BOND); assert_eq!( result, @@ -526,7 +521,7 @@ pub mod tests { // more than that is still fine let mut bond = test_helpers::good_gateway_bond(); - bond[0].amount = INITIAL_GATEWAY_BOND + Uint128(1); + bond[0].amount = INITIAL_GATEWAY_BOND + Uint128::new(1); let result = validate_gateway_bond(&bond, INITIAL_GATEWAY_BOND); assert!(result.is_ok()); diff --git a/contracts/mixnet/src/lib.rs b/contracts/mixnet/src/lib.rs index a76f961144..9db84c4aab 100644 --- a/contracts/mixnet/src/lib.rs +++ b/contracts/mixnet/src/lib.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 pub mod contract; +mod delegations; pub mod error; pub mod gateways; pub mod mixnet_contract_settings; pub mod mixnodes; -pub mod query_support; mod rewards; pub mod support; diff --git a/contracts/mixnet/src/mixnet_contract_settings/queries.rs b/contracts/mixnet/src/mixnet_contract_settings/queries.rs index 691a0e71f1..46d2f3d997 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/queries.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/queries.rs @@ -1,20 +1,21 @@ use super::storage; -use cosmwasm_std::Deps; +use cosmwasm_std::{Deps, StdResult}; use mixnet_contract::{ContractSettingsParams, MixnetContractVersion, RewardingIntervalResponse}; -pub(crate) fn query_contract_settings_params(deps: Deps) -> ContractSettingsParams { - storage::read_contract_settings_params(deps.storage) +pub(crate) fn query_contract_settings_params(deps: Deps) -> StdResult { + storage::CONTRACT_SETTINGS + .load(deps.storage) + .map(|settings| settings.params) } -pub(crate) fn query_rewarding_interval(deps: Deps) -> RewardingIntervalResponse { - let state = storage::contract_settings_read(deps.storage) - .load() - .unwrap(); - RewardingIntervalResponse { +pub(crate) fn query_rewarding_interval(deps: Deps) -> StdResult { + let state = storage::CONTRACT_SETTINGS.load(deps.storage)?; + + Ok(RewardingIntervalResponse { current_rewarding_interval_starting_block: state.rewarding_interval_starting_block, current_rewarding_interval_nonce: state.latest_rewarding_interval_nonce, rewarding_in_progress: state.rewarding_in_progress, - } + }) } pub(crate) fn query_contract_version() -> MixnetContractVersion { @@ -57,13 +58,13 @@ pub(crate) mod tests { rewarding_in_progress: false, }; - storage::contract_settings(deps.as_mut().storage) - .save(&dummy_state) + storage::CONTRACT_SETTINGS + .save(deps.as_mut().storage, &dummy_state) .unwrap(); assert_eq!( dummy_state.params, - query_contract_settings_params(deps.as_ref()) + query_contract_settings_params(deps.as_ref()).unwrap() ) } diff --git a/contracts/mixnet/src/mixnet_contract_settings/storage.rs b/contracts/mixnet/src/mixnet_contract_settings/storage.rs index 3f62d0e7f9..43f99faa2c 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/storage.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/storage.rs @@ -4,87 +4,57 @@ use crate::mixnet_contract_settings::models::ContractSettings; use cosmwasm_std::StdResult; use cosmwasm_std::Storage; -use cosmwasm_storage::singleton; -use cosmwasm_storage::singleton_read; -use cosmwasm_storage::ReadonlySingleton; -use cosmwasm_storage::Singleton; -use mixnet_contract::ContractSettingsParams; +use cw_storage_plus::Item; use mixnet_contract::Layer; use mixnet_contract::LayerDistribution; -// storage prefixes -const CONFIG_KEY: &[u8] = b"config"; -const LAYER_DISTRIBUTION_KEY: &[u8] = b"layers"; - -pub fn contract_settings(storage: &mut dyn Storage) -> Singleton { - singleton(storage, CONFIG_KEY) -} - -pub fn contract_settings_read(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, CONFIG_KEY) -} - -pub(crate) fn read_contract_settings_params(storage: &dyn Storage) -> ContractSettingsParams { - // note: In any other case, I wouldn't have attempted to unwrap this result, but in here - // if we fail to load the stored state we would already be in the undefined behaviour land, - // so we better just blow up immediately. - contract_settings_read(storage).load().unwrap().params -} - -pub fn layer_distribution(storage: &mut dyn Storage) -> Singleton { - singleton(storage, LAYER_DISTRIBUTION_KEY) -} - -pub(crate) fn read_layer_distribution(storage: &dyn Storage) -> LayerDistribution { - // note: In any other case, I wouldn't have attempted to unwrap this result, but in here - // if we fail to load the stored state we would already be in the undefined behaviour land, - // so we better just blow up immediately. - layer_distribution_read(storage).load().unwrap() -} - -pub fn layer_distribution_read(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, LAYER_DISTRIBUTION_KEY) -} +pub(crate) const CONTRACT_SETTINGS: Item = Item::new("config"); +pub(crate) const LAYERS: Item = Item::new("layers"); pub fn increment_layer_count(storage: &mut dyn Storage, layer: Layer) -> StdResult<()> { - let mut distribution = layer_distribution(storage).load()?; - match layer { - Layer::Gateway => distribution.gateways += 1, - Layer::One => distribution.layer1 += 1, - Layer::Two => distribution.layer2 += 1, - Layer::Three => distribution.layer3 += 1, - } - layer_distribution(storage).save(&distribution) + LAYERS + .update(storage, |mut distribution| { + match layer { + Layer::Gateway => distribution.gateways += 1, + Layer::One => distribution.layer1 += 1, + Layer::Two => distribution.layer2 += 1, + Layer::Three => distribution.layer3 += 1, + } + Ok(distribution) + }) + .map(|_| ()) } pub fn decrement_layer_count(storage: &mut dyn Storage, layer: Layer) -> StdResult<()> { - let mut distribution = layer_distribution(storage).load()?; - // It can't possibly go below zero, if it does, it means there's a serious error in the contract logic - match layer { - Layer::Gateway => { - distribution.gateways = distribution - .gateways - .checked_sub(1) - .expect("tried to subtract from unsigned zero!") - } - Layer::One => { - distribution.layer1 = distribution - .layer1 - .checked_sub(1) - .expect("tried to subtract from unsigned zero!") - } - Layer::Two => { - distribution.layer2 = distribution - .layer2 - .checked_sub(1) - .expect("tried to subtract from unsigned zero!") - } - Layer::Three => { - distribution.layer3 = distribution - .layer3 - .checked_sub(1) - .expect("tried to subtract from unsigned zero!") - } - }; - layer_distribution(storage).save(&distribution) + LAYERS + .update(storage, |mut distribution| { + match layer { + Layer::Gateway => { + distribution.gateways = distribution + .gateways + .checked_sub(1) + .expect("tried to subtract from unsigned zero!") + } + Layer::One => { + distribution.layer1 = distribution + .layer1 + .checked_sub(1) + .expect("tried to subtract from unsigned zero!") + } + Layer::Two => { + distribution.layer2 = distribution + .layer2 + .checked_sub(1) + .expect("tried to subtract from unsigned zero!") + } + Layer::Three => { + distribution.layer3 = distribution + .layer3 + .checked_sub(1) + .expect("tried to subtract from unsigned zero!") + } + } + Ok(distribution) + }) + .map(|_| ()) } diff --git a/contracts/mixnet/src/mixnet_contract_settings/transactions.rs b/contracts/mixnet/src/mixnet_contract_settings/transactions.rs index f7bafe6d7c..d7d3d0e571 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/transactions.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/transactions.rs @@ -13,7 +13,7 @@ pub(crate) fn try_update_contract_settings( info: MessageInfo, params: ContractSettingsParams, ) -> Result { - let mut state = storage::contract_settings_read(deps.storage).load()?; + let mut state = storage::CONTRACT_SETTINGS.load(deps.storage)?; // check if this is executed by the owner, if not reject the transaction if info.sender != state.owner { @@ -35,8 +35,7 @@ pub(crate) fn try_update_contract_settings( } state.params = params; - - storage::contract_settings(deps.storage).save(&state)?; + storage::CONTRACT_SETTINGS.save(deps.storage, &state)?; Ok(Response::default()) } @@ -66,8 +65,8 @@ pub mod tests { // sanity check to ensure new_params are different than the default ones assert_ne!( new_params, - storage::contract_settings_read(deps.as_ref().storage) - .load() + storage::CONTRACT_SETTINGS + .load(deps.as_ref().storage) .unwrap() .params ); @@ -83,8 +82,8 @@ pub mod tests { assert_eq!(res, Ok(Response::default())); // and the state is actually updated - let current_state = storage::contract_settings_read(deps.as_ref().storage) - .load() + let current_state = storage::CONTRACT_SETTINGS + .load(deps.as_ref().storage) .unwrap(); assert_eq!(current_state.params, new_params); diff --git a/contracts/mixnet/src/mixnodes/bonding_queries.rs b/contracts/mixnet/src/mixnodes/bonding_queries.rs index 778fbb2ad1..c00a2c0c9b 100644 --- a/contracts/mixnet/src/mixnodes/bonding_queries.rs +++ b/contracts/mixnet/src/mixnodes/bonding_queries.rs @@ -2,13 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use super::storage; -use crate::query_support::calculate_start_value; -use config::defaults::DENOM; -use cosmwasm_std::{coin, Addr, Deps, Order, StdResult}; -use mixnet_contract::{ - Delegation, IdentityKey, MixNodeBond, MixOwnershipResponse, PagedMixDelegationsResponse, - PagedMixnodeResponse, -}; +use cosmwasm_std::{Deps, Order, StdResult}; +use cw_storage_plus::Bound; +use mixnet_contract::{IdentityKey, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse}; pub fn query_mixnodes_paged( deps: Deps, @@ -18,17 +14,18 @@ pub fn query_mixnodes_paged( let limit = limit .unwrap_or(storage::BOND_PAGE_DEFAULT_LIMIT) .min(storage::BOND_PAGE_MAX_LIMIT) as usize; - let start = calculate_start_value(start_after); - let nodes = storage::mixnodes_read(deps.storage) - .range(start.as_deref(), None, Order::Ascending) + let start = start_after.map(Bound::exclusive); + + let nodes = storage::mixnodes() + .range(deps.storage, start, None, Order::Ascending) .take(limit) .map(|res| res.map(|item| item.1)) .map(|stored_bond| { // I really don't like this additional read per entry, but I don't see an obvious way to remove it stored_bond.map(|stored_bond| { - let total_delegation = storage::total_delegation_read(deps.storage) - .load(stored_bond.identity().as_bytes()); + let total_delegation = + storage::TOTAL_DELEGATION.load(deps.storage, stored_bond.identity()); total_delegation .map(|total_delegation| stored_bond.attach_delegation(total_delegation)) }) @@ -40,57 +37,26 @@ pub fn query_mixnodes_paged( Ok(PagedMixnodeResponse::new(nodes, limit, start_next_after)) } -pub fn query_owns_mixnode(deps: Deps, address: Addr) -> StdResult { - let has_node = storage::mixnodes_owners_read(deps.storage) - .may_load(address.as_bytes())? +pub fn query_owns_mixnode(deps: Deps, address: String) -> StdResult { + let validated_addr = deps.api.addr_validate(&address)?; + let has_node = storage::mixnodes() + .idx + .owner + .item(deps.storage, validated_addr.clone())? .is_some(); - Ok(MixOwnershipResponse { address, has_node }) -} - -pub(crate) fn query_mixnode_delegations_paged( - deps: Deps, - mix_identity: IdentityKey, - start_after: Option, - limit: Option, -) -> StdResult { - let limit = limit - .unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT) - .min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize; - let start = calculate_start_value(start_after); - - let delegations = storage::mix_delegations_read(deps.storage, &mix_identity) - .range(start.as_deref(), None, Order::Ascending) - .take(limit) - .map(|res| { - res.map(|entry| { - Delegation::new( - Addr::unchecked(String::from_utf8(entry.0).expect( - "Non-UTF8 address used as key in bucket. The storage is corrupted!", - )), - coin(entry.1.amount.u128(), DENOM), - entry.1.block_height, - ) - }) - }) - .collect::>>()?; - - let start_next_after = delegations.last().map(|delegation| delegation.owner()); - - Ok(PagedMixDelegationsResponse::new( - mix_identity, - delegations, - start_next_after, - )) + Ok(MixOwnershipResponse { + address: validated_addr, + has_node, + }) } #[cfg(test)] pub(crate) mod tests { - use super::*; - use super::storage; + use super::*; + use crate::mixnodes::storage::BOND_PAGE_DEFAULT_LIMIT; use crate::support::tests::test_helpers; use cosmwasm_std::testing::{mock_env, mock_info}; - use cosmwasm_std::Addr; use mixnet_contract::MixNode; #[test] @@ -116,7 +82,7 @@ pub(crate) mod tests { #[test] fn mixnodes_paged_retrieval_has_default_limit() { let mut deps = test_helpers::init_contract(); - for n in 0..100 { + for n in 0..1000 { let key = format!("bond{}", n); test_helpers::add_mixnode(&key, test_helpers::good_mixnode_bond(), deps.as_mut()); } @@ -124,8 +90,7 @@ pub(crate) mod tests { // query without explicitly setting a limit let page1 = query_mixnodes_paged(deps.as_ref(), None, None).unwrap(); - let expected_limit = 50; - assert_eq!(expected_limit, page1.nodes.len() as u32); + assert_eq!(BOND_PAGE_DEFAULT_LIMIT, page1.nodes.len() as u32); } #[test] @@ -208,7 +173,7 @@ pub(crate) mod tests { let mut deps = test_helpers::init_contract(); // "fred" does not own a mixnode if there are no mixnodes - let res = query_owns_mixnode(deps.as_ref(), Addr::unchecked("fred")).unwrap(); + let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap(); assert!(!res.has_node); // mixnode was added to "bob", "fred" still does not own one @@ -216,7 +181,7 @@ pub(crate) mod tests { identity_key: "bobsnode".into(), ..test_helpers::mix_node_fixture() }; - crate::mixnodes::bonding_transactions::try_add_mixnode( + crate::mixnodes::transactions::try_add_mixnode( deps.as_mut(), mock_env(), mock_info("bob", &test_helpers::good_mixnode_bond()), @@ -224,7 +189,7 @@ pub(crate) mod tests { ) .unwrap(); - let res = query_owns_mixnode(deps.as_ref(), Addr::unchecked("fred")).unwrap(); + let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap(); assert!(!res.has_node); // "fred" now owns a mixnode! @@ -232,7 +197,7 @@ pub(crate) mod tests { identity_key: "fredsnode".into(), ..test_helpers::mix_node_fixture() }; - crate::mixnodes::bonding_transactions::try_add_mixnode( + crate::mixnodes::transactions::try_add_mixnode( deps.as_mut(), mock_env(), mock_info("fred", &test_helpers::good_mixnode_bond()), @@ -240,17 +205,14 @@ pub(crate) mod tests { ) .unwrap(); - let res = query_owns_mixnode(deps.as_ref(), Addr::unchecked("fred")).unwrap(); + let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap(); assert!(res.has_node); // but after unbonding it, he doesn't own one anymore - crate::mixnodes::bonding_transactions::try_remove_mixnode( - deps.as_mut(), - mock_info("fred", &[]), - ) - .unwrap(); + crate::mixnodes::transactions::try_remove_mixnode(deps.as_mut(), mock_info("fred", &[])) + .unwrap(); - let res = query_owns_mixnode(deps.as_ref(), Addr::unchecked("fred")).unwrap(); + let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap(); assert!(!res.has_node); } } diff --git a/contracts/mixnet/src/mixnodes/delegation_helpers.rs b/contracts/mixnet/src/mixnodes/delegation_helpers.rs deleted file mode 100644 index b7b6769fae..0000000000 --- a/contracts/mixnet/src/mixnodes/delegation_helpers.rs +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use cosmwasm_std::{Addr, Order, StdError, StdResult}; -use cosmwasm_storage::ReadonlyBucket; -use mixnet_contract::IdentityKey; -use mixnet_contract::PagedAllDelegationsResponse; -use mixnet_contract::UnpackedDelegation; -use serde::de::DeserializeOwned; -use serde::Serialize; - -pub(crate) fn get_all_delegations_paged( - bucket: &ReadonlyBucket, - start_after: &Option>, - limit: usize, -) -> StdResult> -where - T: Serialize + DeserializeOwned, -{ - let delegations = bucket - .range(start_after.as_deref(), None, Order::Ascending) - .filter(|res| res.is_ok()) - .take(limit) - .map(|res| { - res.map(|entry| { - let (owner, identity) = extract_identity_and_owner(entry.0).expect("Invalid node identity or address used as key in bucket. The storage is corrupted!"); - UnpackedDelegation::new(owner, identity, entry.1) - }) - }) - .collect::>>>()?; - - let start_next_after = if let Some(Ok(last)) = bucket - .range(start_after.as_deref(), None, Order::Ascending) - .filter(|res| res.is_ok()) - .take(limit) - .last() - { - Some(last.0) - } else { - None - }; - - Ok(PagedAllDelegationsResponse::new( - delegations, - start_next_after, - )) -} - -// Extracts the node identity and owner of a delegation from the bytes used as -// key in the delegation buckets. -fn extract_identity_and_owner(bytes: Vec) -> StdResult<(Addr, IdentityKey)> { - // cosmwasm bucket internal value - const NAMESPACE_LENGTH: usize = 2; - - if bytes.len() < NAMESPACE_LENGTH { - return Err(StdError::parse_err( - "mixnet_contract::types::IdentityKey", - "Invalid type", - )); - } - let identity_size = u16::from_be_bytes([bytes[0], bytes[1]]) as usize; - let identity_bytes: Vec = bytes - .iter() - .skip(NAMESPACE_LENGTH) - .take(identity_size) - .copied() - .collect(); - let identity = IdentityKey::from_utf8(identity_bytes) - .map_err(|_| StdError::parse_err("mixnet_contract::types::IdentityKey", "Invalid type"))?; - let owner_bytes: Vec = bytes - .iter() - .skip(NAMESPACE_LENGTH + identity_size) - .copied() - .collect(); - let owner = Addr::unchecked( - String::from_utf8(owner_bytes) - .map_err(|_| StdError::parse_err("cosmwasm_std::addresses::Addr", "Invalid type"))?, - ); - - Ok((owner, identity)) -} - -#[cfg(test)] -pub(crate) const OLD_DELEGATIONS_CHUNK_SIZE: usize = 500; - -#[cfg(test)] -pub struct Delegations<'a, T: Clone + Serialize + DeserializeOwned> { - delegations_bucket: ReadonlyBucket<'a, T>, - curr_delegations: Vec>, - curr_index: usize, - start_after: Option>, - last_page: bool, -} - -#[cfg(test)] -impl<'a, T: Clone + Serialize + DeserializeOwned> Delegations<'a, T> { - pub fn new(delegations_bucket: ReadonlyBucket<'a, T>) -> Self { - Delegations { - delegations_bucket, - curr_delegations: vec![], - curr_index: OLD_DELEGATIONS_CHUNK_SIZE, - start_after: None, - last_page: false, - } - } -} - -#[cfg(test)] -impl<'a, T: Clone + Serialize + DeserializeOwned> Iterator for Delegations<'a, T> { - type Item = UnpackedDelegation; - - fn next(&mut self) -> Option { - if self.curr_index == OLD_DELEGATIONS_CHUNK_SIZE && !self.last_page { - self.start_after = self.start_after.clone().map(|mut v: Vec| { - v.push(0); - v - }); - let delegations_paged = get_all_delegations_paged( - &self.delegations_bucket, - &self.start_after, - OLD_DELEGATIONS_CHUNK_SIZE, - ) - .ok()?; - self.curr_delegations = delegations_paged.delegations; - self.curr_index = 0; - self.start_after = delegations_paged.start_next_after; - if self.start_after.is_none() { - self.last_page = true; - } - } - if self.curr_index < self.curr_delegations.len() { - let ret = self.curr_delegations[self.curr_index].clone(); - self.curr_index += 1; - Some(ret) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mixnodes::delegation_queries::tests::store_n_mix_delegations; - use crate::mixnodes::storage as mixnodes_storage; - use crate::support::tests::test_helpers; - use crate::support::tests::test_helpers::identity_and_owner_to_bytes; - use cosmwasm_std::testing::mock_dependencies; - use mixnet_contract::RawDelegationData; - - #[test] - fn identity_and_owner_serialization() { - let identity: IdentityKey = "gateway".into(); - let owner = Addr::unchecked("bob"); - assert_eq!( - vec![0, 7, 103, 97, 116, 101, 119, 97, 121, 98, 111, 98], - identity_and_owner_to_bytes(&identity, &owner) - ); - } - - #[test] - fn identity_and_owner_deserialization() { - assert!(extract_identity_and_owner(vec![]).is_err()); - assert!(extract_identity_and_owner(vec![0]).is_err()); - let (owner, identity) = extract_identity_and_owner(vec![ - 0, 7, 109, 105, 120, 110, 111, 100, 101, 97, 108, 105, 99, 101, - ]) - .unwrap(); - assert_eq!(owner, "alice"); - assert_eq!(identity, "mixnode"); - } - - #[test] - fn delegations_iterator() { - let mut deps = test_helpers::init_contract(); - let node_identity: IdentityKey = "foo".into(); - - store_n_mix_delegations( - 2 * OLD_DELEGATIONS_CHUNK_SIZE as u32, - &mut deps.storage, - &node_identity, - ); - let mix_bucket = - mixnodes_storage::all_mix_delegations_read::(&deps.storage); - let mut delegations = Delegations::new(mix_bucket); - assert!(delegations.curr_delegations.is_empty()); - assert_eq!(delegations.curr_index, OLD_DELEGATIONS_CHUNK_SIZE); - delegations.next().unwrap(); - assert_eq!( - delegations.curr_delegations.len(), - OLD_DELEGATIONS_CHUNK_SIZE - ); - assert_eq!(delegations.curr_index, 1); - for _ in 0..OLD_DELEGATIONS_CHUNK_SIZE { - delegations.next().unwrap(); - } - assert_eq!( - delegations.curr_delegations.len(), - OLD_DELEGATIONS_CHUNK_SIZE - ); - assert_eq!(delegations.curr_index, 1); - for _ in 0..OLD_DELEGATIONS_CHUNK_SIZE - 1 { - delegations.next().unwrap(); - } - assert!(delegations.next().is_none()); - } - - #[test] - fn all_mix_delegations() { - let mut deps = mock_dependencies(&[]); - let node_identity1: IdentityKey = "foo1".into(); - let delegation_owner1 = Addr::unchecked("bar1"); - let node_identity2: IdentityKey = "foo2".into(); - let delegation_owner2 = Addr::unchecked("bar2"); - let raw_delegation = RawDelegationData::new(1000u128.into(), 42); - let mut start_after = None; - - mixnodes_storage::mix_delegations(&mut deps.storage, &node_identity1) - .save(delegation_owner1.as_bytes(), &raw_delegation) - .unwrap(); - - let bucket = mixnodes_storage::all_mix_delegations_read::(&deps.storage); - let response = - get_all_delegations_paged::(&bucket, &start_after, 10).unwrap(); - start_after = response.start_next_after; - let delegations = response.delegations; - assert_eq!(delegations.len(), 1); - assert_eq!( - delegations[0], - UnpackedDelegation::new( - delegation_owner1.clone(), - node_identity1.clone(), - raw_delegation.clone() - ) - ); - - mixnodes_storage::mix_delegations(&mut deps.storage, &node_identity2) - .save(delegation_owner2.as_bytes(), &raw_delegation) - .unwrap(); - - let bucket = mixnodes_storage::all_mix_delegations_read::(&deps.storage); - let response = - get_all_delegations_paged::(&bucket, &start_after, 10).unwrap(); - start_after = response.start_next_after; - let delegations = response.delegations; - assert_eq!(delegations.len(), 2); - assert_eq!( - delegations[1], - UnpackedDelegation::new( - delegation_owner2.clone(), - node_identity2.clone(), - raw_delegation.clone() - ) - ); - - mixnodes_storage::mix_delegations(&mut deps.storage, &node_identity1) - .remove(delegation_owner1.as_bytes()); - - let bucket = mixnodes_storage::all_mix_delegations_read::(&deps.storage); - let response = - get_all_delegations_paged::(&bucket, &start_after, 10).unwrap(); - let delegations = response.delegations; - assert_eq!(delegations.len(), 1); - assert_eq!( - delegations[0], - UnpackedDelegation::new(delegation_owner2, node_identity2, raw_delegation.clone()), - ); - } -} diff --git a/contracts/mixnet/src/mixnodes/layer_queries.rs b/contracts/mixnet/src/mixnodes/layer_queries.rs index a522033ba1..2ce21648f3 100644 --- a/contracts/mixnet/src/mixnodes/layer_queries.rs +++ b/contracts/mixnet/src/mixnodes/layer_queries.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::mixnet_contract_settings::storage as mixnet_params_storage; -use cosmwasm_std::Deps; +use cosmwasm_std::{Deps, StdResult}; use mixnet_contract::LayerDistribution; -pub(crate) fn query_layer_distribution(deps: Deps) -> LayerDistribution { - mixnet_params_storage::read_layer_distribution(deps.storage) +pub(crate) fn query_layer_distribution(deps: Deps) -> StdResult { + mixnet_params_storage::LAYERS.load(deps.storage) } diff --git a/contracts/mixnet/src/mixnodes/mod.rs b/contracts/mixnet/src/mixnodes/mod.rs index 887259c899..8f80d5b408 100644 --- a/contracts/mixnet/src/mixnodes/mod.rs +++ b/contracts/mixnet/src/mixnodes/mod.rs @@ -2,9 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 pub mod bonding_queries; -pub mod bonding_transactions; -pub mod delegation_helpers; -pub mod delegation_queries; -pub mod delegation_transactions; pub mod layer_queries; pub mod storage; +pub mod transactions; diff --git a/contracts/mixnet/src/mixnodes/storage.rs b/contracts/mixnet/src/mixnodes/storage.rs index a11843591d..e5c1fa454a 100644 --- a/contracts/mixnet/src/mixnodes/storage.rs +++ b/contracts/mixnet/src/mixnodes/storage.rs @@ -3,32 +3,46 @@ use config::defaults::DENOM; use cosmwasm_std::{StdResult, Storage, Uint128}; -use cosmwasm_storage::{bucket, bucket_read, Bucket, ReadonlyBucket}; -use mixnet_contract::{ - Addr, Coin, IdentityKey, IdentityKeyRef, Layer, MixNode, MixNodeBond, RawDelegationData, - RewardingStatus, -}; -use serde::de::DeserializeOwned; +use cw_storage_plus::{Index, IndexList, IndexedMap, Map, UniqueIndex}; +use mixnet_contract::{Addr, Coin, IdentityKeyRef, Layer, MixNode, MixNodeBond}; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; // storage prefixes -const PREFIX_MIXNODES: &[u8] = b"mn"; -const PREFIX_MIXNODES_OWNERS: &[u8] = b"mo"; -const PREFIX_MIX_DELEGATION: &[u8] = b"md"; -const PREFIX_REVERSE_MIX_DELEGATION: &[u8] = b"dm"; -pub const PREFIX_REWARDED_MIXNODES: &[u8] = b"rm"; +const TOTAL_DELEGATION_NAMESPACE: &str = "td"; +const MIXNODES_PK_NAMESPACE: &str = "mn"; +const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno"; // paged retrieval limits for all queries and transactions -// currently the maximum limit before running into memory issue is somewhere between 1150 and 1200 -pub(crate) const DELEGATION_PAGE_MAX_LIMIT: u32 = 500; -pub(crate) const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 250; pub(crate) const BOND_PAGE_MAX_LIMIT: u32 = 75; pub(crate) const BOND_PAGE_DEFAULT_LIMIT: u32 = 50; -const PREFIX_TOTAL_DELEGATION: &[u8] = b"td"; +pub(crate) const TOTAL_DELEGATION: Map = + Map::new(TOTAL_DELEGATION_NAMESPACE); -#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub(crate) struct MixnodeBondIndex<'a> { + pub(crate) owner: UniqueIndex<'a, Addr, StoredMixnodeBond>, +} + +// IndexList is just boilerplate code for fetching a struct's indexes +// note that from my understanding this will be converted into a macro at some point in the future +impl<'a> IndexList for MixnodeBondIndex<'a> { + fn get_indexes(&'_ self) -> Box> + '_> { + let v: Vec<&dyn Index> = vec![&self.owner]; + Box::new(v.into_iter()) + } +} + +// mixnodes() is the storage access function. +pub(crate) fn mixnodes<'a>( +) -> IndexedMap<'a, IdentityKeyRef<'a>, StoredMixnodeBond, MixnodeBondIndex<'a>> { + let indexes = MixnodeBondIndex { + owner: UniqueIndex::new(|d| d.owner.clone(), MIXNODES_OWNER_IDX_NAMESPACE), + }; + IndexedMap::new(MIXNODES_PK_NAMESPACE, indexes) +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub(crate) struct StoredMixnodeBond { pub bond_amount: Coin, pub owner: Addr, @@ -36,6 +50,7 @@ pub(crate) struct StoredMixnodeBond { pub block_height: u64, pub mix_node: MixNode, pub profit_margin_percent: Option, + pub proxy: Option, } impl StoredMixnodeBond { @@ -46,6 +61,7 @@ impl StoredMixnodeBond { block_height: u64, mix_node: MixNode, profit_margin_percent: Option, + proxy: Option, ) -> Self { StoredMixnodeBond { bond_amount, @@ -54,6 +70,7 @@ impl StoredMixnodeBond { block_height, mix_node, profit_margin_percent, + proxy, } } @@ -69,6 +86,7 @@ impl StoredMixnodeBond { block_height: self.block_height, mix_node: self.mix_node, profit_margin_percent: self.profit_margin_percent, + proxy: self.proxy, } } @@ -91,72 +109,15 @@ impl Display for StoredMixnodeBond { } } -// Mixnode-related stuff - -pub(crate) fn mixnodes(storage: &mut dyn Storage) -> Bucket { - bucket(storage, PREFIX_MIXNODES) -} - -pub(crate) fn mixnodes_read(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, PREFIX_MIXNODES) -} - -// owner address -> node identity -pub fn mixnodes_owners(storage: &mut dyn Storage) -> Bucket { - bucket(storage, PREFIX_MIXNODES_OWNERS) -} - -pub fn mixnodes_owners_read(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, PREFIX_MIXNODES_OWNERS) -} - -pub fn total_delegation(storage: &mut dyn Storage) -> Bucket { - bucket(storage, PREFIX_TOTAL_DELEGATION) -} - -pub fn total_delegation_read(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, PREFIX_TOTAL_DELEGATION) -} - -// we want to treat this bucket as a set so we don't really care about what type of data is being stored. -// I went with u8 as after serialization it takes only a single byte of space, while if a `()` was used, -// it would have taken 4 bytes (representation of 'null') -pub(crate) fn rewarded_mixnodes( - storage: &mut dyn Storage, - rewarding_interval_nonce: u32, -) -> Bucket { - Bucket::multilevel( - storage, - &[ - rewarding_interval_nonce.to_be_bytes().as_ref(), - PREFIX_REWARDED_MIXNODES, - ], - ) -} - -pub(crate) fn rewarded_mixnodes_read( - storage: &dyn Storage, - rewarding_interval_nonce: u32, -) -> ReadonlyBucket { - ReadonlyBucket::multilevel( - storage, - &[ - rewarding_interval_nonce.to_be_bytes().as_ref(), - PREFIX_REWARDED_MIXNODES, - ], - ) -} - pub(crate) fn read_mixnode_bond( storage: &dyn Storage, mix_identity: IdentityKeyRef, ) -> StdResult> { - let stored_bond = mixnodes_read(storage).may_load(mix_identity.as_bytes())?; + let stored_bond = mixnodes().may_load(storage, mix_identity)?; match stored_bond { None => Ok(None), Some(stored_bond) => { - let total_delegation = - total_delegation_read(storage).may_load(mix_identity.as_bytes())?; + let total_delegation = TOTAL_DELEGATION.may_load(storage, mix_identity)?; Ok(Some(MixNodeBond { bond_amount: stored_bond.bond_amount, total_delegation: Coin { @@ -168,68 +129,34 @@ pub(crate) fn read_mixnode_bond( block_height: stored_bond.block_height, mix_node: stored_bond.mix_node, profit_margin_percent: stored_bond.profit_margin_percent, + proxy: stored_bond.proxy, })) } } } -// delegation related -pub fn all_mix_delegations_read(storage: &dyn Storage) -> ReadonlyBucket -where - T: Serialize + DeserializeOwned, -{ - bucket_read(storage, PREFIX_MIX_DELEGATION) -} - -pub fn mix_delegations<'a>( - storage: &'a mut dyn Storage, - mix_identity: IdentityKeyRef, -) -> Bucket<'a, RawDelegationData> { - Bucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) -} - -pub fn mix_delegations_read<'a>( - storage: &'a dyn Storage, - mix_identity: IdentityKeyRef, -) -> ReadonlyBucket<'a, RawDelegationData> { - ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) -} - -// TODO: note for JS when doing a deep review for the contract. Don't store it as (), instead do it as u8 -pub fn reverse_mix_delegations<'a>(storage: &'a mut dyn Storage, owner: &Addr) -> Bucket<'a, ()> { - Bucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()]) -} - -pub fn reverse_mix_delegations_read<'a>( - storage: &'a dyn Storage, - owner: &Addr, -) -> ReadonlyBucket<'a, ()> { - ReadonlyBucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()]) -} - #[cfg(test)] mod tests { use super::super::storage; use super::*; - use crate::mixnodes::bonding_transactions::try_add_mixnode; + use crate::mixnodes::transactions::try_add_mixnode; use crate::support::tests::test_helpers; use config::defaults::DENOM; - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockStorage}; + use cosmwasm_std::testing::{mock_env, mock_info, MockStorage}; use cosmwasm_std::{coin, Addr, Uint128}; use mixnet_contract::IdentityKey; use mixnet_contract::MixNode; - use mixnet_contract::RawDelegationData; #[test] fn mixnode_single_read_retrieval() { let mut storage = MockStorage::new(); - let bond1 = test_helpers::stored_mixnode_bond_fixture(); - let bond2 = test_helpers::stored_mixnode_bond_fixture(); - mixnodes(&mut storage).save(b"bond1", &bond1).unwrap(); - mixnodes(&mut storage).save(b"bond2", &bond2).unwrap(); + let bond1 = test_helpers::stored_mixnode_bond_fixture("owner1"); + let bond2 = test_helpers::stored_mixnode_bond_fixture("owner2"); + mixnodes().save(&mut storage, "bond1", &bond1).unwrap(); + mixnodes().save(&mut storage, "bond2", &bond2).unwrap(); - let res1 = storage::mixnodes_read(&storage).load(b"bond1").unwrap(); - let res2 = storage::mixnodes_read(&storage).load(b"bond2").unwrap(); + let res1 = mixnodes().load(&storage, "bond1").unwrap(); + let res2 = mixnodes().load(&storage, "bond2").unwrap(); assert_eq!(bond1, res1); assert_eq!(bond2, res2); } @@ -241,7 +168,7 @@ mod tests { let node_identity: IdentityKey = "nodeidentity".into(); // produces a None if target mixnode doesn't exist - let res = storage::read_mixnode_bond(deps.as_ref().storage, node_owner.as_str()).unwrap(); + let res = storage::read_mixnode_bond(deps.as_ref().storage, &node_identity).unwrap(); assert!(res.is_none()); // returns appropriate value otherwise @@ -256,7 +183,7 @@ mod tests { try_add_mixnode(deps.as_mut(), mock_env(), info, mixnode).unwrap(); assert_eq!( - Uint128(bond_value), + Uint128::new(bond_value), storage::read_mixnode_bond(deps.as_ref().storage, node_identity.as_str()) .unwrap() .unwrap() @@ -264,106 +191,4 @@ mod tests { .amount ); } - - #[test] - fn all_mixnode_delegations_read_retrieval() { - let mut deps = mock_dependencies(&[]); - let node_identity1: IdentityKey = "foo1".into(); - let delegation_owner1 = Addr::unchecked("bar1"); - let node_identity2: IdentityKey = "foo2".into(); - let delegation_owner2 = Addr::unchecked("bar2"); - let raw_delegation1 = RawDelegationData::new(1u128.into(), 1000); - let raw_delegation2 = RawDelegationData::new(2u128.into(), 2000); - - storage::mix_delegations(&mut deps.storage, &node_identity1) - .save(delegation_owner1.as_bytes(), &raw_delegation1) - .unwrap(); - storage::mix_delegations(&mut deps.storage, &node_identity2) - .save(delegation_owner2.as_bytes(), &raw_delegation2) - .unwrap(); - - let res1 = storage::all_mix_delegations_read::(&deps.storage) - .load(&*test_helpers::identity_and_owner_to_bytes( - &node_identity1, - &delegation_owner1, - )) - .unwrap(); - let res2 = storage::all_mix_delegations_read::(&deps.storage) - .load(&*test_helpers::identity_and_owner_to_bytes( - &node_identity2, - &delegation_owner2, - )) - .unwrap(); - assert_eq!(raw_delegation1, res1); - assert_eq!(raw_delegation2, res2); - } - - #[cfg(test)] - mod reverse_mix_delegations { - use super::*; - use crate::support::tests::test_helpers; - - #[test] - fn reverse_mix_delegation_exists() { - let mut deps = test_helpers::init_contract(); - let node_identity: IdentityKey = "foo".into(); - let delegation_owner = Addr::unchecked("bar"); - - storage::reverse_mix_delegations(&mut deps.storage, &delegation_owner) - .save(node_identity.as_bytes(), &()) - .unwrap(); - - assert!(storage::reverse_mix_delegations_read( - deps.as_ref().storage, - &delegation_owner - ) - .may_load(node_identity.as_bytes()) - .unwrap() - .is_some(),); - } - - #[test] - fn reverse_mix_delegation_returns_none_if_delegation_doesnt_exist() { - let mut deps = test_helpers::init_contract(); - - let node_identity1: IdentityKey = "foo1".into(); - let node_identity2: IdentityKey = "foo2".into(); - let delegation_owner1 = Addr::unchecked("bar"); - let delegation_owner2 = Addr::unchecked("bar2"); - - assert!(storage::reverse_mix_delegations_read( - deps.as_ref().storage, - &delegation_owner1 - ) - .may_load(node_identity1.as_bytes()) - .unwrap() - .is_none()); - - // add delegation for a different node - storage::reverse_mix_delegations(&mut deps.storage, &delegation_owner1) - .save(node_identity2.as_bytes(), &()) - .unwrap(); - - assert!(storage::reverse_mix_delegations_read( - deps.as_ref().storage, - &delegation_owner1 - ) - .may_load(node_identity1.as_bytes()) - .unwrap() - .is_none()); - - // add delegation from a different owner - storage::reverse_mix_delegations(&mut deps.storage, &delegation_owner2) - .save(node_identity1.as_bytes(), &()) - .unwrap(); - - assert!(storage::reverse_mix_delegations_read( - deps.as_ref().storage, - &delegation_owner1 - ) - .may_load(node_identity1.as_bytes()) - .unwrap() - .is_none()); - } - } } diff --git a/contracts/mixnet/src/mixnodes/bonding_transactions.rs b/contracts/mixnet/src/mixnodes/transactions.rs similarity index 77% rename from contracts/mixnet/src/mixnodes/bonding_transactions.rs rename to contracts/mixnet/src/mixnodes/transactions.rs index 691ea0973b..67b3ba2c65 100644 --- a/contracts/mixnet/src/mixnodes/bonding_transactions.rs +++ b/contracts/mixnet/src/mixnodes/transactions.rs @@ -3,118 +3,174 @@ use super::storage; use crate::error::ContractError; -use crate::gateways::storage as gateways_storage; use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::mixnodes::layer_queries::query_layer_distribution; use crate::mixnodes::storage::StoredMixnodeBond; +use crate::support::helpers::ensure_no_existing_bond; use config::defaults::DENOM; -use cosmwasm_std::{attr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128}; -use mixnet_contract::MixNode; +use cosmwasm_std::{ + coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, StdError, + Uint128, +}; +use mixnet_contract::{IdentityKey, MixNode}; +use vesting_contract::messages::ExecuteMsg as VestingContractExecuteMsg; -pub(crate) fn try_add_mixnode( +pub fn try_add_mixnode( deps: DepsMut, env: Env, info: MessageInfo, mix_node: MixNode, ) -> Result { - let sender_bytes = info.sender.as_bytes(); + _try_add_mixnode( + deps, + env, + mix_node, + info.funds[0].clone(), + info.sender.as_str(), + None, + ) +} - // if the client has an active bonded gateway, don't allow mixnode bonding - if gateways_storage::gateways_owners_read(deps.storage) - .may_load(sender_bytes)? - .is_some() - { - return Err(ContractError::AlreadyOwnsGateway); - } +pub fn try_add_mixnode_on_behalf( + deps: DepsMut, + env: Env, + info: MessageInfo, + mix_node: MixNode, + owner: String, +) -> Result { + let proxy = info.sender.to_owned(); + _try_add_mixnode( + deps, + env, + mix_node, + info.funds[0].clone(), + &owner, + Some(proxy), + ) +} - // if the client has an active bonded mixnode, regardless of its identity, don't allow bonding - if storage::mixnodes_owners_read(deps.storage) - .may_load(sender_bytes)? - .is_some() - { - return Err(ContractError::AlreadyOwnsMixnode); - } +fn _try_add_mixnode( + deps: DepsMut, + env: Env, + mix_node: MixNode, + bond_amount: Coin, + owner: &str, + proxy: Option, +) -> Result { + let owner = deps.api.addr_validate(owner)?; + // if the client has an active bonded mixnode or gateway, don't allow bonding + ensure_no_existing_bond(deps.storage, &owner)?; // check if somebody else has already bonded a mixnode with this identity if let Some(existing_bond) = - storage::mixnodes_read(deps.storage).may_load(mix_node.identity_key.as_bytes())? + storage::mixnodes().may_load(deps.storage, &mix_node.identity_key)? { - if existing_bond.owner != info.sender { + if existing_bond.owner != owner { return Err(ContractError::DuplicateMixnode { owner: existing_bond.owner, }); } } - let minimum_bond = - mixnet_params_storage::read_contract_settings_params(deps.storage).minimum_mixnode_bond; - validate_mixnode_bond(&info.funds, minimum_bond)?; + let minimum_bond = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.storage)? + .params + .minimum_mixnode_bond; + let bond_amount = validate_mixnode_bond(&[bond_amount], minimum_bond)?; - let layer_distribution = query_layer_distribution(deps.as_ref()); + let layer_distribution = query_layer_distribution(deps.as_ref())?; let layer = layer_distribution.choose_with_fewest(); let stored_bond = StoredMixnodeBond::new( - info.funds[0].clone(), - info.sender.clone(), + bond_amount, + owner, layer, env.block.height, mix_node, None, + proxy, ); - let identity = stored_bond.identity(); - // technically we don't have to set the total_delegation bucket, but it makes things easier // in different places that we can guarantee that if node exists, so does the data behind the total delegation - storage::mixnodes(deps.storage).save(identity.as_bytes(), &stored_bond)?; - storage::mixnodes_owners(deps.storage).save(sender_bytes, identity)?; - storage::total_delegation(deps.storage).save(identity.as_bytes(), &Uint128::zero())?; + let identity = stored_bond.identity(); + storage::mixnodes().save(deps.storage, identity, &stored_bond)?; + storage::TOTAL_DELEGATION.save(deps.storage, identity, &Uint128::zero())?; mixnet_params_storage::increment_layer_count(deps.storage, stored_bond.layer)?; Ok(Response::new()) } -pub(crate) fn try_remove_mixnode( +pub fn try_remove_mixnode_on_behalf( deps: DepsMut, info: MessageInfo, + owner: String, ) -> Result { - let sender_bytes = info.sender.as_bytes(); + let proxy = info.sender; + _try_remove_mixnode(deps, &owner, Some(proxy)) +} - // try to find the identity of the sender's node - let mix_identity = match storage::mixnodes_owners_read(deps.storage).may_load(sender_bytes)? { - Some(identity) => identity, - None => return Err(ContractError::NoAssociatedMixNodeBond { owner: info.sender }), +pub fn try_remove_mixnode(deps: DepsMut, info: MessageInfo) -> Result { + _try_remove_mixnode(deps, info.sender.as_ref(), None) +} + +pub(crate) fn _try_remove_mixnode( + deps: DepsMut, + owner: &str, + proxy: Option, +) -> Result { + let owner = deps.api.addr_validate(owner)?; + // try to find the node of the sender + let (raw_identity, mixnode_bond) = match storage::mixnodes() + .idx + .owner + .item(deps.storage, owner.clone())? + { + Some(record) => (record.0, record.1), + None => return Err(ContractError::NoAssociatedMixNodeBond { owner }), }; - // get the bond, since we found associated identity, the node MUST exist - let mixnode_bond = storage::mixnodes_read(deps.storage).load(mix_identity.as_bytes())?; - - // send bonded funds back to the bond owner - let messages = vec![BankMsg::Send { - to_address: info.sender.as_str().to_owned(), - amount: vec![mixnode_bond.bond_amount()], + if proxy != mixnode_bond.proxy { + return Err(ContractError::ProxyMismatch { + existing: mixnode_bond + .proxy + .map_or_else(|| "None".to_string(), |a| a.as_str().to_string()), + incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()), + }); } - .into()]; - + // send bonded funds back to the bond owner + let return_tokens = BankMsg::Send { + to_address: proxy.as_ref().unwrap_or(&owner).to_string(), + amount: vec![mixnode_bond.bond_amount()], + }; + // Given that this Vec came directly from the storage and originated from a valid String before + // if this error is ever thrown it implies the entire storage got corrupted. + let mix_identity = IdentityKey::from_utf8(raw_identity) + .map_err(|_| StdError::parse_err("IdentityKey", "Storage got corrupted"))?; // remove the bond from the list of bonded mixnodes - storage::mixnodes(deps.storage).remove(mix_identity.as_bytes()); - // remove the node ownership - storage::mixnodes_owners(deps.storage).remove(sender_bytes); + storage::mixnodes().remove(deps.storage, &mix_identity)?; // decrement layer count mixnet_params_storage::decrement_layer_count(deps.storage, mixnode_bond.layer)?; - // log our actions - let attributes = vec![attr("action", "unbond"), attr("mixnode_bond", mixnode_bond)]; + let mut response = Response::new() + .add_message(return_tokens) + .add_attribute("action", "unbond") + .add_attribute("mixnode_bond", mixnode_bond.to_string()); - Ok(Response { - submessages: Vec::new(), - messages, - attributes, - data: None, - }) + if let Some(proxy) = &proxy { + let msg = VestingContractExecuteMsg::TrackUnbond { + owner: owner.as_str().to_string(), + amount: mixnode_bond.bond_amount, + }; + + let track_unbond_message = wasm_execute(proxy, &msg, coins(0, DENOM))?; + response = response.add_message(track_unbond_message); + } + + Ok(response) } -fn validate_mixnode_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), ContractError> { +fn validate_mixnode_bond(bond: &[Coin], minimum_bond: Uint128) -> Result { // check if anything was put as bond if bond.is_empty() { return Err(ContractError::NoBondFound); @@ -137,7 +193,7 @@ fn validate_mixnode_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), Con }); } - Ok(()) + Ok(bond[0].clone()) } #[cfg(test)] @@ -145,8 +201,8 @@ pub mod tests { use super::*; use crate::contract::{execute, query, INITIAL_MIXNODE_BOND}; use crate::error::ContractError; - use crate::mixnodes::bonding_transactions::try_add_mixnode; - use crate::mixnodes::bonding_transactions::validate_mixnode_bond; + use crate::mixnodes::transactions::try_add_mixnode; + use crate::mixnodes::transactions::validate_mixnode_bond; use crate::support::tests::test_helpers; use config::defaults::DENOM; use cosmwasm_std::attr; @@ -303,8 +359,10 @@ pub mod tests { }; // before the execution the node had no associated owner - assert!(storage::mixnodes_owners_read(deps.as_ref().storage) - .may_load("myAwesomeMixnode".as_bytes()) + assert!(storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("mix-owner")) .unwrap() .is_none()); @@ -314,9 +372,14 @@ pub mod tests { assert_eq!( "myAwesomeMixnode", - storage::mixnodes_owners_read(deps.as_ref().storage) - .load("mix-owner".as_bytes()) + storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("mix-owner")) .unwrap() + .unwrap() + .1 + .identity() ); } @@ -386,9 +449,7 @@ pub mod tests { assert_eq!( LayerDistribution::default(), - mixnet_params_storage::layer_distribution_read(&deps.storage) - .load() - .unwrap(), + mixnet_params_storage::LAYERS.load(&deps.storage).unwrap(), ); let info = mock_info("mix-owner", &test_helpers::good_mixnode_bond()); @@ -405,9 +466,7 @@ pub mod tests { layer1: 1, ..Default::default() }, - mixnet_params_storage::layer_distribution_read(&deps.storage) - .load() - .unwrap() + mixnet_params_storage::LAYERS.load(&deps.storage).unwrap() ); } @@ -481,19 +540,16 @@ pub mod tests { ]; // we should see a funds transfer from the contract back to fred - let expected_messages = vec![BankMsg::Send { + let expected_message = BankMsg::Send { to_address: String::from(info.sender), amount: test_helpers::good_mixnode_bond(), - } - .into()]; + }; // run the executor and check that we got back the correct results - let expected = Response { - submessages: Vec::new(), - messages: expected_messages, - attributes: expected_attributes, - data: None, - }; + let expected = Response::new() + .add_attributes(expected_attributes) + .add_message(expected_message); + assert_eq!(remove_fred, expected); // only 1 node now exists, owned by bob: @@ -517,9 +573,14 @@ pub mod tests { execute(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!( "myAwesomeMixnode", - storage::mixnodes_owners_read(deps.as_ref().storage) - .load("mix-owner".as_bytes()) + storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("mix-owner")) .unwrap() + .unwrap() + .1 + .identity() ); let info = mock_info("mix-owner", &[]); @@ -527,8 +588,10 @@ pub mod tests { assert!(execute(deps.as_mut(), mock_env(), info, msg).is_ok()); - assert!(storage::mixnodes_owners_read(deps.as_ref().storage) - .may_load("mix-owner".as_bytes()) + assert!(storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("mix-owner")) .unwrap() .is_none()); @@ -544,9 +607,14 @@ pub mod tests { assert!(execute(deps.as_mut(), mock_env(), info, msg).is_ok()); assert_eq!( "myAwesomeMixnode", - storage::mixnodes_owners_read(deps.as_ref().storage) - .load("mix-owner".as_bytes()) + storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("mix-owner")) .unwrap() + .unwrap() + .1 + .identity() ); } @@ -558,7 +626,7 @@ pub mod tests { // you must send at least 100 coins... let mut bond = test_helpers::good_mixnode_bond(); - bond[0].amount = INITIAL_MIXNODE_BOND.checked_sub(Uint128(1)).unwrap(); + bond[0].amount = INITIAL_MIXNODE_BOND.checked_sub(Uint128::new(1)).unwrap(); let result = validate_mixnode_bond(&bond, INITIAL_MIXNODE_BOND); assert_eq!( result, @@ -570,7 +638,7 @@ pub mod tests { // more than that is still fine let mut bond = test_helpers::good_mixnode_bond(); - bond[0].amount = INITIAL_MIXNODE_BOND + Uint128(1); + bond[0].amount = INITIAL_MIXNODE_BOND + Uint128::new(1); let result = validate_mixnode_bond(&bond, INITIAL_MIXNODE_BOND); assert!(result.is_ok()); diff --git a/contracts/mixnet/src/query_support.rs b/contracts/mixnet/src/query_support.rs deleted file mode 100644 index 6d6a1b9ae7..0000000000 --- a/contracts/mixnet/src/query_support.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -/// Adds a 0 byte to terminate the `start_after` value given. This allows CosmWasm -/// to get the succeeding key as the start of the next page. -// S works for both `String` and `Addr` and that's what we wanted -pub fn calculate_start_value>(start_after: Option) -> Option> { - start_after.as_ref().map(|identity| { - identity - .as_ref() - .as_bytes() - .iter() - .cloned() - .chain(std::iter::once(0)) - .collect() - }) -} diff --git a/contracts/mixnet/src/rewards/queries.rs b/contracts/mixnet/src/rewards/queries.rs index c6461f475b..5b6d6ea93f 100644 --- a/contracts/mixnet/src/rewards/queries.rs +++ b/contracts/mixnet/src/rewards/queries.rs @@ -2,16 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use super::storage; -use crate::mixnodes::storage as mixnodes_storage; use cosmwasm_std::Uint128; use cosmwasm_std::{Deps, StdResult}; use mixnet_contract::{IdentityKey, MixnodeRewardingStatusResponse}; -pub(crate) fn query_reward_pool(deps: Deps) -> Uint128 { - storage::reward_pool_value(deps.storage) +pub(crate) fn query_reward_pool(deps: Deps) -> StdResult { + storage::REWARD_POOL.load(deps.storage) } -pub(crate) fn query_circulating_supply(deps: Deps) -> Uint128 { +pub(crate) fn query_circulating_supply(deps: Deps) -> StdResult { storage::circulating_supply(deps.storage) } @@ -20,8 +19,10 @@ pub(crate) fn query_rewarding_status( mix_identity: IdentityKey, rewarding_interval_nonce: u32, ) -> StdResult { - let status = mixnodes_storage::rewarded_mixnodes_read(deps.storage, rewarding_interval_nonce) - .may_load(mix_identity.as_bytes())?; + let status = storage::REWARDING_STATUS.may_load( + deps.storage, + (rewarding_interval_nonce.into(), mix_identity), + )?; Ok(MixnodeRewardingStatusResponse { status }) } @@ -38,8 +39,8 @@ pub(crate) mod tests { mod querying_for_rewarding_status { use super::storage; use super::*; - use crate::mixnodes::bonding_transactions::try_add_mixnode; - use crate::mixnodes::delegation_transactions::try_delegate_to_mixnode; + use crate::delegations::transactions::try_delegate_to_mixnode; + use crate::mixnodes::transactions::try_add_mixnode; use crate::rewards::transactions::{ try_begin_mixnode_rewarding, try_finish_mixnode_rewarding, try_reward_mixnode_v2, try_reward_next_mixnode_delegators_v2, @@ -52,10 +53,9 @@ pub(crate) mod tests { fn returns_empty_status_for_unrewarded_nodes() { let mut deps = test_helpers::init_contract(); let env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; let node_identity = @@ -93,10 +93,9 @@ pub(crate) mod tests { // with single page let mut deps = test_helpers::init_contract(); let mut env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; let node_identity = "bobsnode".to_string(); @@ -216,10 +215,9 @@ pub(crate) mod tests { fn returns_pending_next_delegator_page_status_when_there_are_more_delegators_to_reward() { let mut deps = test_helpers::init_contract(); let mut env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; let node_identity = "bobsnode".to_string(); diff --git a/contracts/mixnet/src/rewards/storage.rs b/contracts/mixnet/src/rewards/storage.rs index 18e959f9c0..c38ab4f166 100644 --- a/contracts/mixnet/src/rewards/storage.rs +++ b/contracts/mixnet/src/rewards/storage.rs @@ -1,13 +1,14 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::contract::INITIAL_REWARD_POOL; use crate::error::ContractError; use config::defaults::TOTAL_SUPPLY; -use cosmwasm_std::{Storage, Uint128}; -use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton}; +use cosmwasm_std::{StdResult, Storage, Uint128}; +use cw_storage_plus::{Item, Map, U32Key}; +use mixnet_contract::{IdentityKey, RewardingStatus}; -const REWARD_POOL_PREFIX: &[u8] = b"pool"; +pub(crate) const REWARD_POOL: Item = Item::new("pool"); +pub(crate) const REWARDING_STATUS: Map<(U32Key, IdentityKey), RewardingStatus> = Map::new("rm"); // approximately 1 day (assuming 5s per block) pub(crate) const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 17280; @@ -15,49 +16,34 @@ pub(crate) const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 17280; // approximately 30min (assuming 5s per block) pub(crate) const MAX_REWARDING_DURATION_IN_BLOCKS: u64 = 360; -fn reward_pool(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, REWARD_POOL_PREFIX) -} - -pub fn mut_reward_pool(storage: &mut dyn Storage) -> Singleton { - singleton(storage, REWARD_POOL_PREFIX) -} - -pub fn reward_pool_value(storage: &dyn Storage) -> Uint128 { - match reward_pool(storage).load() { - Ok(value) => value, - Err(_e) => Uint128(INITIAL_REWARD_POOL), - } -} - #[allow(dead_code)] pub fn incr_reward_pool( amount: Uint128, storage: &mut dyn Storage, ) -> Result { - let stake = reward_pool_value(storage).saturating_add(amount); - mut_reward_pool(storage).save(&stake)?; - Ok(stake) + REWARD_POOL.update::<_, ContractError>(storage, |mut current_pool| { + current_pool += amount; + Ok(current_pool) + }) } pub fn decr_reward_pool( amount: Uint128, storage: &mut dyn Storage, ) -> Result { - let stake = match reward_pool_value(storage).checked_sub(amount) { - Ok(stake) => stake, - Err(_e) => { - return Err(ContractError::OutOfFunds { + REWARD_POOL.update(storage, |current_pool| { + let stake = current_pool + .checked_sub(amount) + .map_err(|_| ContractError::OutOfFunds { to_remove: amount.u128(), - reward_pool: reward_pool_value(storage).u128(), - }) - } - }; - mut_reward_pool(storage).save(&stake)?; - Ok(stake) + reward_pool: current_pool.u128(), + })?; + + Ok(stake) + }) } -pub fn circulating_supply(storage: &dyn Storage) -> Uint128 { - let reward_pool = reward_pool_value(storage).u128(); - Uint128(TOTAL_SUPPLY - reward_pool) +pub fn circulating_supply(storage: &dyn Storage) -> StdResult { + let reward_pool = REWARD_POOL.load(storage)?; + Ok(Uint128::new(TOTAL_SUPPLY) - reward_pool) } diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 3758ef8eea..c92d557de4 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -2,20 +2,22 @@ // SPDX-License-Identifier: Apache-2.0 use super::storage; +use crate::delegations::storage as delegations_storage; use crate::error::ContractError; use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::mixnodes::storage as mixnodes_storage; -use cosmwasm_std::{attr, DepsMut, Env, MessageInfo, Response, StdResult, Storage, Uint128}; +use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response, StdResult, Storage, Uint128}; +use cw_storage_plus::{Bound, PrimaryKey}; use mixnet_contract::mixnode::{DelegatorRewardParams, NodeRewardParams}; use mixnet_contract::{ - IdentityKey, IdentityKeyRef, PendingDelegatorRewarding, RewardingResult, RewardingStatus, + IdentityKey, PendingDelegatorRewarding, RewardingResult, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT, }; #[derive(Debug)] struct MixDelegationRewardingResult { total_rewarded: Uint128, - start_next: Option, + start_next: Option, } /// Checks whether under the current context, any rewarding-related functionalities can be called. @@ -34,7 +36,7 @@ fn verify_rewarding_state( info: MessageInfo, rewarding_interval_nonce: u32, ) -> Result<(), ContractError> { - let state = mixnet_params_storage::contract_settings_read(storage).load()?; + let state = mixnet_params_storage::CONTRACT_SETTINGS.load(storage)?; // check if this is executed by the permitted validator, if not reject the transaction if info.sender != state.rewarding_validator_address { @@ -70,7 +72,7 @@ pub(crate) fn try_begin_mixnode_rewarding( info: MessageInfo, rewarding_interval_nonce: u32, ) -> Result { - let mut state = mixnet_params_storage::contract_settings_read(deps.storage).load()?; + let mut state = mixnet_params_storage::CONTRACT_SETTINGS.load(deps.storage)?; // check if this is executed by the permitted validator, if not reject the transaction if info.sender != state.rewarding_validator_address { @@ -102,28 +104,30 @@ pub(crate) fn try_begin_mixnode_rewarding( state.latest_rewarding_interval_nonce = rewarding_interval_nonce; state.rewarding_in_progress = true; - mixnet_params_storage::contract_settings(deps.storage).save(&state)?; + mixnet_params_storage::CONTRACT_SETTINGS.save(deps.storage, &state)?; - let mut response = Response::new(); - response.add_attribute( + Ok(Response::new().add_attribute( "rewarding interval nonce", rewarding_interval_nonce.to_string(), - ); - Ok(response) + )) } +// TODO: change it to start_after fn reward_mix_delegators_v2( storage: &mut dyn Storage, - mix_identity: IdentityKeyRef, - start: Option, + mix_identity: IdentityKey, + start: Option, params: DelegatorRewardParams, ) -> StdResult { // TODO: some checks to make sure stuff is not TOO stale. let chunk_size = MIXNODE_DELEGATORS_PAGE_LIMIT; - let start_value = start.as_ref().map(|addr| addr.as_bytes()); - let mut delegations = mixnodes_storage::mix_delegations(storage, mix_identity); + // TODO: change it to exclusive bound for simpler logic and consistency + let start_value = + start.map(|start| Bound::Inclusive((mix_identity.clone(), start).joined_key())); + + let delegations = delegations_storage::delegations(); let mut total_rewarded = Uint128::zero(); let mut items = 0; @@ -145,16 +149,19 @@ fn reward_mix_delegators_v2( // Note: we can't just return last key of `chunk_size` with appended 0 byte as that // would not be a valid utf8 string for delegation in delegations - .range(start_value, None, cosmwasm_std::Order::Ascending) + .idx + .mixnode + .prefix(mix_identity) + .range(storage, start_value, None, cosmwasm_std::Order::Ascending) .take(chunk_size + 1) { items += 1; - let (delegator_address, mut delegation) = delegation?; + let (_pk, mut delegation) = delegation?; if items == chunk_size + 1 { // we shouldn't process this data, it's for the next call - start_next = Some(String::from_utf8(delegator_address)?); + start_next = Some(delegation.owner()); break; } else { // and for each of them increase the stake proportionally to the reward @@ -163,18 +170,19 @@ fn reward_mix_delegators_v2( if delegation.block_height + storage::MINIMUM_BLOCK_AGE_FOR_REWARDING <= params.node_reward_params().reward_blockstamp() { - let reward = params.determine_delegation_reward(delegation.amount); - delegation.amount += Uint128(reward); - total_rewarded += Uint128(reward); + let reward = params.determine_delegation_reward(delegation.amount.amount); + delegation.increment_amount(Uint128::new(reward), None); + total_rewarded += Uint128::new(reward); - rewarded_delegations.push((delegator_address, delegation)); + rewarded_delegations.push(delegation); } } } - // finally save all delegation data back into the bucket + // finally save all delegation data back into the storage for rewarded_delegation in rewarded_delegations { - delegations.save(&rewarded_delegation.0, &rewarded_delegation.1)?; + let storage_key = rewarded_delegation.storage_key().joined_key(); + delegations.save(storage, storage_key, &rewarded_delegation)?; } Ok(MixDelegationRewardingResult { @@ -191,9 +199,10 @@ pub(crate) fn try_reward_next_mixnode_delegators_v2( ) -> Result { verify_rewarding_state(deps.storage, info, rewarding_interval_nonce)?; - match mixnodes_storage::rewarded_mixnodes_read(deps.storage, rewarding_interval_nonce) - .may_load(mix_identity.as_bytes())? - { + match storage::REWARDING_STATUS.may_load( + deps.storage, + (rewarding_interval_nonce.into(), mix_identity.clone()), + )? { None => { // we haven't called 'regular' try_reward_mixnode, i.e. the operator itself // was not rewarded yet @@ -210,14 +219,15 @@ pub(crate) fn try_reward_next_mixnode_delegators_v2( Some(RewardingStatus::PendingNextDelegatorPage(next_page_info)) => { let delegation_rewarding_result = reward_mix_delegators_v2( deps.storage, - &mix_identity, + mix_identity.clone(), Some(next_page_info.next_start), next_page_info.rewarding_params, )?; // update the memoised total delegation field - mixnodes_storage::total_delegation(deps.storage).update::<_, ContractError>( - mix_identity.as_bytes(), + mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>( + deps.storage, + &mix_identity, |current_total| { // unwrap is fine as if the mixnode if this mixnode's delegators are getting rewarded // it means it MUST HAVE existed at some point in the past @@ -238,8 +248,9 @@ pub(crate) fn try_reward_next_mixnode_delegators_v2( if let Some(next_start) = delegation_rewarding_result.start_next { attributes.push(("more delegators to reward", "true".to_owned())); - mixnodes_storage::rewarded_mixnodes(deps.storage, rewarding_interval_nonce).save( - mix_identity.as_bytes(), + storage::REWARDING_STATUS.save( + deps.storage, + (rewarding_interval_nonce.into(), mix_identity), &RewardingStatus::PendingNextDelegatorPage(PendingDelegatorRewarding { running_results: rewarding_results, next_start, @@ -249,19 +260,14 @@ pub(crate) fn try_reward_next_mixnode_delegators_v2( } else { attributes.push(("more delegators to reward", "false".to_owned())); - mixnodes_storage::rewarded_mixnodes(deps.storage, rewarding_interval_nonce).save( - mix_identity.as_bytes(), + storage::REWARDING_STATUS.save( + deps.storage, + (rewarding_interval_nonce.into(), mix_identity), &RewardingStatus::Complete(rewarding_results), )?; } - let mut response = Response::new(); - // it looks kinda ugly now, but the API for this is vastly improved in cosmwasm 1.0 - for attribute in attributes { - response.add_attribute(attribute.0, attribute.1) - } - - Ok(response) + Ok(Response::new().add_attributes(attributes)) } } } @@ -281,9 +287,10 @@ pub(crate) fn try_reward_mixnode_v2( verify_rewarding_state(deps.storage, info, rewarding_interval_nonce)?; // check if the mixnode hasn't been rewarded in this rewarding interval already - match mixnodes_storage::rewarded_mixnodes_read(deps.storage, rewarding_interval_nonce) - .may_load(mix_identity.as_bytes())? - { + match storage::REWARDING_STATUS.may_load( + deps.storage, + (rewarding_interval_nonce.into(), mix_identity.clone()), + )? { None => (), Some(RewardingStatus::Complete(_)) => { return Err(ContractError::MixnodeAlreadyRewarded { @@ -303,12 +310,7 @@ pub(crate) fn try_reward_mixnode_v2( // check if the bond even exists let current_bond = match mixnodes_storage::read_mixnode_bond(deps.storage, &mix_identity)? { Some(bond) => bond, - None => { - return Ok(Response { - attributes: vec![attr("result", "bond not found")], - ..Default::default() - }); - } + None => return Ok(Response::new().add_attribute("result", "bond not found")), }; // in cosmwasm 1.0 all attributes have to be of type T: Into anyway @@ -328,21 +330,23 @@ pub(crate) fn try_reward_mixnode_v2( node_reward = operator_reward_result.reward().to_string(); // Omitting the price per packet function now, it follows that base operator reward is the node_reward - operator_reward = Uint128(current_bond.operator_reward(&node_reward_params)); + operator_reward = Uint128::new(current_bond.operator_reward(&node_reward_params)); let delegator_params = DelegatorRewardParams::new(¤t_bond, node_reward_params); let delegation_rewarding_result = - reward_mix_delegators_v2(deps.storage, &mix_identity, None, delegator_params)?; + reward_mix_delegators_v2(deps.storage, mix_identity.clone(), None, delegator_params)?; - mixnodes_storage::total_delegation(deps.storage).update::<_, ContractError>( - mix_identity.as_bytes(), + mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>( + deps.storage, + &mix_identity, |current_total| { // unwrap is fine as if the mixnode itself exists, so must this entry Ok(current_total.unwrap() + delegation_rewarding_result.total_rewarded) }, )?; - mixnodes_storage::mixnodes(deps.storage).update::<_, ContractError>( - mix_identity.as_bytes(), + mixnodes_storage::mixnodes().update::<_, ContractError>( + deps.storage, + &mix_identity, |current_bond| { // unwrap is fine because we just read the entry... let mut unwrapped = current_bond.unwrap(); @@ -366,8 +370,9 @@ pub(crate) fn try_reward_mixnode_v2( if let Some(next_start) = delegation_rewarding_result.start_next { more_delegators = true; - mixnodes_storage::rewarded_mixnodes(deps.storage, rewarding_interval_nonce).save( - mix_identity.as_bytes(), + storage::REWARDING_STATUS.save( + deps.storage, + (rewarding_interval_nonce.into(), mix_identity), &RewardingStatus::PendingNextDelegatorPage(PendingDelegatorRewarding { running_results: rewarding_results, next_start, @@ -375,37 +380,34 @@ pub(crate) fn try_reward_mixnode_v2( }), )?; } else { - mixnodes_storage::rewarded_mixnodes(deps.storage, rewarding_interval_nonce).save( - mix_identity.as_bytes(), + storage::REWARDING_STATUS.save( + deps.storage, + (rewarding_interval_nonce.into(), mix_identity), &RewardingStatus::Complete(rewarding_results), )?; } } else { // node is not eligible for rewarding, so we're done immediately - mixnodes_storage::rewarded_mixnodes(deps.storage, rewarding_interval_nonce).save( - mix_identity.as_bytes(), + storage::REWARDING_STATUS.save( + deps.storage, + (rewarding_interval_nonce.into(), mix_identity), &RewardingStatus::Complete(Default::default()), )?; } - Ok(Response { - submessages: vec![], - messages: vec![], - attributes: vec![ - attr("node reward", node_reward), - attr("operator reward", operator_reward), - attr("total delegation increase", total_delegation_increase), - attr("more delegators to reward", more_delegators), - ], - data: None, - }) + Ok(Response::new() + .add_attribute("node reward", node_reward) + .add_attribute("operator reward", operator_reward) + .add_attribute("total delegation increase", total_delegation_increase) + .add_attribute("more delegators to reward", more_delegators.to_string())) } + pub(crate) fn try_finish_mixnode_rewarding( deps: DepsMut, info: MessageInfo, rewarding_interval_nonce: u32, ) -> Result { - let mut state = mixnet_params_storage::contract_settings_read(deps.storage).load()?; + let mut state = mixnet_params_storage::CONTRACT_SETTINGS.load(deps.storage)?; // check if this is executed by the permitted validator, if not reject the transaction if info.sender != state.rewarding_validator_address { @@ -425,7 +427,7 @@ pub(crate) fn try_finish_mixnode_rewarding( } state.rewarding_in_progress = false; - mixnet_params_storage::contract_settings(deps.storage).save(&state)?; + mixnet_params_storage::CONTRACT_SETTINGS.save(deps.storage, &state)?; Ok(Response::new()) } @@ -434,12 +436,12 @@ pub(crate) fn try_finish_mixnode_rewarding( pub mod tests { use super::*; use crate::contract::DEFAULT_SYBIL_RESISTANCE_PERCENT; + use crate::delegations::transactions::try_delegate_to_mixnode; use crate::error::ContractError; use crate::mixnet_contract_settings::storage as mixnet_params_storage; - use crate::mixnodes::bonding_transactions::try_add_mixnode; - use crate::mixnodes::delegation_transactions::try_delegate_to_mixnode; use crate::mixnodes::storage as mixnodes_storage; use crate::mixnodes::storage::StoredMixnodeBond; + use crate::mixnodes::transactions::try_add_mixnode; use crate::rewards::transactions::{ try_begin_mixnode_rewarding, try_finish_mixnode_rewarding, try_reward_mixnode_v2, }; @@ -451,8 +453,8 @@ pub mod tests { use cosmwasm_std::{attr, Order}; use cosmwasm_std::{coin, Addr, Uint128}; use mixnet_contract::mixnode::NodeRewardParams; - use mixnet_contract::MixNode; - use mixnet_contract::{IdentityKey, Layer, RawDelegationData}; + use mixnet_contract::{Delegation, MixNode}; + use mixnet_contract::{IdentityKey, Layer}; #[cfg(test)] mod beginning_mixnode_rewarding { @@ -465,10 +467,9 @@ pub mod tests { fn can_only_be_called_by_specified_validator_address() { let mut deps = test_helpers::init_contract(); let env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; let res = try_begin_mixnode_rewarding( @@ -492,10 +493,9 @@ pub mod tests { fn cannot_be_called_if_rewarding_is_already_in_progress_with_little_day() { let mut deps = test_helpers::init_contract(); let env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; try_begin_mixnode_rewarding( @@ -519,10 +519,9 @@ pub mod tests { fn can_be_called_if_rewarding_is_in_progress_if_sufficient_number_of_blocks_elapsed() { let mut deps = test_helpers::init_contract(); let env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; try_begin_mixnode_rewarding( @@ -550,13 +549,12 @@ pub mod tests { fn provided_nonce_must_be_equal_the_current_plus_one() { let mut deps = test_helpers::init_contract(); let env = mock_env(); - let mut current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let mut current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); current_state.latest_rewarding_interval_nonce = 42; - mixnet_params_storage::contract_settings(deps.as_mut().storage) - .save(¤t_state) + mixnet_params_storage::CONTRACT_SETTINGS + .save(deps.as_mut().storage, ¤t_state) .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; @@ -616,8 +614,8 @@ pub mod tests { fn updates_contract_state() { let mut deps = test_helpers::init_contract(); let env = mock_env(); - let start_state = mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() + let start_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) .unwrap(); let rewarding_validator_address = start_state.rewarding_validator_address; @@ -629,8 +627,8 @@ pub mod tests { ) .unwrap(); - let new_state = mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() + let new_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) .unwrap(); assert!(new_state.rewarding_in_progress); assert_eq!( @@ -656,10 +654,9 @@ pub mod tests { fn can_only_be_called_by_specified_validator_address() { let mut deps = test_helpers::init_contract(); let env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; try_begin_mixnode_rewarding( @@ -688,10 +685,9 @@ pub mod tests { #[test] fn cannot_be_called_if_rewarding_is_not_in_progress() { let mut deps = test_helpers::init_contract(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; let res = try_finish_mixnode_rewarding( @@ -706,13 +702,12 @@ pub mod tests { fn provided_nonce_must_be_equal_the_current_one() { let mut deps = test_helpers::init_contract(); let env = mock_env(); - let mut current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let mut current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); current_state.latest_rewarding_interval_nonce = 42; - mixnet_params_storage::contract_settings(deps.as_mut().storage) - .save(¤t_state) + mixnet_params_storage::CONTRACT_SETTINGS + .save(deps.as_mut().storage, ¤t_state) .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; @@ -776,10 +771,9 @@ pub mod tests { fn updates_contract_state() { let mut deps = test_helpers::init_contract(); let env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; try_begin_mixnode_rewarding( @@ -797,8 +791,8 @@ pub mod tests { ) .unwrap(); - let new_state = mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() + let new_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) .unwrap(); assert!(!new_state.rewarding_in_progress); } @@ -808,8 +802,8 @@ pub mod tests { fn rewarding_mixnodes_outside_rewarding_period() { let mut deps = test_helpers::init_contract(); let env = mock_env(); - let current_state = mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; @@ -856,8 +850,8 @@ pub mod tests { fn rewarding_mixnodes_with_incorrect_rewarding_nonce() { let mut deps = test_helpers::init_contract(); let env = mock_env(); - let current_state = mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; @@ -925,8 +919,8 @@ pub mod tests { fn attempting_rewarding_mixnode_multiple_times_per_interval() { let mut deps = test_helpers::init_contract(); let env = mock_env(); - let current_state = mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; @@ -994,8 +988,8 @@ pub mod tests { fn rewarding_mixnode_blockstamp_based() { let mut deps = test_helpers::init_contract(); let mut env = mock_env(); - let current_state = mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; @@ -1014,22 +1008,34 @@ pub mod tests { ..mix_node_fixture() }, profit_margin_percent: Some(10), + proxy: None, }; - mixnodes_storage::mixnodes(deps.as_mut().storage) - .save(node_identity.as_bytes(), &mixnode_bond) + mixnodes_storage::mixnodes() + .save(deps.as_mut().storage, &node_identity, &mixnode_bond) .unwrap(); - mixnodes_storage::total_delegation(deps.as_mut().storage) - .save(node_identity.as_bytes(), &Uint128::new(initial_delegation)) + mixnodes_storage::TOTAL_DELEGATION + .save( + deps.as_mut().storage, + &node_identity, + &Uint128::new(initial_delegation), + ) .unwrap(); // delegation happens later, but not later enough env.block.height += storage::MINIMUM_BLOCK_AGE_FOR_REWARDING - 1; - mixnodes_storage::mix_delegations(&mut deps.storage, &node_identity) + delegations_storage::delegations() .save( - b"delegator", - &RawDelegationData::new(initial_delegation.into(), env.block.height), + deps.as_mut().storage, + (node_identity.clone(), "delegator").joined_key(), + &Delegation::new( + Addr::unchecked("delegator"), + node_identity.clone(), + coin(initial_delegation, DENOM), + env.block.height, + None, + ), ) .unwrap(); @@ -1048,14 +1054,14 @@ pub mod tests { assert_eq!( initial_bond, - test_helpers::read_mixnode_bond_amount(deps.as_ref().storage, node_identity.as_bytes()) + test_helpers::read_mixnode_bond_amount(deps.as_ref().storage, &node_identity) .unwrap() .u128() ); assert_eq!( initial_delegation, - mixnodes_storage::total_delegation_read(deps.as_ref().storage) - .load(node_identity.as_bytes()) + mixnodes_storage::TOTAL_DELEGATION + .load(deps.as_ref().storage, &node_identity) .unwrap() .u128() ); @@ -1063,7 +1069,10 @@ pub mod tests { assert_eq!(res.attributes[0], attr("node reward", "0")); assert_eq!(res.attributes[1], attr("operator reward", "0")); assert_eq!(res.attributes[2], attr("total delegation increase", "0")); - assert_eq!(res.attributes[3], attr("more delegators to reward", false)); + assert_eq!( + res.attributes[3], + attr("more delegators to reward", false.to_string()) + ); // reward can happen now, but only for bonded node env.block.height += 1; @@ -1082,15 +1091,15 @@ pub mod tests { try_finish_mixnode_rewarding(deps.as_mut(), info, 2).unwrap(); assert!( - test_helpers::read_mixnode_bond_amount(deps.as_ref().storage, node_identity.as_bytes()) + test_helpers::read_mixnode_bond_amount(deps.as_ref().storage, &node_identity) .unwrap() .u128() > initial_bond ); assert_eq!( initial_delegation, - mixnodes_storage::total_delegation_read(deps.as_ref().storage) - .load(node_identity.as_bytes()) + mixnodes_storage::TOTAL_DELEGATION + .load(deps.as_ref().storage, &node_identity) .unwrap() .u128() ); @@ -1098,13 +1107,16 @@ pub mod tests { assert_ne!(res.attributes[0], attr("node reward", "0")); assert_ne!(res.attributes[1], attr("operator reward", "0")); assert_eq!(res.attributes[2], attr("total delegation increase", "0")); - assert_eq!(res.attributes[3], attr("more delegators to reward", false)); + assert_eq!( + res.attributes[3], + attr("more delegators to reward", false.to_string()) + ); // reward happens now, both for node owner and delegators env.block.height += storage::MINIMUM_BLOCK_AGE_FOR_REWARDING - 1; let bond_before_rewarding = - test_helpers::read_mixnode_bond_amount(deps.as_ref().storage, node_identity.as_bytes()) + test_helpers::read_mixnode_bond_amount(deps.as_ref().storage, &node_identity) .unwrap() .u128(); @@ -1122,14 +1134,14 @@ pub mod tests { try_finish_mixnode_rewarding(deps.as_mut(), info, 3).unwrap(); assert!( - test_helpers::read_mixnode_bond_amount(deps.as_ref().storage, node_identity.as_bytes()) + test_helpers::read_mixnode_bond_amount(deps.as_ref().storage, &node_identity) .unwrap() .u128() > bond_before_rewarding ); assert!( - mixnodes_storage::total_delegation_read(deps.as_ref().storage) - .load(node_identity.as_bytes()) + mixnodes_storage::TOTAL_DELEGATION + .load(deps.as_ref().storage, &node_identity) .unwrap() .u128() > initial_delegation @@ -1138,7 +1150,10 @@ pub mod tests { assert_ne!(res.attributes[0], attr("node reward", "0")); assert_ne!(res.attributes[1], attr("operator reward", "0")); assert_ne!(res.attributes[2], attr("total delegation increase", "0")); - assert_eq!(res.attributes[3], attr("more delegators to reward", false)); + assert_eq!( + res.attributes[3], + attr("more delegators to reward", false.to_string()) + ); } #[test] @@ -1149,17 +1164,17 @@ pub mod tests { let mut deps = test_helpers::init_contract(); let mut env = mock_env(); - let current_state = mixnet_params_storage::contract_settings_read(deps.as_ref().storage) - .load() + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_ref().storage) .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; let period_reward_pool = (INITIAL_REWARD_POOL / 100) * EPOCH_REWARD_PERCENT as u128; assert_eq!(period_reward_pool, 5_000_000_000_000); let k = 200; // Imagining our active set size is 200 - let circulating_supply = storage::circulating_supply(&deps.storage).u128(); + let circulating_supply = storage::circulating_supply(&deps.storage).unwrap().u128(); assert_eq!(circulating_supply, 750_000_000_000_000u128); // mut_reward_pool(deps.as_mut().storage) - // .save(&Uint128(period_reward_pool)) + // .save(&Uint128::new(period_reward_pool)) // .unwrap(); try_add_mixnode( @@ -1169,7 +1184,7 @@ pub mod tests { "alice", &[Coin { denom: DENOM.to_string(), - amount: Uint128(10_000_000_000), + amount: Uint128::new(10_000_000_000), }], ), MixNode { @@ -1182,7 +1197,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info("d1", &[coin(8000_000000, DENOM)]), + mock_info("alice_d1", &[coin(8000_000000, DENOM)]), "alice".to_string(), ) .unwrap(); @@ -1190,7 +1205,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info("d2", &[coin(2000_000000, DENOM)]), + mock_info("alice_d2", &[coin(2000_000000, DENOM)]), "alice".to_string(), ) .unwrap(); @@ -1238,21 +1253,21 @@ pub mod tests { let mix1_operator_profit = mix_1.operator_reward(¶ms); - let mix1_delegator1_reward = mix_1.reward_delegation(Uint128(8000_000000), ¶ms); + let mix1_delegator1_reward = mix_1.reward_delegation(Uint128::new(8000_000000), ¶ms); - let mix1_delegator2_reward = mix_1.reward_delegation(Uint128(2000_000000), ¶ms); + let mix1_delegator2_reward = mix_1.reward_delegation(Uint128::new(2000_000000), ¶ms); assert_eq!(mix1_operator_profit, U128::from_num(74455384)); assert_eq!(mix1_delegator1_reward, U128::from_num(22552615)); assert_eq!(mix1_delegator2_reward, U128::from_num(5638153)); - let pre_reward_bond = test_helpers::read_mixnode_bond_amount(&deps.storage, b"alice") + let pre_reward_bond = test_helpers::read_mixnode_bond_amount(&deps.storage, "alice") .unwrap() .u128(); assert_eq!(pre_reward_bond, 10_000_000_000); - let pre_reward_delegation = mixnodes_storage::total_delegation_read(&deps.storage) - .load(b"alice") + let pre_reward_delegation = mixnodes_storage::TOTAL_DELEGATION + .load(&deps.storage, "alice") .unwrap() .u128(); assert_eq!(pre_reward_delegation, 10_000_000_000); @@ -1260,21 +1275,21 @@ pub mod tests { try_reward_mixnode_v2(deps.as_mut(), env, info, "alice".to_string(), params, 1).unwrap(); assert_eq!( - test_helpers::read_mixnode_bond_amount(&deps.storage, b"alice") + test_helpers::read_mixnode_bond_amount(&deps.storage, "alice") .unwrap() .u128(), U128::from_num(pre_reward_bond) + U128::from_num(mix1_operator_profit) ); assert_eq!( - mixnodes_storage::total_delegation_read(&deps.storage) - .load(b"alice") + mixnodes_storage::TOTAL_DELEGATION + .load(&deps.storage, "alice") .unwrap() .u128(), pre_reward_delegation + mix1_delegator1_reward + mix1_delegator2_reward ); assert_eq!( - storage::reward_pool_value(&deps.storage).u128(), + storage::REWARD_POOL.load(&deps.storage).unwrap().u128(), U128::from_num(INITIAL_REWARD_POOL) - (U128::from_num(mix1_operator_profit) + U128::from_num(mix1_delegator1_reward) @@ -1282,8 +1297,8 @@ pub mod tests { ); // it's all correctly saved - match mixnodes_storage::rewarded_mixnodes_read(&deps.storage, 1) - .load(b"alice") + match storage::REWARDING_STATUS + .load(deps.as_ref().storage, (1.into(), "alice".into())) .unwrap() { RewardingStatus::Complete(result) => assert_eq!( @@ -1308,13 +1323,12 @@ pub mod tests { let mut deps = test_helpers::init_contract(); let mut env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; - let mix_bond = Uint128(10000_000_000); + let mix_bond = Uint128::new(10000_000_000); let delegation_value = 2000_000000; try_add_mixnode( deps.as_mut(), @@ -1366,7 +1380,10 @@ pub mod tests { 1, ) .unwrap(); - assert_eq!(res.attributes[3], attr("more delegators to reward", false)); + assert_eq!( + res.attributes[3], + attr("more delegators to reward", false.to_string()) + ); try_finish_mixnode_rewarding( deps.as_mut(), @@ -1376,11 +1393,14 @@ pub mod tests { .unwrap(); for i in 0..10 { - let delegation = - mixnodes_storage::mix_delegations_read(deps.as_ref().storage, "10delegators") - .load(format!("delegator{}", i).as_bytes()) - .unwrap(); - assert!(delegation.amount > Uint128(delegation_value)); + let delegation = test_helpers::read_delegation( + &deps.storage, + "10delegators", + format!("delegator{}", i), + ) + .unwrap(); + + assert!(delegation.amount.amount > Uint128::new(delegation_value)); } } @@ -1390,13 +1410,12 @@ pub mod tests { let mut deps = test_helpers::init_contract(); let mut env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; - let mix_bond = Uint128(10000_000_000); + let mix_bond = Uint128::new(10000_000_000); let delegation_value = 2000_000000; try_add_mixnode( deps.as_mut(), @@ -1448,7 +1467,10 @@ pub mod tests { 1, ) .unwrap(); - assert_eq!(res.attributes[3], attr("more delegators to reward", false)); + assert_eq!( + res.attributes[3], + attr("more delegators to reward", false.to_string()) + ); try_finish_mixnode_rewarding( deps.as_mut(), @@ -1458,13 +1480,14 @@ pub mod tests { .unwrap(); for i in 0..MIXNODE_DELEGATORS_PAGE_LIMIT { - let delegation = mixnodes_storage::mix_delegations_read( - deps.as_ref().storage, + let delegation = test_helpers::read_delegation( + &deps.storage, "MIXNODE_DELEGATORS_PAGE_LIMIT_delegators", + format!("delegator{}", i), ) - .load(format!("delegator{}", i).as_bytes()) .unwrap(); - assert!(delegation.amount > Uint128(delegation_value)); + + assert!(delegation.amount.amount > Uint128::new(delegation_value)); } } @@ -1474,13 +1497,12 @@ pub mod tests { let mut deps = test_helpers::init_contract(); let mut env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; - let mix_bond = Uint128(10000_000_000); + let mix_bond = Uint128::new(10000_000_000); let delegation_value = 2000_000000; try_add_mixnode( deps.as_mut(), @@ -1532,7 +1554,10 @@ pub mod tests { 1, ) .unwrap(); - assert_eq!(res.attributes[3], attr("more delegators to reward", true)); + assert_eq!( + res.attributes[3], + attr("more delegators to reward", true.to_string()) + ); try_finish_mixnode_rewarding( deps.as_mut(), @@ -1542,23 +1567,24 @@ pub mod tests { .unwrap(); for i in 0..MIXNODE_DELEGATORS_PAGE_LIMIT { - let delegation = mixnodes_storage::mix_delegations_read( - deps.as_ref().storage, + let delegation = test_helpers::read_delegation( + &deps.storage, "MIXNODE_DELEGATORS_PAGE_LIMIT+1_delegators", + format!("delegator{:04}", i), ) - .load(format!("delegator{:04}", i).as_bytes()) .unwrap(); - assert!(delegation.amount > Uint128(delegation_value)); + + assert!(delegation.amount.amount > Uint128::new(delegation_value)); } - // and the one on the next page should have been unrewarded - let delegation = mixnodes_storage::mix_delegations_read( - deps.as_ref().storage, + let delegation = test_helpers::read_delegation( + &deps.storage, "MIXNODE_DELEGATORS_PAGE_LIMIT+1_delegators", + format!("delegator{:04}", MIXNODE_DELEGATORS_PAGE_LIMIT), ) - .load(format!("delegator{:04}", MIXNODE_DELEGATORS_PAGE_LIMIT).as_bytes()) .unwrap(); - assert_eq!(delegation.amount, Uint128(delegation_value)); + + assert_eq!(delegation.amount.amount, Uint128::new(delegation_value)); } } @@ -1605,16 +1631,18 @@ pub mod tests { let params = DelegatorRewardParams::new(&bond, node_rewarding_params); let res = - reward_mix_delegators_v2(deps.as_mut().storage, &node_identity, None, params).unwrap(); + reward_mix_delegators_v2(deps.as_mut().storage, node_identity.clone(), None, params) + .unwrap(); let mut actual_reward = Uint128::new(0); - for delegation in mixnodes_storage::mix_delegations_read( - deps.as_ref().storage, - &node_identity, - ) - .range(None, None, Order::Ascending) + for delegation in delegations_storage::delegations() + .idx + .mixnode + .prefix(node_identity.clone()) + .range(deps.as_ref().storage, None, None, Order::Ascending) { - actual_reward += Uint128(delegation.unwrap().1.amount.u128() - base_delegation); + actual_reward += + Uint128::new(delegation.unwrap().1.amount.amount.u128() - base_delegation); } // sanity check to make sure we actually gave out any rewards @@ -1661,21 +1689,27 @@ pub mod tests { let params = DelegatorRewardParams::new(&bond, node_rewarding_params); let res = - reward_mix_delegators_v2(deps.as_mut().storage, &node_identity, None, params).unwrap(); + reward_mix_delegators_v2(deps.as_mut().storage, node_identity.clone(), None, params) + .unwrap(); let mut actual_reward = Uint128::new(0); - for delegation in mixnodes_storage::mix_delegations_read( - deps.as_ref().storage, - &node_identity, - ) - .range(None, None, Order::Ascending) + for delegation in delegations_storage::delegations() + .idx + .mixnode + .prefix(node_identity.clone()) + .range(deps.as_ref().storage, None, None, Order::Ascending) { - let (delegator, delegation) = delegation.unwrap(); - let delegator_reward = Uint128(delegation.amount.u128() - base_delegation); + let (primary_key, delegation) = delegation.unwrap(); + let delegator_reward = Uint128::new(delegation.amount.amount.u128() - base_delegation); actual_reward += delegator_reward; - let delegator = String::from_utf8(delegator).unwrap(); - let delegator_id: usize = delegator + // we start from index 2 as first 2 bytes are used to indicate length of first part + // of the composite key + let id_delegator = String::from_utf8_lossy(&primary_key[2..]); + + let delegator_id: usize = id_delegator + .strip_prefix(&node_identity) + .unwrap() .strip_prefix("delegator") .unwrap() .parse() @@ -1694,22 +1728,24 @@ pub mod tests { let res2 = reward_mix_delegators_v2( deps.as_mut().storage, - &node_identity, + node_identity.clone(), res.start_next.clone(), params, ) .unwrap(); let start = res.start_next.unwrap(); - let start_bytes = start.as_bytes(); let mut actual_reward = Uint128::new(0); - for delegation in mixnodes_storage::mix_delegations_read( - deps.as_ref().storage, - &node_identity, - ) - .range(Some(start_bytes), None, Order::Ascending) + + let start = Bound::Inclusive((node_identity.clone(), start).joined_key()); + for delegation in delegations_storage::delegations() + .idx + .mixnode + .prefix(node_identity.clone()) + .range(deps.as_ref().storage, Some(start), None, Order::Ascending) { - actual_reward += Uint128(delegation.unwrap().1.amount.u128() - base_delegation); + actual_reward += + Uint128::new(delegation.unwrap().1.amount.amount.u128() - base_delegation); } assert_eq!(actual_reward, res2.total_rewarded); @@ -1736,10 +1772,9 @@ pub mod tests { #[test] fn cannot_be_called_if_rewarding_is_not_in_progress() { let mut deps = test_helpers::init_contract(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; let res = try_reward_next_mixnode_delegators_v2( @@ -1756,10 +1791,9 @@ pub mod tests { fn cannot_be_called_if_mixnodes_operator_wasnt_rewarded() { let mut deps = test_helpers::init_contract(); let env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; try_begin_mixnode_rewarding( @@ -1790,10 +1824,9 @@ pub mod tests { // everything was done in a single reward call let mut deps = test_helpers::init_contract(); let mut env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; try_add_mixnode( @@ -1803,7 +1836,7 @@ pub mod tests { "alice", &vec![Coin { denom: DENOM.to_string(), - amount: Uint128(10000_000_000), + amount: Uint128::new(10000_000_000), }], ), MixNode { @@ -1862,7 +1895,7 @@ pub mod tests { "bob", &vec![Coin { denom: DENOM.to_string(), - amount: Uint128(10000_000_000), + amount: Uint128::new(10000_000_000), }], ), MixNode { @@ -1942,13 +1975,12 @@ pub mod tests { // setup: bond > page limit delegators, reward operator + first batch let mut deps = test_helpers::init_contract(); let mut env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; - let mix_bond = Uint128(10000_000_000); + let mix_bond = Uint128::new(10000_000_000); let delegation_value = 2000_000000; let total_delegators = 2 * MIXNODE_DELEGATORS_PAGE_LIMIT + 123; @@ -2020,18 +2052,24 @@ pub mod tests { ) .unwrap(); - let expected = mixnodes_storage::mix_delegations_read(deps.as_ref().storage, "alice") - .load("delegator0001".as_bytes()) + let expected = delegations_storage::delegations() + .load( + deps.as_ref().storage, + ("alice", "delegator0001").joined_key(), + ) .unwrap() .amount; for i in 0..total_delegators { // everyone was rewarded (and the same amount, because they all delegated the same amount) - let delegation = - mixnodes_storage::mix_delegations_read(deps.as_ref().storage, "alice") - .load(format!("delegator{:04}", i).as_bytes()) - .unwrap(); - assert!(delegation.amount > Uint128(delegation_value)); + let delegation = test_helpers::read_delegation( + &deps.storage, + "alice", + format!("delegator{:04}", i), + ) + .unwrap(); + + assert!(delegation.amount.amount > Uint128::new(delegation_value)); assert_eq!(expected, delegation.amount) } } @@ -2041,13 +2079,12 @@ pub mod tests { // setup: bond > page limit delegators, reward operator + first batch let mut deps = test_helpers::init_contract(); let mut env = mock_env(); - let current_state = - mixnet_params_storage::contract_settings_read(deps.as_mut().storage) - .load() - .unwrap(); + let current_state = mixnet_params_storage::CONTRACT_SETTINGS + .load(deps.as_mut().storage) + .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; - let mix_bond = Uint128(10000_000_000); + let mix_bond = Uint128::new(10000_000_000); let delegation_value = 2000_000000; let total_delegators = MIXNODE_DELEGATORS_PAGE_LIMIT + 123; @@ -2134,22 +2171,23 @@ pub mod tests { ) .unwrap(); - let expected = mixnodes_storage::mix_delegations_read(deps.as_ref().storage, "alice") - .load("delegator0001".as_bytes()) + let expected = test_helpers::read_delegation(&deps.storage, "alice", "delegator0001") .unwrap() .amount; for i in 0..total_delegators { // everyone was rewarded (and the same amount, because they all delegated the same amount) - let delegation = - mixnodes_storage::mix_delegations_read(deps.as_ref().storage, "alice") - .load(format!("delegator{:04}", i).as_bytes()) - .unwrap(); + let delegation = test_helpers::read_delegation( + &deps.storage, + "alice", + format!("delegator{:04}", i), + ) + .unwrap(); if i == 123 || i == 123 + MIXNODE_DELEGATORS_PAGE_LIMIT { - assert_eq!(delegation.amount, Uint128(2 * delegation_value)) + assert_eq!(delegation.amount.amount, Uint128::new(2 * delegation_value)) } else { - assert!(delegation.amount > Uint128(delegation_value)); + assert!(delegation.amount.amount > Uint128::new(delegation_value)); assert_eq!(expected, delegation.amount) } } diff --git a/contracts/mixnet/src/support/helpers.rs b/contracts/mixnet/src/support/helpers.rs new file mode 100644 index 0000000000..e389ae5814 --- /dev/null +++ b/contracts/mixnet/src/support/helpers.rs @@ -0,0 +1,47 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::ContractError; +use crate::gateways::storage as gateways_storage; +use crate::mixnodes::storage as mixnodes_storage; +use cosmwasm_std::{Addr, Storage}; + +pub fn generate_storage_key(address: &Addr, proxy: Option<&Addr>) -> Vec { + if let Some(proxy) = &proxy { + address + .as_bytes() + .iter() + .zip(proxy.as_bytes()) + .map(|(x, y)| x ^ y) + .collect() + } else { + address.as_bytes().to_vec() + } +} + +// check if the target address has already bonded a mixnode or gateway, +// in either case, return an appropriate error +pub(crate) fn ensure_no_existing_bond( + storage: &dyn Storage, + sender: &Addr, +) -> Result<(), ContractError> { + if mixnodes_storage::mixnodes() + .idx + .owner + .item(storage, sender.clone())? + .is_some() + { + return Err(ContractError::AlreadyOwnsMixnode); + } + + if gateways_storage::gateways() + .idx + .owner + .item(storage, sender.clone())? + .is_some() + { + return Err(ContractError::AlreadyOwnsGateway); + } + + Ok(()) +} diff --git a/contracts/mixnet/src/support/mod.rs b/contracts/mixnet/src/support/mod.rs index 15ab560571..f27f7eecd0 100644 --- a/contracts/mixnet/src/support/mod.rs +++ b/contracts/mixnet/src/support/mod.rs @@ -1 +1,5 @@ -pub mod tests; +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) mod helpers; +pub(crate) mod tests; diff --git a/contracts/mixnet/src/support/tests.rs b/contracts/mixnet/src/support/tests.rs index 0298797d82..3d5db205f1 100644 --- a/contracts/mixnet/src/support/tests.rs +++ b/contracts/mixnet/src/support/tests.rs @@ -5,11 +5,13 @@ pub mod test_helpers { use crate::contract::{ query, DEFAULT_SYBIL_RESISTANCE_PERCENT, EPOCH_REWARD_PERCENT, INITIAL_REWARD_POOL, }; + use crate::delegations::storage as delegations_storage; use crate::gateways::transactions::try_add_gateway; - use crate::mixnodes::bonding_transactions::try_add_mixnode; use crate::mixnodes::storage as mixnodes_storage; use crate::mixnodes::storage::StoredMixnodeBond; + use crate::mixnodes::transactions::try_add_mixnode; use config::defaults::{DENOM, TOTAL_SUPPLY}; + use cosmwasm_std::coin; use cosmwasm_std::testing::mock_dependencies; use cosmwasm_std::testing::mock_env; use cosmwasm_std::testing::mock_info; @@ -18,14 +20,14 @@ pub mod test_helpers { use cosmwasm_std::testing::MockStorage; use cosmwasm_std::Coin; use cosmwasm_std::OwnedDeps; - use cosmwasm_std::{coin, Uint128}; use cosmwasm_std::{from_binary, DepsMut}; use cosmwasm_std::{Addr, StdResult, Storage}; use cosmwasm_std::{Empty, MemoryStorage}; + use cw_storage_plus::PrimaryKey; use mixnet_contract::mixnode::NodeRewardParams; use mixnet_contract::{ - Gateway, GatewayBond, InstantiateMsg, Layer, MixNode, MixNodeBond, PagedGatewayResponse, - PagedMixnodeResponse, QueryMsg, RawDelegationData, + Delegation, Gateway, GatewayBond, IdentityKeyRef, InstantiateMsg, Layer, MixNode, + MixNodeBond, PagedGatewayResponse, PagedMixnodeResponse, QueryMsg, }; pub fn add_mixnode(sender: &str, stake: Vec, deps: DepsMut) -> String { @@ -61,15 +63,11 @@ pub mod test_helpers { page.nodes } - pub fn add_gateway( - sender: &str, - stake: Vec, - deps: &mut OwnedDeps, - ) -> String { + pub fn add_gateway(sender: &str, stake: Vec, deps: DepsMut) -> String { let info = mock_info(sender, &stake); let key = format!("{}gateway", sender); try_add_gateway( - deps.as_mut(), + deps, mock_env(), info, Gateway { @@ -99,7 +97,7 @@ pub mod test_helpers { } pub fn init_contract() -> OwnedDeps> { - let mut deps = mock_dependencies(&[]); + let mut deps = mock_dependencies(); let msg = InstantiateMsg {}; let env = mock_env(); let info = mock_info("creator", &[]); @@ -119,33 +117,17 @@ pub mod test_helpers { } } - pub fn mixnode_bond_fixture() -> MixNodeBond { - let mix_node = MixNode { - host: "1.1.1.1".to_string(), - mix_port: 1789, - verloc_port: 1790, - http_api_port: 8000, - sphinx_key: "1234".to_string(), - identity_key: "aaaa".to_string(), - version: "0.10.0".to_string(), - }; - MixNodeBond::new( - coin(50, DENOM), - Addr::unchecked("foo"), - Layer::One, - 12_345, - mix_node, - None, - ) - } - - pub(crate) fn stored_mixnode_bond_fixture() -> mixnodes_storage::StoredMixnodeBond { + pub(crate) fn stored_mixnode_bond_fixture(owner: &str) -> mixnodes_storage::StoredMixnodeBond { StoredMixnodeBond::new( coin(50, DENOM), - Addr::unchecked("foo"), + Addr::unchecked(owner), Layer::One, 12_345, - mix_node_fixture(), + MixNode { + identity_key: format!("id-{}", owner), + ..mix_node_fixture() + }, + None, None, ) } @@ -156,28 +138,18 @@ pub mod test_helpers { mix_port: 1789, clients_port: 9000, location: "Sweden".to_string(), - sphinx_key: "sphinx".to_string(), identity_key: "identity".to_string(), version: "0.10.0".to_string(), } } - pub fn gateway_bond_fixture() -> GatewayBond { + pub fn gateway_bond_fixture(owner: &str) -> GatewayBond { let gateway = Gateway { - host: "1.1.1.1".to_string(), - mix_port: 1789, - clients_port: 9000, - location: "London".to_string(), - sphinx_key: "sphinx".to_string(), - identity_key: "identity".to_string(), - version: "0.10.0".to_string(), + identity_key: format!("id-{}", owner), + ..gateway_fixture() }; - GatewayBond::new(coin(50, DENOM), Addr::unchecked("foo"), 12_345, gateway) - } - - pub fn raw_delegation_fixture(amount: u128) -> RawDelegationData { - RawDelegationData::new(Uint128(amount), 42) + GatewayBond::new(coin(50, DENOM), Addr::unchecked(owner), 12_345, gateway) } pub fn query_contract_balance( @@ -214,23 +186,40 @@ pub mod test_helpers { ) } - // Converts the node identity and owner of a delegation into the bytes used as - // key in the delegation buckets. Basically a helper function. - pub(crate) fn identity_and_owner_to_bytes(identity: &str, owner: &Addr) -> Vec { - let mut bytes = u16::to_be_bytes(identity.len() as u16).to_vec(); - bytes.append(&mut identity.as_bytes().to_vec()); - bytes.append(&mut owner.as_bytes().to_vec()); - - bytes - } - // currently not used outside tests pub(crate) fn read_mixnode_bond_amount( storage: &dyn Storage, - identity: &[u8], + identity: IdentityKeyRef, ) -> StdResult { - let bucket = mixnodes_storage::mixnodes_read(storage); - let node = bucket.load(identity)?; + let node = mixnodes_storage::mixnodes().load(storage, identity)?; Ok(node.bond_amount.amount) } + + pub(crate) fn save_dummy_delegation( + storage: &mut dyn Storage, + mix: impl Into, + owner: impl Into, + ) { + let delegation = Delegation { + owner: Addr::unchecked(owner.into()), + node_identity: mix.into(), + amount: coin(12345, DENOM), + block_height: 12345, + proxy: None, + }; + + delegations_storage::delegations() + .save(storage, delegation.storage_key().joined_key(), &delegation) + .unwrap(); + } + + pub(crate) fn read_delegation( + storage: &dyn Storage, + mix: impl Into, + owner: impl Into, + ) -> Option { + delegations_storage::delegations() + .may_load(storage, (mix.into(), owner.into()).joined_key()) + .unwrap() + } } diff --git a/contracts/vesting/Cargo.lock b/contracts/vesting/Cargo.lock new file mode 100644 index 0000000000..f0e659d499 --- /dev/null +++ b/contracts/vesting/Cargo.lock @@ -0,0 +1,908 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "az" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "bytemuck" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "config" +version = "0.1.0" +dependencies = [ + "handlebars", + "humantime-serde", + "network-defaults", + "serde", + "toml", + "url", +] + +[[package]] +name = "const-oid" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" + +[[package]] +name = "cosmwasm-crypto" +version = "0.14.1" +source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +dependencies = [ + "digest 0.9.0", + "ed25519-zebra", + "k256", + "rand_core 0.5.1", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "0.14.1" +source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +dependencies = [ + "syn", +] + +[[package]] +name = "cosmwasm-std" +version = "0.14.1" +source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +dependencies = [ + "base64", + "cosmwasm-crypto", + "cosmwasm-derive", + "schemars", + "serde", + "serde-json-wasm", + "thiserror", + "uint", +] + +[[package]] +name = "cosmwasm-storage" +version = "0.14.1" +source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +dependencies = [ + "cosmwasm-std", + "serde", +] + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" +dependencies = [ + "generic-array 0.14.4", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "der" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2" +dependencies = [ + "const-oid", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "dyn-clone" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" + +[[package]] +name = "ecdsa" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" +dependencies = [ + "der", + "elliptic-curve", + "hmac", + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409" +dependencies = [ + "curve25519-dalek", + "hex", + "rand_core 0.5.1", + "serde", + "sha2", + "thiserror", +] + +[[package]] +name = "elliptic-curve" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b" +dependencies = [ + "crypto-bigint", + "ff", + "generic-array 0.14.4", + "group", + "pkcs8", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "ff" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "fixed" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d333a26ec13a023c6dff4b7584de4d323cfee2e508f5dd2bbee6669e4f7efdf" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "group" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "handlebars" +version = "3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error", + "serde", + "serde_json", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "humantime-serde" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058" +dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "k256" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "libc" +version = "0.2.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "mixnet-contract" +version = "0.1.0" +dependencies = [ + "az", + "cosmwasm-std", + "fixed", + "log", + "network-defaults", + "schemars", + "serde", + "serde_repr", + "thiserror", +] + +[[package]] +name = "network-defaults" +version = "0.1.0" +dependencies = [ + "hex-literal", + "serde", + "time", + "url", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] + +[[package]] +name = "pkcs8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.3", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "schemars" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271ac0c667b8229adf70f0f957697c96fafd7486ab7481e15dc5e45e3e6a4368" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ebda811090b257411540779860bc09bf321bc587f58d2c5864309d1566214e7" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50eef3672ec8fa45f3457fd423ba131117786784a895548021976117c1ded449" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "signature" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.3", +] + +[[package]] +name = "spki" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" +dependencies = [ + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" +dependencies = [ + "libc", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "typenum" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "uint" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "vesting-contracts" +version = "0.1.0" +dependencies = [ + "config", + "cosmwasm-std", + "cosmwasm-storage", + "mixnet-contract", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "zeroize" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" diff --git a/contracts/vesting/Cargo.toml b/contracts/vesting/Cargo.toml new file mode 100644 index 0000000000..21b5588e41 --- /dev/null +++ b/contracts/vesting/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "vesting-contract" +version = "0.1.0" +authors = ["Drazen Urch "] +edition = "2018" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] + +[dependencies] +mixnet-contract = { path = "../../common/mixnet-contract" } +config = { path = "../../common/config" } + +# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version +cosmwasm-std = { version = "1.0.0-beta2", features = ["iterator"]} +cw-storage-plus = { version = "0.10.3", features = ["iterator"] } + +schemars = "0.8" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.23" } diff --git a/contracts/vesting/Makefile b/contracts/vesting/Makefile new file mode 100644 index 0000000000..a00097bc65 --- /dev/null +++ b/contracts/vesting/Makefile @@ -0,0 +1,2 @@ +wasm: + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown \ No newline at end of file diff --git a/contracts/vesting/README.md b/contracts/vesting/README.md new file mode 100644 index 0000000000..5011529566 --- /dev/null +++ b/contracts/vesting/README.md @@ -0,0 +1,11 @@ +## Nym vesting contract + +1. Initial vesting tokens are deposited and assigned to existing addresses via `CreatePeriodicVestingAccount` +2. Admin account can then delegate vested and unvested tokens to mixnodes on behalf of vesting accounts +3. Vesting accounts can withdraw vested and undelegated (spendable) coins to their addresses + +### Vesting coin delegation flow + +![vesting-coin-delegation](images/vesting-coin-delegation.png) + + diff --git a/contracts/vesting/images/vesting-coin-delegation.excalidraw b/contracts/vesting/images/vesting-coin-delegation.excalidraw new file mode 100644 index 0000000000..a266cbdafe --- /dev/null +++ b/contracts/vesting/images/vesting-coin-delegation.excalidraw @@ -0,0 +1,2334 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 519, + "versionNonce": 1062888247, + "isDeleted": false, + "id": "MYxtoDxJzXclc1V_xi1WF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 706, + "y": 189, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 711, + "height": 230, + "seed": 1533201726, + "groupIds": [ + "NlwuykxSg7SLzsQ4Nwg1X" + ], + "strokeSharpness": "sharp", + "boundElementIds": [ + "HYOHncnQVZWBvr-M9ZZAS", + "rnwQO7IBwRmWjeyiQB3Vi", + "FEiGi1WpRiafeEjW2e0ar", + "eyOrkoJeisVmkl6epuFRs" + ] + }, + { + "type": "rectangle", + "version": 371, + "versionNonce": 868555865, + "isDeleted": false, + "id": "Vi4HRGJ-TBCqHwf5o1x7J", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 361, + "y": 149, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 212, + "height": 88.00000000000003, + "seed": 1236203966, + "groupIds": [ + "kiuCZjjLWn-Li99caEDKL" + ], + "strokeSharpness": "round", + "boundElementIds": [ + "qODF9ZRapeJw6RFhAhgYM" + ] + }, + { + "type": "text", + "version": 250, + "versionNonce": 736899511, + "isDeleted": false, + "id": "2Z1lUF5arNib6N0vyjvKr", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 372, + "y": 167.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 189, + "height": 51, + "seed": 816465982, + "groupIds": [ + "kiuCZjjLWn-Li99caEDKL" + ], + "strokeSharpness": "round", + "boundElementIds": [ + "HYOHncnQVZWBvr-M9ZZAS" + ], + "fontSize": 20, + "fontFamily": 1, + "text": "Execute:\nDelegateToMixnode", + "baseline": 44, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 324, + "versionNonce": 2104633657, + "isDeleted": false, + "id": "Y_uwHW80lXF2Kt7SFz7Ij", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 710, + "y": 248.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 256, + "height": 26, + "seed": 818905762, + "groupIds": [ + "39ikc3B8kKQkq7GO-_0F9" + ], + "strokeSharpness": "round", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "try_delegate_to_mixnode", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "rectangle", + "version": 314, + "versionNonce": 1762321111, + "isDeleted": false, + "id": "lD_DnClqtxFDaaRSPrt_a", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 701, + "y": 238, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 279, + "height": 45, + "seed": 1934994110, + "groupIds": [ + "39ikc3B8kKQkq7GO-_0F9" + ], + "strokeSharpness": "sharp", + "boundElementIds": [ + "FEiGi1WpRiafeEjW2e0ar", + "Adhr8zJSmeGoNUciUuifM", + "PB32t0UgRYTpNSDkxZkdM", + "ZcsHHePHKfoC6K3HCq1eH", + "qODF9ZRapeJw6RFhAhgYM" + ] + }, + { + "type": "rectangle", + "version": 510, + "versionNonce": 1694054841, + "isDeleted": false, + "id": "cvM_i-TKjR7-aSkH5_qwN", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1121, + "y": 432, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 314, + "height": 90.00000000000001, + "seed": 112049086, + "groupIds": [ + "C2X0UQ4xhJzCgCEimf-QD" + ], + "strokeSharpness": "round", + "boundElementIds": [ + "ZcsHHePHKfoC6K3HCq1eH", + "W2QMkmcNKpOgJQVtbbzjS", + "UtRs1Ny8Lu12kDVC_-p4R" + ] + }, + { + "type": "text", + "version": 458, + "versionNonce": 1404809367, + "isDeleted": false, + "id": "Ht9vgSpAFy0EjmjYn_P-C", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1132, + "y": 451, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 277, + "height": 51, + "seed": 491554174, + "groupIds": [ + "C2X0UQ4xhJzCgCEimf-QD" + ], + "strokeSharpness": "round", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Execute:\nDelegateToMixnodeOnBehalf", + "baseline": 44, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 44, + "versionNonce": 1600253689, + "isDeleted": false, + "id": "bP4Fh03tsEQa9DsbMSxyg", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 880, + "y": 200, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 162, + "height": 26, + "seed": 582443810, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Vesting contract", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 603, + "versionNonce": 68909497, + "isDeleted": false, + "id": "zUPjGl_y_pL1qf2rlQvuo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1431.5, + "y": 694.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 618.9999999999999, + "height": 161, + "seed": 47294398, + "groupIds": [ + "AKMAhe22j853RCAwqK163" + ], + "strokeSharpness": "sharp", + "boundElementIds": [ + "HYOHncnQVZWBvr-M9ZZAS", + "rnwQO7IBwRmWjeyiQB3Vi", + "FEiGi1WpRiafeEjW2e0ar" + ] + }, + { + "type": "text", + "version": 129, + "versionNonce": 1327212505, + "isDeleted": false, + "id": "3BXfL5Hr7ZCx29qXsQMyM", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1606, + "y": 704, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 158, + "height": 26, + "seed": 1656643938, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Mixnet contract", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 628, + "versionNonce": 2142641719, + "isDeleted": false, + "id": "nA1Lc3bWpnjvA9QRTmqcK", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1436, + "y": 752, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 369, + "height": 26, + "seed": 1866424866, + "groupIds": [ + "qGUIeAaaWqraxOMfntW4B" + ], + "strokeSharpness": "round", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "try_delegate_to_mixnode_on_behalf", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 615, + "versionNonce": 218420985, + "isDeleted": false, + "id": "ISAmxS-jD85ZpBv7JUbZL", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1421.5, + "y": 741.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 394.00000000000006, + "height": 43, + "seed": 813222142, + "groupIds": [ + "qGUIeAaaWqraxOMfntW4B" + ], + "strokeSharpness": "sharp", + "boundElementIds": [ + "FEiGi1WpRiafeEjW2e0ar", + "Adhr8zJSmeGoNUciUuifM", + "Qwwla_PjNOp6ryDhpZzFL", + "YAffQPR88P739qySe0Tju", + "W2QMkmcNKpOgJQVtbbzjS", + "UtRs1Ny8Lu12kDVC_-p4R" + ] + }, + { + "type": "line", + "version": 2747, + "versionNonce": 1977827159, + "isDeleted": false, + "id": "en1lt96mRAuMeolVkCtWh", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1944.9704524465478, + "y": 708.7884556530419, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "width": 101.43255738417912, + "height": 117.61725800769342, + "seed": 13204121, + "groupIds": [ + "jYMzI_eNq4Qvs7RnSljjb", + "f7GCz2tAQgaAdWudK5eIf" + ], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 81.24504586319145 + ], + [ + 1.777062999357842, + 95.93711004248823 + ], + [ + 13.975545169118597, + 101.33520266828153 + ], + [ + 32.24595007890858, + 105.6036749382846 + ], + [ + 51.72898522706211, + 106.7059820442988 + ], + [ + 68.36290060874084, + 105.6069084227593 + ], + [ + 83.9642241791822, + 102.88989158362904 + ], + [ + 99.52537188642827, + 95.35807376427104 + ], + [ + 100.58910440204657, + 81.24504586319145 + ], + [ + 101.43255738417912, + 11.066502392818677 + ], + [ + 100.15182896713623, + -0.353787502790162 + ], + [ + 87.54390769820083, + -6.446029372429869 + ], + [ + 73.39395832349102, + -9.897171202621365 + ], + [ + 54.99841378339567, + -10.91127596339463 + ], + [ + 43.322977231062616, + -10.881763683096402 + ], + [ + 18.460883213902022, + -8.686704925433872 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1005, + "versionNonce": 1014252953, + "isDeleted": false, + "id": "5XBuz5OPyN-8gquS8QAsJ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1944.5969901692724, + "y": 696.4636016253974, + "strokeColor": "#c92a2a", + "backgroundColor": "#ffffff", + "width": 101.25296680383022, + "height": 27.53016315242473, + "seed": 1720964983, + "groupIds": [ + "jYMzI_eNq4Qvs7RnSljjb", + "f7GCz2tAQgaAdWudK5eIf" + ], + "strokeSharpness": "sharp", + "boundElementIds": [ + "YAffQPR88P739qySe0Tju", + "5grdyB0Elp_zD4irC9Gsk" + ] + }, + { + "type": "rectangle", + "version": 561, + "versionNonce": 672087159, + "isDeleted": false, + "id": "gj5xoaSTz0iuLCuwV1oz2", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1964.2598526574575, + "y": 777.7343765377857, + "strokeColor": "#495057", + "backgroundColor": "#ffffff", + "width": 22.91135804392394, + "height": 14.590977260318633, + "seed": 1590738809, + "groupIds": [ + "JVOFJKLzDTzckiWSUgN5m", + "f7GCz2tAQgaAdWudK5eIf" + ], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "rectangle", + "version": 564, + "versionNonce": 283781753, + "isDeleted": false, + "id": "Bqfw2fQsNS-qaQRpWRhz2", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2004.058588888207, + "y": 777.7343765377857, + "strokeColor": "#495057", + "backgroundColor": "#ffffff", + "width": 22.91135804392394, + "height": 14.590977260318633, + "seed": 1127731351, + "groupIds": [ + "JVOFJKLzDTzckiWSUgN5m", + "f7GCz2tAQgaAdWudK5eIf" + ], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "rectangle", + "version": 572, + "versionNonce": 1778292119, + "isDeleted": false, + "id": "0BOSVuYak2H2TqQ5oaxYV", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1984.15922077283, + "y": 737.9356403070326, + "strokeColor": "#495057", + "backgroundColor": "#ffffff", + "width": 22.91135804392394, + "height": 14.590977260318633, + "seed": 157050969, + "groupIds": [ + "JVOFJKLzDTzckiWSUgN5m", + "f7GCz2tAQgaAdWudK5eIf" + ], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "line", + "version": 610, + "versionNonce": 722498393, + "isDeleted": false, + "id": "c-b8nksmRAEKTjKVjwjeI", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1975.2699687220284, + "y": 777.7706835995771, + "strokeColor": "#495057", + "backgroundColor": "#ffffff", + "width": 42.82342909333, + "height": 10.921302987215531, + "seed": 390047159, + "groupIds": [ + "JVOFJKLzDTzckiWSUgN5m", + "f7GCz2tAQgaAdWudK5eIf" + ], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.6478576310471701, + -10.921302987215531 + ], + [ + 42.296066688807485, + -10.68835017932191 + ], + [ + 42.82342909333, + -1.4299639028268747 + ] + ] + }, + { + "type": "line", + "version": 562, + "versionNonce": 585484983, + "isDeleted": false, + "id": "zeRaGh8Lrnf-UJITBNl6a", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1994.6867240928714, + "y": 767.4926435145032, + "strokeColor": "#495057", + "backgroundColor": "#ffffff", + "width": 0.17616130803224594, + "height": 14.56556303030538, + "seed": 977270073, + "groupIds": [ + "JVOFJKLzDTzckiWSUgN5m", + "f7GCz2tAQgaAdWudK5eIf" + ], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.17616130803224594, + -14.56556303030538 + ] + ] + }, + { + "type": "text", + "version": 262, + "versionNonce": 1060806713, + "isDeleted": false, + "id": "3fhK0EbFmk40pk08qaoLh", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1957.1148997947957, + "y": 819.5363983746026, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "width": 77, + "height": 26, + "seed": 30398167, + "groupIds": [ + "ZYXcHiKiCn7_iwCfsh2K6", + "Uf0VuMlgmQq2TUCNj2ePW", + "f7GCz2tAQgaAdWudK5eIf" + ], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Storage", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "YAffQPR88P739qySe0Tju", + "type": "arrow", + "x": 1824, + "y": 766.1757538016249, + "width": 120.32509607542079, + "height": 49.42270163035721, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1543535417, + "version": 294, + "versionNonce": 1600740311, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 120.32509607542079, + -49.42270163035721 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "ISAmxS-jD85ZpBv7JUbZL", + "focus": 0.8551703476442745, + "gap": 8.5 + }, + "endBinding": { + "elementId": "5XBuz5OPyN-8gquS8QAsJ", + "focus": 0.5767091753791422, + "gap": 3.5200653993721005 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "version": 459, + "versionNonce": 726413593, + "isDeleted": false, + "id": "UJ4MLZgRS4EhJ2sQzFQPn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1068.0222222222224, + "y": 248.5333333333333, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 159.57777777777778, + "height": 24.844444444444445, + "seed": 751528793, + "groupIds": [ + "Xx9cp5S4oRp_u3JIvpJ7q" + ], + "strokeSharpness": "round", + "boundElementIds": [], + "fontSize": 19.11111111111111, + "fontFamily": 1, + "text": "track_delegation", + "baseline": 16.844444444444445, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 520, + "versionNonce": 597543159, + "isDeleted": false, + "id": "tz0dxRUO4ifmYOu4YJ7pK", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1044.9, + "y": 238.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 193.6, + "height": 43, + "seed": 1752584887, + "groupIds": [ + "Xx9cp5S4oRp_u3JIvpJ7q" + ], + "strokeSharpness": "sharp", + "boundElementIds": [ + "FEiGi1WpRiafeEjW2e0ar", + "Adhr8zJSmeGoNUciUuifM", + "PB32t0UgRYTpNSDkxZkdM", + "inlX1eg8mOf9IUm3InbSv" + ] + }, + { + "id": "PB32t0UgRYTpNSDkxZkdM", + "type": "arrow", + "x": 987, + "y": 260, + "width": 53, + "height": 1, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 905064503, + "version": 42, + "versionNonce": 1152951801, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 53, + -1 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "lD_DnClqtxFDaaRSPrt_a", + "focus": 0.09009009009009009, + "gap": 7 + }, + "endBinding": { + "elementId": "tz0dxRUO4ifmYOu4YJ7pK", + "focus": 0.12513144058885387, + "gap": 4.900000000000091 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "ZcsHHePHKfoC6K3HCq1eH", + "type": "arrow", + "x": 988.0000000000002, + "y": 271.1790010297284, + "width": 167.5941724800407, + "height": 157.5664409659072, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1609613783, + "version": 410, + "versionNonce": 553360025, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 167.5941724800407, + 157.5664409659072 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "lD_DnClqtxFDaaRSPrt_a", + "focus": -0.8322016091692943, + "gap": 8.000000000000227 + }, + "endBinding": { + "elementId": "cvM_i-TKjR7-aSkH5_qwN", + "focus": -0.346963529304385, + "gap": 3.2545580043644122 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "W2QMkmcNKpOgJQVtbbzjS", + "type": "arrow", + "x": 1318.4753590614534, + "y": 656.8275952783034, + "width": 94.63688086231036, + "height": 141.10141936175967, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1566418649, + "version": 439, + "versionNonce": 1629705817, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 94.63688086231036, + 141.10141936175967 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "36nolnhlBq-awLM-MTxNR", + "focus": -0.5859833057818128, + "gap": 8.82759527830342 + }, + "endBinding": { + "elementId": "NFtY5AR_vMtKJVxoRiy9_", + "focus": -0.9181619064021775, + "gap": 7.887760076236191 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "qODF9ZRapeJw6RFhAhgYM", + "type": "arrow", + "x": 584.0000000000001, + "y": 210.3633588669681, + "width": 107.99999999999989, + "height": 30.577074887578163, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1560079799, + "version": 237, + "versionNonce": 1419458359, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 107.99999999999989, + 30.577074887578163 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "Vi4HRGJ-TBCqHwf5o1x7J", + "focus": -0.21628994544037405, + "gap": 11 + }, + "endBinding": { + "elementId": "lD_DnClqtxFDaaRSPrt_a", + "focus": -0.3626707132018209, + "gap": 9 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "version": 394, + "versionNonce": 1061174201, + "isDeleted": false, + "id": "Dru8651ZYhpnaJs1faWeT", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 350, + "y": 264, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 230.99999999999994, + "height": 91.00000000000004, + "seed": 1871238009, + "groupIds": [ + "GgJnuZMIcR_4V2kFO9JIP" + ], + "strokeSharpness": "round", + "boundElementIds": [ + "qODF9ZRapeJw6RFhAhgYM" + ] + }, + { + "id": "kdzYRrqFjIX7xFqmuBVL5", + "type": "text", + "x": 353.5, + "y": 282.5, + "width": 225, + "height": 51, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "GgJnuZMIcR_4V2kFO9JIP" + ], + "strokeSharpness": "round", + "seed": 4742263, + "version": 120, + "versionNonce": 12400727, + "isDeleted": false, + "boundElementIds": [ + "2tsaPZnEIAbBQH3RkxDWA" + ], + "text": "Execute:\nUndelegateFromMixnode", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 44 + }, + { + "type": "text", + "version": 486, + "versionNonce": 1595038873, + "isDeleted": false, + "id": "FRjaK_ykF6FoJPSeAfO7x", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 717, + "y": 305, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 297, + "height": 26, + "seed": 1222845081, + "groupIds": [ + "C0URVRGN4Yr4SK28McttS" + ], + "strokeSharpness": "round", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "try_undelegate_from_mixnode", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 497, + "versionNonce": 390206839, + "isDeleted": false, + "id": "H8iLjH4gabuF8pMHcZOx7", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 700.5, + "y": 294.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 323, + "height": 45, + "seed": 71872375, + "groupIds": [ + "C0URVRGN4Yr4SK28McttS" + ], + "strokeSharpness": "sharp", + "boundElementIds": [ + "FEiGi1WpRiafeEjW2e0ar", + "Adhr8zJSmeGoNUciUuifM", + "PB32t0UgRYTpNSDkxZkdM", + "ZcsHHePHKfoC6K3HCq1eH", + "qODF9ZRapeJw6RFhAhgYM", + "PUrPGopNGqDfT1zkMHLbv", + "2tsaPZnEIAbBQH3RkxDWA" + ] + }, + { + "type": "rectangle", + "version": 573, + "versionNonce": 1620378263, + "isDeleted": false, + "id": "36nolnhlBq-awLM-MTxNR", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1003, + "y": 558, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 329.99999999999994, + "height": 90.00000000000001, + "seed": 377715033, + "groupIds": [ + "VX1OP277Cy_eIf_1Zmn60" + ], + "strokeSharpness": "round", + "boundElementIds": [ + "ZcsHHePHKfoC6K3HCq1eH", + "W2QMkmcNKpOgJQVtbbzjS", + "PUrPGopNGqDfT1zkMHLbv" + ] + }, + { + "type": "text", + "version": 510, + "versionNonce": 2137935511, + "isDeleted": false, + "id": "z2Py4KGZzlgp0vu1t-8-W", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1010, + "y": 577, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 317, + "height": 51, + "seed": 546186423, + "groupIds": [ + "VX1OP277Cy_eIf_1Zmn60" + ], + "strokeSharpness": "round", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Execute:\nUnDelegateFromMixnodeOnBehalf", + "baseline": 44, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "PUrPGopNGqDfT1zkMHLbv", + "type": "arrow", + "x": 977, + "y": 349, + "width": 184, + "height": 199, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 736356023, + "version": 33, + "versionNonce": 810863193, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 184, + 199 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "H8iLjH4gabuF8pMHcZOx7", + "focus": -0.46851440936091626, + "gap": 9.5 + }, + "endBinding": { + "elementId": "36nolnhlBq-awLM-MTxNR", + "focus": 0.21225829989055092, + "gap": 10 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "line", + "version": 2908, + "versionNonce": 1963048537, + "isDeleted": false, + "id": "QSHF55m7GGVz--aR54rwE", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1311.4704524465483, + "y": 205.78845565304186, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "width": 101.43255738417912, + "height": 117.61725800769342, + "seed": 1138815769, + "groupIds": [ + "Rr3qpiNqfnUB2L6Eaqx-p", + "8XQjskjWZv-xjIxPq5m1P" + ], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 81.24504586319145 + ], + [ + 1.777062999357842, + 95.93711004248823 + ], + [ + 13.975545169118597, + 101.33520266828153 + ], + [ + 32.24595007890858, + 105.6036749382846 + ], + [ + 51.72898522706211, + 106.7059820442988 + ], + [ + 68.36290060874084, + 105.6069084227593 + ], + [ + 83.9642241791822, + 102.88989158362904 + ], + [ + 99.52537188642827, + 95.35807376427104 + ], + [ + 100.58910440204657, + 81.24504586319145 + ], + [ + 101.43255738417912, + 11.066502392818677 + ], + [ + 100.15182896713623, + -0.353787502790162 + ], + [ + 87.54390769820083, + -6.446029372429869 + ], + [ + 73.39395832349102, + -9.897171202621365 + ], + [ + 54.99841378339567, + -10.91127596339463 + ], + [ + 43.322977231062616, + -10.881763683096402 + ], + [ + 18.460883213902022, + -8.686704925433872 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1168, + "versionNonce": 599961527, + "isDeleted": false, + "id": "YrGORvo4mrQf5OKDyGc3_", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1311.0969901692729, + "y": 193.46360162539736, + "strokeColor": "#c92a2a", + "backgroundColor": "#ffffff", + "width": 101.25296680383022, + "height": 27.53016315242473, + "seed": 489563895, + "groupIds": [ + "Rr3qpiNqfnUB2L6Eaqx-p", + "8XQjskjWZv-xjIxPq5m1P" + ], + "strokeSharpness": "sharp", + "boundElementIds": [ + "YAffQPR88P739qySe0Tju", + "inlX1eg8mOf9IUm3InbSv", + "vJsQiY14sEJV4oqTconnZ" + ] + }, + { + "type": "rectangle", + "version": 722, + "versionNonce": 608529911, + "isDeleted": false, + "id": "kYHcbpfjXgJ9XQjntWZKX", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1330.759852657458, + "y": 274.7343765377857, + "strokeColor": "#495057", + "backgroundColor": "#ffffff", + "width": 22.91135804392394, + "height": 14.590977260318633, + "seed": 828712953, + "groupIds": [ + "0rpvvbnQiKgAHji8s7Qu3", + "8XQjskjWZv-xjIxPq5m1P" + ], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "rectangle", + "version": 725, + "versionNonce": 1065996537, + "isDeleted": false, + "id": "bwW8j70Gy5jj3w6ZyN6AK", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1370.5585888882074, + "y": 274.7343765377857, + "strokeColor": "#495057", + "backgroundColor": "#ffffff", + "width": 22.91135804392394, + "height": 14.590977260318633, + "seed": 1625042967, + "groupIds": [ + "0rpvvbnQiKgAHji8s7Qu3", + "8XQjskjWZv-xjIxPq5m1P" + ], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "rectangle", + "version": 733, + "versionNonce": 863147799, + "isDeleted": false, + "id": "dqdgL2rHawoWupZIeQTE1", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1350.6592207728304, + "y": 234.9356403070326, + "strokeColor": "#495057", + "backgroundColor": "#ffffff", + "width": 22.91135804392394, + "height": 14.590977260318633, + "seed": 1313849561, + "groupIds": [ + "0rpvvbnQiKgAHji8s7Qu3", + "8XQjskjWZv-xjIxPq5m1P" + ], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "line", + "version": 771, + "versionNonce": 1677150681, + "isDeleted": false, + "id": "95FmJsSAqv9WpnUuhwhGx", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1341.7699687220288, + "y": 274.77068359957707, + "strokeColor": "#495057", + "backgroundColor": "#ffffff", + "width": 42.82342909333, + "height": 10.921302987215531, + "seed": 1345066295, + "groupIds": [ + "0rpvvbnQiKgAHji8s7Qu3", + "8XQjskjWZv-xjIxPq5m1P" + ], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.6478576310471701, + -10.921302987215531 + ], + [ + 42.296066688807485, + -10.68835017932191 + ], + [ + 42.82342909333, + -1.4299639028268747 + ] + ] + }, + { + "type": "line", + "version": 723, + "versionNonce": 1901960247, + "isDeleted": false, + "id": "mw7AcMiLMB5xYOVqrvIei", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1361.1867240928718, + "y": 264.49264351450324, + "strokeColor": "#495057", + "backgroundColor": "#ffffff", + "width": 0.17616130803224594, + "height": 14.56556303030538, + "seed": 674512313, + "groupIds": [ + "0rpvvbnQiKgAHji8s7Qu3", + "8XQjskjWZv-xjIxPq5m1P" + ], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.17616130803224594, + -14.56556303030538 + ] + ] + }, + { + "type": "text", + "version": 423, + "versionNonce": 302163641, + "isDeleted": false, + "id": "u11XXMZ5LcNtNmgWAnv3n", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1323.6148997947962, + "y": 316.53639837460264, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "width": 77, + "height": 26, + "seed": 1384273495, + "groupIds": [ + "RwpSM0R81XvdyvUMpoUbC", + "yhkcgK-l9x6mhGFLY3Jfa", + "8XQjskjWZv-xjIxPq5m1P" + ], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Storage", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "inlX1eg8mOf9IUm3InbSv", + "type": "arrow", + "x": 1247, + "y": 252.2329452965089, + "width": 64.04119567211796, + "height": 35.64586132003288, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1856013593, + "version": 198, + "versionNonce": 609183641, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 64.04119567211796, + -35.64586132003288 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "tz0dxRUO4ifmYOu4YJ7pK", + "focus": 0.6738835879223535, + "gap": 8.5 + }, + "endBinding": { + "elementId": "YrGORvo4mrQf5OKDyGc3_", + "focus": 0.6011130862592362, + "gap": 6.507655297480852 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "version": 721, + "versionNonce": 122564951, + "isDeleted": false, + "id": "1VQxI4u2cDSz1mSYccPwM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1437.5, + "y": 804, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 409, + "height": 26, + "seed": 667338553, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "try_undelegate_from_mixnode_on_behalf", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 717, + "versionNonce": 908498871, + "isDeleted": false, + "id": "NFtY5AR_vMtKJVxoRiy9_", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1421, + "y": 794.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 426.9999999999999, + "height": 43, + "seed": 844571863, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "FEiGi1WpRiafeEjW2e0ar", + "Adhr8zJSmeGoNUciUuifM", + "Qwwla_PjNOp6ryDhpZzFL", + "YAffQPR88P739qySe0Tju", + "W2QMkmcNKpOgJQVtbbzjS", + "5grdyB0Elp_zD4irC9Gsk", + "wdDSMsTbNCzlPn-WmEwrx", + "esBq5iMspILz9jRzm15K1" + ] + }, + { + "id": "5grdyB0Elp_zD4irC9Gsk", + "type": "arrow", + "x": 1856, + "y": 814, + "width": 90, + "height": 90, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 757605145, + "version": 40, + "versionNonce": 1934356087, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 90, + -90 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "NFtY5AR_vMtKJVxoRiy9_", + "focus": 0.9340425531914895, + "gap": 8 + }, + "endBinding": { + "elementId": "5XBuz5OPyN-8gquS8QAsJ", + "focus": 0.6757367741076844, + "gap": 8.573730543604587 + }, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "rectangle", + "version": 1006, + "versionNonce": 1322583959, + "isDeleted": false, + "id": "velWmWDcSiHCPQup-xZeu", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 834, + "y": 745, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 139.9999999999999, + "height": 57.00000000000001, + "seed": 735949529, + "groupIds": [ + "s7Pz73_VfEgY7ONAEZ2p5" + ], + "strokeSharpness": "round", + "boundElementIds": [ + "ZcsHHePHKfoC6K3HCq1eH", + "W2QMkmcNKpOgJQVtbbzjS", + "PUrPGopNGqDfT1zkMHLbv", + "esBq5iMspILz9jRzm15K1", + "eyOrkoJeisVmkl6epuFRs" + ] + }, + { + "type": "text", + "version": 889, + "versionNonce": 2056397145, + "isDeleted": false, + "id": "LvgqQ10r94Rj2zCe3MG09", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 847.5, + "y": 764, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 106, + "height": 26, + "seed": 1345209143, + "groupIds": [ + "s7Pz73_VfEgY7ONAEZ2p5" + ], + "strokeSharpness": "round", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Bank: Send", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 772, + "versionNonce": 1209924791, + "isDeleted": false, + "id": "KUjca258rJaXZznX7KWj1", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 790, + "y": 822, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 212.99999999999997, + "height": 79.00000000000001, + "seed": 867866937, + "groupIds": [ + "4-8LxHAwGyvkKWAS9cJGZ" + ], + "strokeSharpness": "round", + "boundElementIds": [ + "ZcsHHePHKfoC6K3HCq1eH", + "W2QMkmcNKpOgJQVtbbzjS", + "PUrPGopNGqDfT1zkMHLbv", + "wdDSMsTbNCzlPn-WmEwrx", + "KzqubZqd-7NgyjKNvp2-Y", + "OndtnXpIY7AoqlQyuQlRM" + ] + }, + { + "type": "text", + "version": 670, + "versionNonce": 1314295353, + "isDeleted": false, + "id": "sO377ffIuyZm8BKne1bEt", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 807, + "y": 841, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 179, + "height": 51, + "seed": 1841394391, + "groupIds": [ + "4-8LxHAwGyvkKWAS9cJGZ" + ], + "strokeSharpness": "round", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Execute:\nTrackUndelegation", + "baseline": 44, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "JNmV0EB6mOhwV5kCE4v0X", + "type": "text", + "x": 1023, + "y": 648, + "width": 334, + "height": 124, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 588791575, + "version": 946, + "versionNonce": 1027073847, + "isDeleted": false, + "boundElementIds": null, + "text": "Due to delegation rewards we can't\ndo accounting when we send the message. \nInstead we have to wait for the \nTrackUndelegation message from the \nmixnet contract specifying the amount \nreturned.", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 117 + }, + { + "id": "wdDSMsTbNCzlPn-WmEwrx", + "type": "arrow", + "x": 1414, + "y": 820.3714121162449, + "width": 401, + "height": 36.7940147553378, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 368386649, + "version": 185, + "versionNonce": 213540633, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + -401, + 36.7940147553378 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "NFtY5AR_vMtKJVxoRiy9_", + "focus": 0.3926038062283737, + "gap": 7 + }, + "endBinding": { + "elementId": "KUjca258rJaXZznX7KWj1", + "focus": 0.12897716490606612, + "gap": 10 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "esBq5iMspILz9jRzm15K1", + "type": "arrow", + "x": 1415, + "y": 796.7781708420412, + "width": 434, + "height": 12.167546819330028, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 411797687, + "version": 201, + "versionNonce": 2045868791, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + -434, + -12.167546819330028 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "NFtY5AR_vMtKJVxoRiy9_", + "focus": 0.49968146103206634, + "gap": 6 + }, + "endBinding": { + "elementId": "velWmWDcSiHCPQup-xZeu", + "focus": 0.29386503067484665, + "gap": 7.000000000000057 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "2tsaPZnEIAbBQH3RkxDWA", + "type": "arrow", + "x": 591, + "y": 300, + "width": 104, + "height": 14, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 167466583, + "version": 24, + "versionNonce": 1949373433, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 104, + 14 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "kdzYRrqFjIX7xFqmuBVL5", + "focus": -0.6108351076413532, + "gap": 12.5 + }, + "endBinding": { + "elementId": "H8iLjH4gabuF8pMHcZOx7", + "focus": -0.4403390567267985, + "gap": 5.5 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "version": 555, + "versionNonce": 1913979927, + "isDeleted": false, + "id": "D-6ZIhX8grr6SGNnNBnjo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 715.1111111111114, + "y": 373.5333333333333, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 180, + "height": 25, + "seed": 279060761, + "groupIds": [ + "hKVNcRCwNHDCHct_vMfYO" + ], + "strokeSharpness": "round", + "boundElementIds": [], + "fontSize": 19.11111111111111, + "fontFamily": 1, + "text": "track_undelegation", + "baseline": 17, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 615, + "versionNonce": 683551961, + "isDeleted": false, + "id": "EChQpkk9mxGBoBBVYAIYC", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 699.2, + "y": 364.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 203.6, + "height": 43, + "seed": 1524553975, + "groupIds": [ + "hKVNcRCwNHDCHct_vMfYO" + ], + "strokeSharpness": "sharp", + "boundElementIds": [ + "FEiGi1WpRiafeEjW2e0ar", + "Adhr8zJSmeGoNUciUuifM", + "PB32t0UgRYTpNSDkxZkdM", + "inlX1eg8mOf9IUm3InbSv", + "OndtnXpIY7AoqlQyuQlRM", + "vJsQiY14sEJV4oqTconnZ" + ] + }, + { + "id": "eyOrkoJeisVmkl6epuFRs", + "type": "arrow", + "x": 906.8587758762048, + "y": 738.4817813765183, + "width": 16.16044911259951, + "height": 306.44534412955466, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1923527289, + "version": 323, + "versionNonce": 1413452505, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + -16.16044911259951, + -306.44534412955466 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "velWmWDcSiHCPQup-xZeu", + "gap": 6.5182186234817845, + "focus": 0.06548454623814808 + }, + "endBinding": { + "elementId": "MYxtoDxJzXclc1V_xi1WF", + "gap": 13.036437246963569, + "focus": 0.49177335814704365 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "OndtnXpIY7AoqlQyuQlRM", + "type": "arrow", + "x": 780, + "y": 858, + "width": 289, + "height": 479, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 138006873, + "version": 223, + "versionNonce": 580266583, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + -174, + -16 + ], + [ + -257, + -129 + ], + [ + -289, + -298 + ], + [ + -257, + -420 + ], + [ + -196, + -470 + ], + [ + -91, + -479 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "KUjca258rJaXZznX7KWj1", + "focus": -0.14632155765419141, + "gap": 10 + }, + "endBinding": { + "elementId": "EChQpkk9mxGBoBBVYAIYC", + "focus": 0.5492012477549864, + "gap": 10.199999999999989 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "vJsQiY14sEJV4oqTconnZ", + "type": "arrow", + "x": 911, + "y": 388, + "width": 415.4526402695069, + "height": 167.18380058741542, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1042955223, + "version": 199, + "versionNonce": 409942647, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 346, + -12 + ], + [ + 415.4526402695069, + -167.18380058741542 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "EChQpkk9mxGBoBBVYAIYC", + "focus": 0.23231646768122302, + "gap": 8.199999999999989 + }, + "endBinding": { + "elementId": "YrGORvo4mrQf5OKDyGc3_", + "focus": 0.5723483437764005, + "gap": 4.0232110202697555 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "42AxuGNOt10Wjsqvb6hYX", + "type": "rectangle", + "x": 337, + "y": 56, + "width": 257, + "height": 311, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1929213815, + "version": 119, + "versionNonce": 1269050263, + "isDeleted": false, + "boundElementIds": null + }, + { + "id": "CbEEvfC7IJFj9CA86cPAx", + "type": "text", + "x": 396, + "y": 57.5, + "width": 193, + "height": 51, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1837936121, + "version": 165, + "versionNonce": 1835451353, + "isDeleted": false, + "boundElementIds": null, + "text": "Sent by the vesting\naccount address", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 44 + }, + { + "id": "UtRs1Ny8Lu12kDVC_-p4R", + "type": "arrow", + "x": 1323.0644228704543, + "y": 533, + "width": 93.93557712954566, + "height": 221.8731396845535, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1115651863, + "version": 193, + "versionNonce": 1941888889, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 93.93557712954566, + 221.8731396845535 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "cvM_i-TKjR7-aSkH5_qwN", + "focus": -0.12115907599012223, + "gap": 11 + }, + "endBinding": { + "elementId": "ISAmxS-jD85ZpBv7JUbZL", + "focus": -0.9609743972160079, + "gap": 4.5 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/contracts/vesting/images/vesting-coin-delegation.png b/contracts/vesting/images/vesting-coin-delegation.png new file mode 100644 index 0000000000..7e228cb7f3 Binary files /dev/null and b/contracts/vesting/images/vesting-coin-delegation.png differ diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs new file mode 100644 index 0000000000..8b3fdc9ac8 --- /dev/null +++ b/contracts/vesting/src/contract.rs @@ -0,0 +1,356 @@ +use crate::errors::ContractError; +use crate::messages::{ExecuteMsg, InitMsg, QueryMsg}; +use crate::storage::account_from_address; +use crate::traits::{BondingAccount, DelegatingAccount, VestingAccount}; +use crate::vesting::{populate_vesting_periods, Account}; +use config::defaults::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DENOM}; +use cosmwasm_std::{ + entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, + Response, Timestamp, Uint128, +}; +use mixnet_contract::{IdentityKey, MixNode}; + +// We're using a 24 month vesting period with 3 months sub-periods. +// There are 8 three month periods in two years +// and duration of a single period is 30 days. +pub const NUM_VESTING_PERIODS: usize = 8; +pub const VESTING_PERIOD: u64 = 3 * 30 * 86400; +// Address of the account set to be contract admin +pub const ADMIN_ADDRESS: &str = "admin"; + +#[entry_point] +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: InitMsg, +) -> Result { + Ok(Response::default()) +} + +#[entry_point] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::DelegateToMixnode { mix_identity } => { + try_delegate_to_mixnode(mix_identity, info, env, deps) + } + ExecuteMsg::UndelegateFromMixnode { mix_identity } => { + try_undelegate_from_mixnode(mix_identity, info, deps) + } + ExecuteMsg::CreateAccount { + address, + start_time, + } => try_create_periodic_vesting_account(&address, start_time, info, env, deps), + ExecuteMsg::WithdrawVestedCoins { amount } => { + try_withdraw_vested_coins(amount, env, info, deps) + } + ExecuteMsg::TrackUndelegation { + owner, + mix_identity, + amount, + } => try_track_undelegation(&owner, mix_identity, amount, info, deps), + ExecuteMsg::BondMixnode { mix_node } => try_bond_mixnode(mix_node, info, env, deps), + ExecuteMsg::UnbondMixnode {} => try_unbond_mixnode(info, deps), + ExecuteMsg::TrackUnbond { owner, amount } => try_track_unbond(&owner, amount, info, deps), + } +} + +pub fn try_bond_mixnode( + mix_node: MixNode, + info: MessageInfo, + env: Env, + deps: DepsMut, +) -> Result { + let bond = validate_funds(&info.funds)?; + let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; + account.try_bond_mixnode(mix_node, bond, &env, deps.storage) +} + +pub fn try_unbond_mixnode(info: MessageInfo, deps: DepsMut) -> Result { + let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; + account.try_unbond_mixnode(deps.storage) +} + +pub fn try_track_unbond( + owner: &str, + amount: Coin, + info: MessageInfo, + deps: DepsMut, +) -> Result { + if info.sender != DEFAULT_MIXNET_CONTRACT_ADDRESS { + return Err(ContractError::NotMixnetContract(info.sender)); + } + let account = account_from_address(owner, deps.storage, deps.api)?; + account.track_unbond(amount, deps.storage)?; + Ok(Response::default()) +} + +pub fn try_withdraw_vested_coins( + amount: Coin, + env: Env, + info: MessageInfo, + deps: DepsMut, +) -> Result { + let address = info.sender.clone(); + let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; + let spendable_coins = account.spendable_coins(None, &env, deps.storage)?; + if amount.amount <= spendable_coins.amount { + let new_balance = account + .load_balance(deps.storage)? + .u128() + .saturating_sub(amount.amount.u128()); + account.save_balance(Uint128::new(new_balance), deps.storage)?; + + let send_tokens = BankMsg::Send { + to_address: address.as_str().to_string(), + amount: vec![amount], + }; + + Ok(Response::new() + .add_attribute("action", "whitdraw") + .add_message(send_tokens)) + } else { + Err(ContractError::InsufficientSpendable( + address.as_str().to_string(), + spendable_coins.amount.u128(), + )) + } +} + +fn try_track_undelegation( + address: &str, + mix_identity: IdentityKey, + amount: Coin, + info: MessageInfo, + deps: DepsMut, +) -> Result { + if info.sender != DEFAULT_MIXNET_CONTRACT_ADDRESS { + return Err(ContractError::NotMixnetContract(info.sender)); + } + let account = account_from_address(address, deps.storage, deps.api)?; + account.track_undelegation(mix_identity, amount, deps.storage)?; + Ok(Response::default()) +} + +fn try_delegate_to_mixnode( + mix_identity: IdentityKey, + info: MessageInfo, + env: Env, + deps: DepsMut, +) -> Result { + let amount = validate_funds(&info.funds)?; + let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; + account.try_delegate_to_mixnode(mix_identity, amount, &env, deps.storage) +} + +fn try_undelegate_from_mixnode( + mix_identity: IdentityKey, + info: MessageInfo, + deps: DepsMut, +) -> Result { + let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; + account.try_undelegate_from_mixnode(mix_identity, deps.storage) +} + +fn try_create_periodic_vesting_account( + address: &str, + start_time: Option, + info: MessageInfo, + env: Env, + deps: DepsMut, +) -> Result { + if info.sender != ADMIN_ADDRESS { + return Err(ContractError::NotAdmin(info.sender.as_str().to_string())); + } + let coin = validate_funds(&info.funds)?; + let address = deps.api.addr_validate(address)?; + let start_time = start_time.unwrap_or_else(|| env.block.time.seconds()); + let periods = populate_vesting_periods(start_time, NUM_VESTING_PERIODS); + Account::new( + address, + coin, + Timestamp::from_seconds(start_time), + periods, + deps.storage, + )?; + Ok(Response::default()) +} + +#[entry_point] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { + let query_res = match msg { + QueryMsg::LockedCoins { + vesting_account_address, + block_time, + } => to_binary(&try_get_locked_coins( + &vesting_account_address, + block_time, + env, + deps, + )?), + QueryMsg::SpendableCoins { + vesting_account_address, + block_time, + } => to_binary(&try_get_spendable_coins( + &vesting_account_address, + block_time, + env, + deps, + )?), + QueryMsg::GetVestedCoins { + vesting_account_address, + block_time, + } => to_binary(&try_get_vested_coins( + &vesting_account_address, + block_time, + env, + deps, + )?), + QueryMsg::GetVestingCoins { + vesting_account_address, + block_time, + } => to_binary(&try_get_vesting_coins( + &vesting_account_address, + block_time, + env, + deps, + )?), + QueryMsg::GetStartTime { + vesting_account_address, + } => to_binary(&try_get_start_time(&vesting_account_address, deps)?), + QueryMsg::GetEndTime { + vesting_account_address, + } => to_binary(&try_get_end_time(&vesting_account_address, deps)?), + QueryMsg::GetOriginalVesting { + vesting_account_address, + } => to_binary(&try_get_original_vesting(&vesting_account_address, deps)?), + QueryMsg::GetDelegatedFree { + block_time, + vesting_account_address, + } => to_binary(&try_get_delegated_free( + block_time, + &vesting_account_address, + env, + deps, + )?), + QueryMsg::GetDelegatedVesting { + block_time, + vesting_account_address, + } => to_binary(&try_get_delegated_vesting( + block_time, + &vesting_account_address, + env, + deps, + )?), + }; + + Ok(query_res?) +} + +pub fn try_get_locked_coins( + vesting_account_address: &str, + block_time: Option, + env: Env, + deps: Deps, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + account.locked_coins(block_time, &env, deps.storage) +} + +pub fn try_get_spendable_coins( + vesting_account_address: &str, + block_time: Option, + env: Env, + deps: Deps, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + account.spendable_coins(block_time, &env, deps.storage) +} + +pub fn try_get_vested_coins( + vesting_account_address: &str, + block_time: Option, + env: Env, + deps: Deps, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + account.get_vested_coins(block_time, &env) +} + +pub fn try_get_vesting_coins( + vesting_account_address: &str, + block_time: Option, + env: Env, + deps: Deps, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + account.get_vesting_coins(block_time, &env) +} + +pub fn try_get_start_time( + vesting_account_address: &str, + deps: Deps, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + Ok(account.get_start_time()) +} + +pub fn try_get_end_time( + vesting_account_address: &str, + deps: Deps, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + Ok(account.get_end_time()) +} + +pub fn try_get_original_vesting( + vesting_account_address: &str, + deps: Deps, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + Ok(account.get_original_vesting()) +} + +pub fn try_get_delegated_free( + block_time: Option, + vesting_account_address: &str, + env: Env, + deps: Deps, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + account.get_delegated_free(block_time, &env, deps.storage) +} + +pub fn try_get_delegated_vesting( + block_time: Option, + vesting_account_address: &str, + env: Env, + deps: Deps, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + account.get_delegated_vesting(block_time, &env, deps.storage) +} + +fn validate_funds(funds: &[Coin]) -> Result { + if funds.is_empty() || funds[0].amount.is_zero() { + return Err(ContractError::EmptyFunds); + } + + if funds.len() > 1 { + return Err(ContractError::MultipleDenoms); + } + + if funds[0].denom != DENOM { + return Err(ContractError::WrongDenom( + funds[0].denom.clone(), + DENOM.to_string(), + )); + } + + Ok(funds[0].clone()) +} diff --git a/contracts/vesting/src/errors.rs b/contracts/vesting/src/errors.rs new file mode 100644 index 0000000000..978dfc115b --- /dev/null +++ b/contracts/vesting/src/errors.rs @@ -0,0 +1,41 @@ +use cosmwasm_std::{Addr, StdError}; +use mixnet_contract::IdentityKey; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + #[error("Account does not exist - {0}")] + NoAccountForAddress(String), + #[error("Only admin can perform this action, {0} is not admin")] + NotAdmin(String), + #[error("Balance not found for existing account ({0}), this is a bug")] + NoBalanceForAddress(String), + #[error("Insufficient balance for address {0} -> {1}")] + InsufficientBalance(String, u128), + #[error("Insufficient spendable balance for address {0} -> {1}")] + InsufficientSpendable(String, u128), + #[error( + "Only delegation owner can perform delegation actions, {0} is not the delegation owner" + )] + NotDelegate(String), + #[error("Total vesting amount is inprobably low -> {0}, this is likely an error")] + ImprobableVestingAmount(u128), + #[error("Address {0} has already bonded a node")] + AlreadyBonded(String), + #[error("Recieved empty funds vector")] + EmptyFunds, + #[error("Recieved wrong denom: {0}, expected {1}")] + WrongDenom(String, String), + #[error("Recieved multiple denoms, expected 1")] + MultipleDenoms, + #[error("No delegations found for account {0}, mix_identity {1}")] + NoSuchDelegation(Addr, IdentityKey), + #[error("Only mixnet contract can perform this operation, got {0}")] + NotMixnetContract(Addr), + #[error("Calculation underflowed")] + Underflow, + #[error("No bond found for account {0}")] + NoBondFound(String), +} diff --git a/contracts/vesting/src/lib.rs b/contracts/vesting/src/lib.rs new file mode 100644 index 0000000000..d437267939 --- /dev/null +++ b/contracts/vesting/src/lib.rs @@ -0,0 +1,7 @@ +pub mod contract; +mod errors; +pub mod messages; +mod storage; +mod support; +mod traits; +mod vesting; diff --git a/contracts/vesting/src/messages.rs b/contracts/vesting/src/messages.rs new file mode 100644 index 0000000000..44f89e7496 --- /dev/null +++ b/contracts/vesting/src/messages.rs @@ -0,0 +1,78 @@ +use cosmwasm_std::{Coin, Timestamp}; +use mixnet_contract::IdentityKey; +use mixnet_contract::MixNode; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct InitMsg {} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + DelegateToMixnode { + mix_identity: IdentityKey, + }, + UndelegateFromMixnode { + mix_identity: IdentityKey, + }, + CreateAccount { + address: String, + start_time: Option, + }, + WithdrawVestedCoins { + amount: Coin, + }, + TrackUndelegation { + owner: String, + mix_identity: IdentityKey, + amount: Coin, + }, + BondMixnode { + mix_node: MixNode, + }, + UnbondMixnode {}, + TrackUnbond { + owner: String, + amount: Coin, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + LockedCoins { + vesting_account_address: String, + block_time: Option, + }, + SpendableCoins { + vesting_account_address: String, + block_time: Option, + }, + GetVestedCoins { + vesting_account_address: String, + block_time: Option, + }, + GetVestingCoins { + vesting_account_address: String, + block_time: Option, + }, + GetStartTime { + vesting_account_address: String, + }, + GetEndTime { + vesting_account_address: String, + }, + GetOriginalVesting { + vesting_account_address: String, + }, + GetDelegatedFree { + block_time: Option, + vesting_account_address: String, + }, + GetDelegatedVesting { + block_time: Option, + vesting_account_address: String, + }, +} diff --git a/contracts/vesting/src/storage.rs b/contracts/vesting/src/storage.rs new file mode 100644 index 0000000000..0b3a3327bc --- /dev/null +++ b/contracts/vesting/src/storage.rs @@ -0,0 +1,30 @@ +use crate::errors::ContractError; +use crate::vesting::Account; +use cosmwasm_std::{Addr, Api, Storage}; +use cw_storage_plus::Map; + +const ACCOUNTS: Map = Map::new("acc"); + +pub fn save_account(account: &Account, storage: &mut dyn Storage) -> Result<(), ContractError> { + Ok(ACCOUNTS.save(storage, account.address(), account)?) +} + +pub fn load_account( + address: &Addr, + storage: &dyn Storage, +) -> Result, ContractError> { + Ok(ACCOUNTS.may_load(storage, address.to_owned())?) +} + +fn validate_account(address: &Addr, storage: &dyn Storage) -> Result { + load_account(address, storage)? + .ok_or_else(|| ContractError::NoAccountForAddress(address.as_str().to_string())) +} + +pub fn account_from_address( + address: &str, + storage: &dyn Storage, + api: &dyn Api, +) -> Result { + validate_account(&api.addr_validate(address)?, storage) +} diff --git a/contracts/vesting/src/support/mod.rs b/contracts/vesting/src/support/mod.rs new file mode 100644 index 0000000000..15ab560571 --- /dev/null +++ b/contracts/vesting/src/support/mod.rs @@ -0,0 +1 @@ +pub mod tests; diff --git a/contracts/vesting/src/support/tests.rs b/contracts/vesting/src/support/tests.rs new file mode 100644 index 0000000000..40686d794c --- /dev/null +++ b/contracts/vesting/src/support/tests.rs @@ -0,0 +1,35 @@ +#[cfg(test)] +pub mod helpers { + use crate::contract::{instantiate, NUM_VESTING_PERIODS}; + use crate::messages::InitMsg; + use crate::vesting::{populate_vesting_periods, Account}; + use config::defaults::DENOM; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}; + use cosmwasm_std::{Addr, Coin, Empty, Env, MemoryStorage, OwnedDeps, Storage, Uint128}; + + pub fn init_contract() -> OwnedDeps> { + let mut deps = mock_dependencies(); + let msg = InitMsg {}; + let env = mock_env(); + let info = mock_info("creator", &[]); + instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); + return deps; + } + + pub fn vesting_account_fixture(storage: &mut dyn Storage, env: &Env) -> Account { + let start_time = env.block.time; + let periods = populate_vesting_periods(start_time.seconds(), NUM_VESTING_PERIODS); + + Account::new( + Addr::unchecked("fixture"), + Coin { + amount: Uint128::new(1_000_000_000_000), + denom: DENOM.to_string(), + }, + start_time, + periods, + storage, + ) + .unwrap() + } +} diff --git a/contracts/vesting/src/traits/bonding_account.rs b/contracts/vesting/src/traits/bonding_account.rs new file mode 100644 index 0000000000..b526534589 --- /dev/null +++ b/contracts/vesting/src/traits/bonding_account.rs @@ -0,0 +1,17 @@ +use crate::errors::ContractError; +use cosmwasm_std::{Coin, Env, Response, Storage}; +use mixnet_contract::MixNode; + +pub trait BondingAccount { + fn try_bond_mixnode( + &self, + mix_node: MixNode, + amount: Coin, + env: &Env, + storage: &mut dyn Storage, + ) -> Result; + + fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result; + + fn track_unbond(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError>; +} diff --git a/contracts/vesting/src/traits/delegating_account.rs b/contracts/vesting/src/traits/delegating_account.rs new file mode 100644 index 0000000000..bc133911d9 --- /dev/null +++ b/contracts/vesting/src/traits/delegating_account.rs @@ -0,0 +1,41 @@ +use crate::errors::ContractError; +use cosmwasm_std::{Coin, Env, Response, Storage, Timestamp, Uint128}; +use mixnet_contract::IdentityKey; + +pub trait DelegatingAccount { + fn try_delegate_to_mixnode( + &self, + mix_identity: IdentityKey, + amount: Coin, + env: &Env, + storage: &mut dyn Storage, + ) -> Result; + + fn try_undelegate_from_mixnode( + &self, + mix_identity: IdentityKey, + storage: &dyn Storage, + ) -> Result; + + // track_delegation performs internal vesting accounting necessary when + // delegating from a vesting account. It accepts the current block time, the + // delegation amount and balance of all coins whose denomination exists in + // the account's original vesting balance. + fn track_delegation( + &self, + block_time: Timestamp, + mix_identity: IdentityKey, + // Save some gas by passing it in + current_balance: Uint128, + delegation: Coin, + storage: &mut dyn Storage, + ) -> Result<(), ContractError>; + // track_undelegation performs internal vesting accounting necessary when a + // vesting account performs an undelegation. + fn track_undelegation( + &self, + mix_identity: IdentityKey, + amount: Coin, + storage: &mut dyn Storage, + ) -> Result<(), ContractError>; +} diff --git a/contracts/vesting/src/traits/mod.rs b/contracts/vesting/src/traits/mod.rs new file mode 100644 index 0000000000..87587c7dde --- /dev/null +++ b/contracts/vesting/src/traits/mod.rs @@ -0,0 +1,7 @@ +pub mod bonding_account; +pub mod delegating_account; +pub mod vesting_account; + +pub use self::bonding_account::BondingAccount; +pub use self::delegating_account::DelegatingAccount; +pub use self::vesting_account::VestingAccount; diff --git a/contracts/vesting/src/traits/vesting_account.rs b/contracts/vesting/src/traits/vesting_account.rs new file mode 100644 index 0000000000..2b35348690 --- /dev/null +++ b/contracts/vesting/src/traits/vesting_account.rs @@ -0,0 +1,65 @@ +use crate::errors::ContractError; +use cosmwasm_std::{Coin, Env, Storage, Timestamp}; + +pub trait VestingAccount { + // locked_coins returns the set of coins that are not spendable (can still be delegated tough) (i.e. locked), + // defined as the vesting coins that are not delegated or bonded. + // + // To get spendable coins of a vesting account, first the total balance must + // be retrieved and the locked tokens can be subtracted from the total balance. + // Note, the spendable balance can be negative. + fn locked_coins( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result; + + // Calculates the total spendable balance that can be sent to other accounts. + fn spendable_coins( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result; + + fn get_vested_coins( + &self, + block_time: Option, + env: &Env, + ) -> Result; + fn get_vesting_coins( + &self, + block_time: Option, + env: &Env, + ) -> Result; + + fn get_start_time(&self) -> Timestamp; + fn get_end_time(&self) -> Timestamp; + + fn get_original_vesting(&self) -> Coin; + fn get_delegated_free( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result; + fn get_delegated_vesting( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result; + fn get_bonded_free( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result; + fn get_bonded_vesting( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result; +} diff --git a/contracts/vesting/src/vesting/account/bonding_account.rs b/contracts/vesting/src/vesting/account/bonding_account.rs new file mode 100644 index 0000000000..625ef3fd49 --- /dev/null +++ b/contracts/vesting/src/vesting/account/bonding_account.rs @@ -0,0 +1,80 @@ +use super::BondData; +use crate::errors::ContractError; +use crate::traits::BondingAccount; +use config::defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS; +use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128}; +use mixnet_contract::{ExecuteMsg as MixnetExecuteMsg, MixNode}; + +use super::Account; + +impl BondingAccount for Account { + fn try_bond_mixnode( + &self, + mix_node: MixNode, + bond: Coin, + env: &Env, + storage: &mut dyn Storage, + ) -> Result { + let current_balance = self.load_balance(storage)?; + + if current_balance < bond.amount { + return Err(ContractError::InsufficientBalance( + self.address.as_str().to_string(), + current_balance.u128(), + )); + } + + let bond_data = if let Some(_bond) = self.load_bond(storage)? { + return Err(ContractError::AlreadyBonded( + self.address.as_str().to_string(), + )); + } else { + BondData { + block_time: env.block.time, + amount: bond.amount, + } + }; + + let msg = MixnetExecuteMsg::BondMixnodeOnBehalf { + mix_node, + owner: self.address().into_string(), + }; + + let new_balance = Uint128::new(current_balance.u128() - bond.amount.u128()); + + let bond_mixnode_mag = wasm_execute(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg, vec![bond])?; + + self.save_balance(new_balance, storage)?; + self.save_bond(bond_data, storage)?; + + Ok(Response::new() + .add_attribute("action", "bond mixnode on behalf") + .add_message(bond_mixnode_mag)) + } + + fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result { + let msg = MixnetExecuteMsg::UnbondMixnodeOnBehalf { + owner: self.address().into_string(), + }; + + if let Some(_bond) = self.load_bond(storage)? { + let unbond_msg = wasm_execute(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg, vec![])?; + + Ok(Response::new() + .add_attribute("action", "unbond mixnode on behalf") + .add_message(unbond_msg)) + } else { + Err(ContractError::NoBondFound( + self.address.as_str().to_string(), + )) + } + } + + fn track_unbond(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> { + let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128()); + self.save_balance(new_balance, storage)?; + + self.remove_bond(storage)?; + Ok(()) + } +} diff --git a/contracts/vesting/src/vesting/account/delegating_account.rs b/contracts/vesting/src/vesting/account/delegating_account.rs new file mode 100644 index 0000000000..002ad58b72 --- /dev/null +++ b/contracts/vesting/src/vesting/account/delegating_account.rs @@ -0,0 +1,125 @@ +use crate::errors::ContractError; +use crate::traits::DelegatingAccount; +use config::defaults::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DENOM}; +use cosmwasm_std::{wasm_execute, Coin, Env, Order, Response, Storage, Timestamp, Uint128}; +use mixnet_contract::ExecuteMsg as MixnetExecuteMsg; +use mixnet_contract::IdentityKey; + +use super::Account; + +impl DelegatingAccount for Account { + fn try_delegate_to_mixnode( + &self, + mix_identity: IdentityKey, + coin: Coin, + env: &Env, + storage: &mut dyn Storage, + ) -> Result { + let current_balance = self.load_balance(storage)?; + + if current_balance < coin.amount { + return Err(ContractError::InsufficientBalance( + self.address.as_str().to_string(), + current_balance.u128(), + )); + } + + let msg = MixnetExecuteMsg::DelegateToMixnodeOnBehalf { + mix_identity: mix_identity.clone(), + delegate: self.address().into_string(), + }; + let delegate_to_mixnode = + wasm_execute(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg, vec![coin.clone()])?; + self.track_delegation(env.block.time, mix_identity, current_balance, coin, storage)?; + + Ok(Response::new() + .add_attribute("action", "delegate to mixnode on behalf") + .add_message(delegate_to_mixnode)) + } + + fn try_undelegate_from_mixnode( + &self, + mix_identity: IdentityKey, + storage: &dyn Storage, + ) -> Result { + if !self.any_delegation_for_mix(&mix_identity, storage) { + return Err(ContractError::NoSuchDelegation( + self.address(), + mix_identity, + )); + } + + let msg = MixnetExecuteMsg::UndelegateFromMixnodeOnBehalf { + mix_identity, + delegate: self.address().into_string(), + }; + let undelegate_from_mixnode = wasm_execute( + DEFAULT_MIXNET_CONTRACT_ADDRESS, + &msg, + vec![Coin { + amount: Uint128::new(0), + denom: DENOM.to_string(), + }], + )?; + + Ok(Response::new() + .add_attribute("action", "undelegate to mixnode on behalf") + .add_message(undelegate_from_mixnode)) + } + + fn track_delegation( + &self, + block_time: Timestamp, + mix_identity: IdentityKey, + current_balance: Uint128, + delegation: Coin, + storage: &mut dyn Storage, + ) -> Result<(), ContractError> { + let delegation_key = (mix_identity.as_bytes(), block_time.seconds()); + + let new_delegation = if let Some(existing_delegation) = + self.delegations().may_load(storage, delegation_key)? + { + existing_delegation + delegation.amount + } else { + delegation.amount + }; + + self.delegations() + .save(storage, delegation_key, &new_delegation)?; + + let new_balance = Uint128::new(current_balance.u128() - delegation.amount.u128()); + + self.save_balance(new_balance, storage)?; + + Ok(()) + } + + fn track_undelegation( + &self, + mix_identity: IdentityKey, + amount: Coin, + storage: &mut dyn Storage, + ) -> Result<(), ContractError> { + let mix_bytes = mix_identity.as_bytes(); + + // Iterate over keys matching the prefix and remove them from the map + let block_times = self + .delegations() + .prefix_de(mix_bytes) + .keys_de(storage, None, None, Order::Ascending) + // Scan will blow up on first error + .scan((), |_, x| x.ok()) + .collect::>(); + + for t in block_times { + self.delegations().remove(storage, (mix_bytes, t)) + } + + let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128()); + + self.save_balance(new_balance, storage)?; + + Ok(()) + } +} diff --git a/contracts/vesting/src/vesting/account/mod.rs b/contracts/vesting/src/vesting/account/mod.rs new file mode 100644 index 0000000000..998f2e4fe3 --- /dev/null +++ b/contracts/vesting/src/vesting/account/mod.rs @@ -0,0 +1,191 @@ +use super::{BondData, VestingPeriod}; +use crate::contract::NUM_VESTING_PERIODS; +use crate::errors::ContractError; +use crate::storage::save_account; +use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128}; +use cw_storage_plus::{Item, Map}; +use mixnet_contract::IdentityKey; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +mod bonding_account; +mod delegating_account; +mod vesting_account; + +const DELEGATIONS_SUFFIX: &str = "de"; +const BALANCE_SUFFIX: &str = "ba"; +const BOND_SUFFIX: &str = "bo"; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Account { + address: Addr, + start_time: Timestamp, + periods: Vec, + coin: Coin, + delegations_key: String, + balance_key: String, + bond_key: String, +} + +impl Account { + pub fn new( + address: Addr, + coin: Coin, + start_time: Timestamp, + periods: Vec, + storage: &mut dyn Storage, + ) -> Result { + let amount = coin.amount; + let account = Account { + address: address.to_owned(), + start_time, + periods, + coin, + delegations_key: format!("{}_{}", address, DELEGATIONS_SUFFIX), + balance_key: format!("{}_{}", address, BALANCE_SUFFIX), + bond_key: format!("{}_{}", address, BOND_SUFFIX), + }; + save_account(&account, storage)?; + account.save_balance(amount, storage)?; + Ok(account) + } + + pub fn address(&self) -> Addr { + self.address.clone() + } + + #[allow(dead_code)] + pub fn periods(&self) -> Vec { + self.periods.clone() + } + + #[allow(dead_code)] + pub fn start_time(&self) -> Timestamp { + self.start_time + } + + pub fn tokens_per_period(&self) -> Result { + let amount = self.coin.amount.u128(); + if amount < NUM_VESTING_PERIODS as u128 { + Err(ContractError::ImprobableVestingAmount(amount)) + } else { + // Remainder tokens will be lumped into the last period. + Ok(amount / NUM_VESTING_PERIODS as u128) + } + } + + pub fn get_current_vesting_period(&self, block_time: Timestamp) -> usize { + // Returns the index of the next vesting period. Unless the current time is somehow in the past or vesting has not started yet. + // In case vesting is over it will always return NUM_VESTING_PERIODS. + let period = match self + .periods + .iter() + .map(|period| period.start_time) + .collect::>() + .binary_search(&block_time.seconds()) + { + Ok(u) => u, + Err(u) => u, + }; + + if period > 0 { + period - 1 + } else { + 0 + } + } + + pub fn load_balance(&self, storage: &dyn Storage) -> Result { + Ok(self + .balance() + .may_load(storage)? + .unwrap_or_else(Uint128::zero)) + } + + pub fn save_balance( + &self, + amount: Uint128, + storage: &mut dyn Storage, + ) -> Result<(), ContractError> { + Ok(self.balance().save(storage, &amount)?) + } + + fn balance(&self) -> Item { + Item::new(self.balance_key.as_ref()) + } + + pub fn load_bond(&self, storage: &dyn Storage) -> Result, ContractError> { + Ok(self.bond().may_load(storage)?) + } + + pub fn save_bond( + &self, + bond: BondData, + storage: &mut dyn Storage, + ) -> Result<(), ContractError> { + Ok(self.bond().save(storage, &bond)?) + } + + pub fn remove_bond(&self, storage: &mut dyn Storage) -> Result<(), ContractError> { + self.bond().remove(storage); + Ok(()) + } + + fn bond(&self) -> Item { + Item::new(self.bond_key.as_ref()) + } + + fn delegations(&self) -> Map<(&[u8], u64), Uint128> { + Map::new(self.delegations_key.as_ref()) + } + + // Returns block_time part of the delegation key + pub fn delegation_keys_for_mix(&self, mix: &str, storage: &dyn Storage) -> Vec { + self.delegations() + .prefix_de(mix.as_bytes()) + .keys_de(storage, None, None, Order::Ascending) + // Scan will blow up on first error + .scan((), |_, x| x.ok()) + .collect::>() + } + + pub fn any_delegation_for_mix(&self, mix: &str, storage: &dyn Storage) -> bool { + !self.delegation_keys_for_mix(mix, storage).is_empty() + } + + pub fn delegations_for_mix( + &self, + mix: IdentityKey, + storage: &dyn Storage, + ) -> Result, ContractError> { + let mix_bytes = mix.as_bytes(); + let keys = self.delegation_keys_for_mix(&mix, storage); + + let mut delegation_amounts = Vec::new(); + for key in keys { + delegation_amounts.push(self.delegations().load(storage, (mix_bytes, key))?) + } + + Ok(delegation_amounts) + } + + #[allow(dead_code)] + pub fn total_delegations_for_mix( + &self, + mix: IdentityKey, + storage: &dyn Storage, + ) -> Result { + Ok(self + .delegations_for_mix(mix, storage)? + .iter() + .fold(Uint128::zero(), |acc, x| acc + *x)) + } + + pub fn total_delegations(&self, storage: &dyn Storage) -> Result { + Ok(self + .delegations() + .range(storage, None, None, Order::Ascending) + .scan((), |_, x| x.ok()) + .fold(Uint128::zero(), |acc, (_, x)| acc + x)) + } +} diff --git a/contracts/vesting/src/vesting/account/vesting_account.rs b/contracts/vesting/src/vesting/account/vesting_account.rs new file mode 100644 index 0000000000..29c254c56a --- /dev/null +++ b/contracts/vesting/src/vesting/account/vesting_account.rs @@ -0,0 +1,206 @@ +use crate::contract::NUM_VESTING_PERIODS; +use crate::errors::ContractError; +use crate::traits::VestingAccount; +use config::defaults::DENOM; +use cosmwasm_std::{Coin, Env, Order, Storage, Timestamp, Uint128}; + +use super::Account; + +impl VestingAccount for Account { + fn locked_coins( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result { + // Returns 0 in case of underflow. + Ok(Coin { + amount: Uint128::new( + self.get_vesting_coins(block_time, env)? + .amount + .u128() + .checked_sub( + self.get_delegated_vesting(block_time, env, storage)? + .amount + .u128(), + ) + .ok_or(ContractError::Underflow)? + .checked_sub( + self.get_bonded_vesting(block_time, env, storage)? + .amount + .u128(), + ) + .ok_or(ContractError::Underflow)?, + ), + denom: DENOM.to_string(), + }) + } + + fn spendable_coins( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result { + Ok(Coin { + amount: Uint128::new( + self.load_balance(storage)? + .u128() + .saturating_sub(self.locked_coins(block_time, env, storage)?.amount.u128()), + ), + denom: DENOM.to_string(), + }) + } + + fn get_vested_coins( + &self, + block_time: Option, + env: &Env, + ) -> Result { + let block_time = block_time.unwrap_or(env.block.time); + let period = self.get_current_vesting_period(block_time); + + let amount = match period { + // We're in the first period, or the vesting has not started yet. + 0 => Coin { + amount: Uint128::new(0), + denom: DENOM.to_string(), + }, + // We always have 8 vesting periods, so periods 1-7 are special + 1..=7 => Coin { + amount: Uint128::new(self.tokens_per_period()? * period as u128), + denom: DENOM.to_string(), + }, + _ => Coin { + amount: self.coin.amount, + denom: DENOM.to_string(), + }, + }; + Ok(amount) + } + + fn get_vesting_coins( + &self, + block_time: Option, + env: &Env, + ) -> Result { + Ok(Coin { + amount: self.get_original_vesting().amount + - self.get_vested_coins(block_time, env)?.amount, + denom: DENOM.to_string(), + }) + } + + fn get_start_time(&self) -> Timestamp { + self.start_time + } + + fn get_end_time(&self) -> Timestamp { + self.periods[(NUM_VESTING_PERIODS - 1) as usize].end_time() + } + + fn get_original_vesting(&self) -> Coin { + self.coin.clone() + } + + fn get_delegated_free( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result { + let block_time = block_time.unwrap_or(env.block.time); + let period = self.get_current_vesting_period(block_time); + let max_vested = self.tokens_per_period()? * period as u128; + let start_time = self.periods[period].start_time; + + let delegations_keys = self + .delegations() + .keys_de(storage, None, None, Order::Ascending) + .scan((), |_, x| x.ok()) + .filter(|(_mix, block_time)| *block_time < start_time) + .map(|(mix, block_time)| (mix, block_time)) + .collect::, u64)>>(); + + let mut amount = Uint128::zero(); + for (mix, block_time) in delegations_keys { + amount += self.delegations().load(storage, (&mix, block_time))? + } + amount = Uint128::new(amount.u128().min(max_vested)); + + Ok(Coin { + amount, + denom: DENOM.to_string(), + }) + } + + fn get_delegated_vesting( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result { + let block_time = block_time.unwrap_or(env.block.time); + let delegated_free = self.get_delegated_free(Some(block_time), env, storage)?; + let total_delegations = self.total_delegations(storage)?; + + let amount = total_delegations - delegated_free.amount; + + Ok(Coin { + amount, + denom: DENOM.to_string(), + }) + } + + fn get_bonded_free( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result { + let block_time = block_time.unwrap_or(env.block.time); + let period = self.get_current_vesting_period(block_time); + let max_vested = self.tokens_per_period()? * period as u128; + let start_time = self.periods[period].start_time; + + let amount = if let Some(bond) = self.load_bond(storage)? { + if bond.block_time.seconds() < start_time { + bond.amount + } else { + Uint128::zero() + } + } else { + Uint128::zero() + }; + + let amount = Uint128::new(amount.u128().min(max_vested)); + + Ok(Coin { + amount, + denom: DENOM.to_string(), + }) + } + + fn get_bonded_vesting( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result { + let block_time = block_time.unwrap_or(env.block.time); + let bonded_free = self.get_bonded_free(Some(block_time), env, storage)?; + + if let Some(bond) = self.load_bond(storage)? { + let amount = bond.amount - bonded_free.amount; + Ok(Coin { + amount, + denom: DENOM.to_string(), + }) + } else { + Ok(Coin { + amount: Uint128::zero(), + denom: DENOM.to_string(), + }) + } + } +} diff --git a/contracts/vesting/src/vesting/mod.rs b/contracts/vesting/src/vesting/mod.rs new file mode 100644 index 0000000000..e12d6fa59f --- /dev/null +++ b/contracts/vesting/src/vesting/mod.rs @@ -0,0 +1,344 @@ +use crate::contract::VESTING_PERIOD; +use cosmwasm_std::{Timestamp, Uint128}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +mod account; +pub use account::*; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct VestingPeriod { + pub start_time: u64, +} + +impl VestingPeriod { + pub fn end_time(&self) -> Timestamp { + Timestamp::from_seconds(self.start_time + VESTING_PERIOD) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct BondData { + amount: Uint128, + block_time: Timestamp, +} + +pub fn populate_vesting_periods(start_time: u64, n: usize) -> Vec { + let mut periods = Vec::with_capacity(n as usize); + for i in 0..n { + let period = VestingPeriod { + start_time: start_time + i as u64 * VESTING_PERIOD, + }; + periods.push(period); + } + periods +} + +#[cfg(test)] +mod tests { + use crate::contract::{NUM_VESTING_PERIODS, VESTING_PERIOD}; + use crate::storage::load_account; + use crate::support::tests::helpers::{init_contract, vesting_account_fixture}; + use crate::traits::BondingAccount; + use crate::traits::DelegatingAccount; + use crate::traits::VestingAccount; + use config::defaults::DENOM; + use cosmwasm_std::testing::mock_env; + use cosmwasm_std::{Addr, Coin, Timestamp, Uint128}; + use mixnet_contract::MixNode; + + #[test] + fn test_account_creation() { + let mut deps = init_contract(); + let env = mock_env(); + let account = vesting_account_fixture(&mut deps.storage, &env); + let created_account = load_account(&account.address(), &deps.storage).unwrap(); + let created_account_test = + load_account(&Addr::unchecked("fixture"), &deps.storage).unwrap(); + assert_eq!(Some(&account), created_account.as_ref()); + assert_eq!(Some(&account), created_account_test.as_ref()); + assert_eq!( + account.load_balance(&deps.storage).unwrap(), + Uint128::new(1_000_000_000_000) + ); + assert_eq!( + account.load_balance(&deps.storage).unwrap(), + Uint128::new(1_000_000_000_000) + ) + } + + #[test] + fn test_period_logic() { + let mut deps = init_contract(); + let env = mock_env(); + + let account = vesting_account_fixture(&mut deps.storage, &env); + + assert_eq!(account.periods().len(), NUM_VESTING_PERIODS as usize); + assert_eq!(account.periods().len(), 8); + + let current_period = account.get_current_vesting_period(Timestamp::from_seconds(0)); + assert_eq!(0, current_period); + + let block_time = + Timestamp::from_seconds(account.start_time().seconds() + VESTING_PERIOD + 1); + let current_period = account.get_current_vesting_period(block_time); + assert_eq!(current_period, 1); + let vested_coins = account.get_vested_coins(Some(block_time), &env).unwrap(); + let vesting_coins = account.get_vesting_coins(Some(block_time), &env).unwrap(); + assert_eq!( + vested_coins.amount, + Uint128::new( + account.get_original_vesting().amount.u128() / NUM_VESTING_PERIODS as u128 + ) + ); + assert_eq!( + vesting_coins.amount, + Uint128::new( + account.get_original_vesting().amount.u128() + - account.get_original_vesting().amount.u128() / NUM_VESTING_PERIODS as u128 + ) + ); + + let block_time = + Timestamp::from_seconds(account.start_time().seconds() + 5 * VESTING_PERIOD + 1); + let current_period = account.get_current_vesting_period(block_time); + assert_eq!(current_period, 5); + let vested_coins = account.get_vested_coins(Some(block_time), &env).unwrap(); + let vesting_coins = account.get_vesting_coins(Some(block_time), &env).unwrap(); + assert_eq!( + vested_coins.amount, + Uint128::new( + 5 * account.get_original_vesting().amount.u128() / NUM_VESTING_PERIODS as u128 + ) + ); + assert_eq!( + vesting_coins.amount, + Uint128::new( + account.get_original_vesting().amount.u128() + - 5 * account.get_original_vesting().amount.u128() + / NUM_VESTING_PERIODS as u128 + ) + ); + } + + #[test] + fn test_delegations() { + let mut deps = init_contract(); + let env = mock_env(); + + let account = vesting_account_fixture(&mut deps.storage, &env); + + // Try delegating too much + let err = account.try_delegate_to_mixnode( + "alice".to_string(), + Coin { + amount: Uint128::new(1_000_000_000_001), + denom: DENOM.to_string(), + }, + &env, + &mut deps.storage, + ); + assert!(err.is_err()); + + let ok = account.try_delegate_to_mixnode( + "alice".to_string(), + Coin { + amount: Uint128::new(500_000_000_000), + denom: DENOM.to_string(), + }, + &env, + &mut deps.storage, + ); + assert!(ok.is_ok()); + + let balance = account.load_balance(&deps.storage).unwrap(); + assert_eq!(balance, Uint128::new(500_000_000_000)); + + // Try delegating too much again + let err = account.try_delegate_to_mixnode( + "alice".to_string(), + Coin { + amount: Uint128::new(500_000_000_001), + denom: DENOM.to_string(), + }, + &env, + &mut deps.storage, + ); + assert!(err.is_err()); + + let total_delegations = account + .total_delegations_for_mix("alice".to_string(), &deps.storage) + .unwrap(); + assert_eq!(Uint128::new(500_000_000_000), total_delegations); + + // Current period -> block_time: None + let delegated_free = account + .get_delegated_free(None, &env, &deps.storage) + .unwrap(); + assert_eq!(Uint128::new(0), delegated_free.amount); + + let delegated_vesting = account + .get_delegated_vesting(None, &env, &deps.storage) + .unwrap(); + assert_eq!( + account.total_delegations(&deps.storage).unwrap() - delegated_free.amount, + delegated_vesting.amount + ); + + // All periods + for (i, period) in account.periods().iter().enumerate() { + let delegated_free = account + .get_delegated_free( + Some(Timestamp::from_seconds(period.start_time + 1)), + &env, + &deps.storage, + ) + .unwrap(); + assert_eq!( + (account.tokens_per_period().unwrap() * i as u128) + .min(account.total_delegations(&deps.storage).unwrap().u128()), + delegated_free.amount.u128() + ); + + let delegated_vesting = account + .get_delegated_vesting( + Some(Timestamp::from_seconds(period.start_time + 1)), + &env, + &deps.storage, + ) + .unwrap(); + assert_eq!( + account.total_delegations(&deps.storage).unwrap() - delegated_free.amount, + delegated_vesting.amount + ); + } + + let delegated_free = account + .get_delegated_free( + Some(Timestamp::from_seconds(1764416964)), + &env, + &deps.storage, + ) + .unwrap(); + assert_eq!(total_delegations, delegated_free.amount); + + let delegated_free = account + .get_delegated_vesting( + Some(Timestamp::from_seconds(1764416964)), + &env, + &deps.storage, + ) + .unwrap(); + assert_eq!(Uint128::zero(), delegated_free.amount); + } + + #[test] + fn test_bonds() { + let mut deps = init_contract(); + let env = mock_env(); + + let account = vesting_account_fixture(&mut deps.storage, &env); + + let mix_node = MixNode { + host: "mix.node.org".to_string(), + mix_port: 1789, + verloc_port: 1790, + http_api_port: 8000, + sphinx_key: "sphinx".to_string(), + identity_key: "identity".to_string(), + version: "0.10.0".to_string(), + }; + // Try delegating too much + let err = account.try_bond_mixnode( + mix_node.clone(), + Coin { + amount: Uint128::new(1_000_000_000_001), + denom: DENOM.to_string(), + }, + &env, + &mut deps.storage, + ); + assert!(err.is_err()); + + let ok = account.try_bond_mixnode( + mix_node.clone(), + Coin { + amount: Uint128::new(500_000_000_000), + denom: DENOM.to_string(), + }, + &env, + &mut deps.storage, + ); + assert!(ok.is_ok()); + + let balance = account.load_balance(&deps.storage).unwrap(); + assert_eq!(balance, Uint128::new(500_000_000_000)); + + // Try delegating too much again + let err = account.try_bond_mixnode( + mix_node, + Coin { + amount: Uint128::new(500_000_000_001), + denom: DENOM.to_string(), + }, + &env, + &mut deps.storage, + ); + assert!(err.is_err()); + + let bond = account.load_bond(&deps.storage).unwrap().unwrap(); + assert_eq!(Uint128::new(500_000_000_000), bond.amount); + + // Current period -> block_time: None + let bonded_free = account.get_bonded_free(None, &env, &deps.storage).unwrap(); + assert_eq!(Uint128::new(0), bonded_free.amount); + + let bonded_vesting = account + .get_bonded_vesting(None, &env, &deps.storage) + .unwrap(); + assert_eq!(bond.amount - bonded_free.amount, bonded_vesting.amount); + + // All periods + for (i, period) in account.periods().iter().enumerate() { + let bonded_free = account + .get_bonded_free( + Some(Timestamp::from_seconds(period.start_time + 1)), + &env, + &deps.storage, + ) + .unwrap(); + assert_eq!( + (account.tokens_per_period().unwrap() * i as u128).min(bond.amount.u128()), + bonded_free.amount.u128() + ); + + let bonded_vesting = account + .get_bonded_vesting( + Some(Timestamp::from_seconds(period.start_time + 1)), + &env, + &deps.storage, + ) + .unwrap(); + assert_eq!(bond.amount - bonded_free.amount, bonded_vesting.amount); + } + + let bonded_free = account + .get_bonded_free( + Some(Timestamp::from_seconds(1764416964)), + &env, + &deps.storage, + ) + .unwrap(); + assert_eq!(bond.amount, bonded_free.amount); + + let bonded_vesting = account + .get_bonded_vesting( + Some(Timestamp::from_seconds(1764416964)), + &env, + &deps.storage, + ) + .unwrap(); + assert_eq!(Uint128::zero(), bonded_vesting.amount); + } +} diff --git a/explorer-api/src/mix_node/http.rs b/explorer-api/src/mix_node/http.rs index d9cb546c3e..3c59c00abf 100644 --- a/explorer-api/src/mix_node/http.rs +++ b/explorer-api/src/mix_node/http.rs @@ -3,7 +3,7 @@ use rocket::serde::json::Json; use rocket::{Route, State}; use serde::Serialize; -use mixnet_contract::{Addr, Coin, Layer, MixNode, RawDelegationData}; +use mixnet_contract::{Addr, Coin, Delegation, Layer, MixNode}; use crate::mix_node::models::{NodeDescription, NodeStats}; use crate::mix_nodes::{get_mixnode_delegations, get_single_mixnode_delegations, Location}; @@ -39,14 +39,13 @@ pub(crate) async fn list( #[openapi(tag = "mix_node")] #[get("//delegations")] -pub(crate) async fn get_delegations(pubkey: &str) -> Json> { +pub(crate) async fn get_delegations(pubkey: &str) -> Json> { Json(get_single_mixnode_delegations(pubkey).await) } #[openapi(tag = "mix_node")] #[get("/all_mix_delegations")] -pub(crate) async fn get_all_delegations( -) -> Json>> { +pub(crate) async fn get_all_delegations() -> Json> { Json(get_mixnode_delegations().await) } diff --git a/explorer-api/src/mix_nodes/mod.rs b/explorer-api/src/mix_nodes/mod.rs index 8aacdfd76f..7ebbbedc85 100644 --- a/explorer-api/src/mix_nodes/mod.rs +++ b/explorer-api/src/mix_nodes/mod.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use crate::mix_node::http::PrettyMixNodeBondWithLocation; use crate::mix_nodes::utils::map_2_letter_to_3_letter_country_code; -use mixnet_contract::{Delegation, MixNodeBond, RawDelegationData, UnpackedDelegation}; +use mixnet_contract::{Delegation, MixNodeBond}; use network_defaults::{ default_api_endpoints, default_nymd_endpoints, DEFAULT_MIXNET_CONTRACT_ADDRESS, }; @@ -207,9 +207,9 @@ pub(crate) async fn get_single_mixnode_delegations(pubkey: &str) -> Vec Vec> { +pub(crate) async fn get_mixnode_delegations() -> Vec { let client = new_nymd_client(); - let delegates = match client.get_all_nymd_mixnode_delegations().await { + let delegates = match client.get_all_network_delegations().await { Ok(result) => result, Err(e) => { error!("Could not get all mix delegations: {:?}", e); diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 4afc9daff1..856bacabd4 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -698,8 +698,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9" dependencies = [ "digest 0.9.0", "ed25519-zebra", @@ -710,16 +711,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2" dependencies = [ "syn", ] [[package]] name = "cosmwasm-std" -version = "0.14.1" -source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b" +version = "1.0.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6" dependencies = [ "base64", "cosmwasm-crypto", diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 3bf8367f52..3868d36e30 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -29,7 +29,7 @@ rand = "0.6.5" cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"] } -cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch = "0.14.1-updatedk256" } +cosmwasm-std = "1.0.0-beta2" validator-client = { path = "../../common/client-libs/validator-client", features = [ "nymd-client", diff --git a/nym-wallet/src-tauri/src/menu.rs b/nym-wallet/src-tauri/src/menu.rs index a15cbac29d..f32d0da96f 100644 --- a/nym-wallet/src-tauri/src/menu.rs +++ b/nym-wallet/src-tauri/src/menu.rs @@ -1,4 +1,4 @@ -use tauri::{Menu, MenuItem, Submenu}; +use tauri::Menu; pub trait AddDefaultSubmenus { fn add_default_app_submenu_if_macos(self) -> Self; diff --git a/nym-wallet/src-tauri/src/operations/delegate.rs b/nym-wallet/src-tauri/src/operations/delegate.rs index e746e715db..44948943ec 100644 --- a/nym-wallet/src-tauri/src/operations/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/delegate.rs @@ -2,7 +2,7 @@ use crate::coin::Coin; use crate::format_err; use crate::state::State; use cosmwasm_std::Coin as CosmWasmCoin; -use mixnet_contract::{Addr, PagedReverseMixDelegationsResponse}; +use mixnet_contract::PagedDelegatorDelegationsResponse; use serde::{Deserialize, Serialize}; use std::convert::TryInto; use std::sync::Arc; @@ -58,11 +58,11 @@ pub async fn undelegate_from_mixnode( #[tauri::command] pub async fn get_reverse_mix_delegations_paged( state: tauri::State<'_, Arc>>, -) -> Result { +) -> Result { let r_state = state.read().await; let client = r_state.client()?; client - .get_reverse_mix_delegations_paged(Addr::unchecked(client.address().as_ref()), None, None) + .get_delegator_delegations_paged(client.address().to_string(), None, None) .await .map_err(|err| format_err!(err)) }