Release/1.0.0 pre1 (#931)

* Upgraded code to be cosmwasm 1.0-beta.2 compatible (#923)

* Upgraded code to be cosmwasm 1.0-beta.2 compatible

* [ci skip] Generate TS types

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>

* Feature/cosmwasm plus storage (#924)

* Upgraded code to be cosmwasm 1.0-beta.2 compatible

* Added cw-storage-plus dependency

* Experimentally replaced storage for config and layers with cw plus Item

* The same for main mixnode storage

* Usingn IndexedMap for mixnodes

* Split delegations from mixnodes into separate module

* MixnodeIndex on Addr directly

* Moved namespace values to constants

* Outdated comment

* [ci skip] Generate TS types

* Removed redundant identity index on mixnodes

* IndexMap for gateways storage

* Moved total delegation into a Map

* Compiling contract code after delegation storage upgrades

Tests dont compile yet and neither, I would assume, the client code

* Delegation type cleanup

* Client fixes

* Migrated delegation tests + fixed them

* Moved Rewarding Status to rewards

* Reward pool

* Rewarding status migrated

* Made clippy happier

* Added explorer API to default workspace members

* Updated delegation types in explorer-api

* Fixed tauri wallet

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>

* Vesting contract (#900)

* Initial interface spec

* .gitignore

* Finalize implementation

* Correct assumptions, use wasm_execute

* Cleanup

* Track delegation balance

* Add delegation flow img

* Proper messaging from the vesting side

* Add proxy_address to RawDelegationData

* Wrap up (un)delegation

* Add proxy: Addr to MixNodeBond

* Stub in bonding/unbonding

* Migrate vesting to cosmwasm 1.0

* Rebase on top of 1.0.0-pre1

* Reimplement delegations tracking with a Map

* Migrate to cw-storage-plus

* Restructure code, add tests

* Streamline contract code, as per review

* Address review comments

* Pre-merge rebase

* Few more nits

* Few more nits

* Fix test

* cargo fmt

* Fix beta CI

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
This commit is contained in:
Drazen Urch
2021-12-01 15:42:34 +01:00
committed by GitHub
parent 3e93b4ffd5
commit eb93b428cf
76 changed files with 8018 additions and 1967 deletions
+2 -1
View File
@@ -34,4 +34,5 @@ contracts/mixnet/code_id
contracts/mixnet/Justfile
contracts/mixnet/Makefile
validator-config
*.patch
*.patch
validator-api-config.toml
Generated
+9 -6
View File
@@ -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",
+1
View File
@@ -62,6 +62,7 @@ default-members = [
"service-providers/network-requester",
"mixnode",
"validator-api",
"explorer-api",
]
exclude = ["explorer", "contracts", "tokenomics-py"]
@@ -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]
@@ -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<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_mixnode_delegations(
&self,
) -> Result<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>, ValidatorClientError>
pub async fn get_all_network_delegations(&self) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
@@ -323,7 +320,7 @@ impl<C> Client<C> {
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<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_reverse_mixnode_delegations(
pub async fn get_all_delegator_delegations(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::IdentityKey>, ValidatorClientError>
) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
@@ -352,13 +349,13 @@ impl<C> Client<C> {
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<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_mixnode_delegations_of_owner(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::Delegation>, 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,
@@ -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,
@@ -38,7 +38,7 @@ impl<'a> Mul<Gas> 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);
}
@@ -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<C> NymdClient<C> {
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<C> NymdClient<C> {
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<C> NymdClient<C> {
pub async fn get_mix_delegations_paged(
&self,
mix_identity: IdentityKey,
// I really hate mixing cosmwasm and cosmos-sdk types here...
start_after: Option<Addr>,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedMixDelegationsResponse, NymdError>
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<C> NymdClient<C> {
}
/// 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<Vec<u8>>,
start_after: Option<(IdentityKey, String)>,
page_limit: Option<u32>,
) -> Result<PagedAllDelegationsResponse<RawDelegationData>, NymdError>
) -> Result<PagedAllDelegationsResponse, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetAllMixDelegations {
let request = QueryMsg::GetAllNetworkDelegations {
start_after,
limit: page_limit,
};
@@ -412,17 +410,17 @@ impl<C> NymdClient<C> {
}
/// 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<IdentityKey>,
page_limit: Option<u32>,
) -> Result<PagedReverseMixDelegationsResponse, NymdError>
) -> Result<PagedDelegatorDelegationsResponse, NymdError>
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<C> NymdClient<C> {
}
/// 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<C> NymdClient<C> {
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)
+1 -3
View File
@@ -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"
+49 -64
View File
@@ -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<T> {
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
pub struct Delegation {
pub owner: Addr,
pub node_identity: IdentityKey,
pub delegation_data: T,
}
impl<T> UnpackedDelegation<T> {
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<Addr>, // 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<Addr>,
) -> Self {
Delegation {
owner,
node_identity,
amount,
block_height,
proxy,
}
}
// TODO: change that to use .joined_key() and return Vec<u8>
pub fn storage_key(&self) -> (IdentityKey, Addr) {
(self.node_identity(), self.owner())
}
pub fn increment_amount(&mut self, amount: Uint128, at_height: Option<u64>) {
self.amount.amount += amount;
if let Some(at_height) = at_height {
self.block_height = at_height;
}
}
@@ -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<Delegation>,
pub start_next_after: Option<Addr>,
pub start_next_after: Option<String>,
}
impl PagedMixDelegationsResponse {
pub fn new(
node_identity: IdentityKey,
delegations: Vec<Delegation>,
start_next_after: Option<Addr>,
) -> Self {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<Addr>) -> 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<IdentityKey>,
pub struct PagedDelegatorDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<IdentityKey>,
}
impl PagedReverseMixDelegationsResponse {
pub fn new(
delegation_owner: Addr,
delegated_nodes: Vec<IdentityKey>,
start_next_after: Option<IdentityKey>,
) -> Self {
PagedReverseMixDelegationsResponse {
delegation_owner,
delegated_nodes,
impl PagedDelegatorDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<IdentityKey>) -> Self {
PagedDelegatorDelegationsResponse {
delegations,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse<T> {
pub delegations: Vec<UnpackedDelegation<T>>,
pub start_next_after: Option<Vec<u8>>,
pub struct PagedAllDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<(IdentityKey, String)>,
}
impl<T> PagedAllDelegationsResponse<T> {
pub fn new(delegations: Vec<UnpackedDelegation<T>>, start_next_after: Option<Vec<u8>>) -> Self {
impl PagedAllDelegationsResponse {
pub fn new(
delegations: Vec<Delegation>,
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())),
}
}
}
+2 -2
View File
@@ -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};
+12 -4
View File
@@ -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<u8>,
pub proxy: Option<Addr>,
}
impl MixNodeBond {
@@ -246,6 +247,7 @@ impl MixNodeBond {
block_height: u64,
mix_node: MixNode,
profit_margin_percent: Option<u8>,
proxy: Option<Addr>,
) -> 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:
+36 -14
View File
@@ -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<u32>,
},
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<u32>,
},
// gets all [paged] delegations associated with particular mixnode
GetMixnodeDelegations {
mix_identity: IdentityKey,
start_after: Option<Addr>,
// since `start_after` is user-provided input, we can't use `Addr` as we
// can't guarantee it's validated.
start_after: Option<String>,
limit: Option<u32>,
},
GetAllMixDelegations {
start_after: Option<Vec<u8>>,
limit: Option<u32>,
},
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<IdentityKey>,
limit: Option<u32>,
},
GetMixDelegation {
// gets delegation associated with particular mixnode, delegator pair
GetDelegationDetails {
mix_identity: IdentityKey,
address: Addr,
delegator: String,
},
LayerDistribution {},
GetRewardPool {},
+2 -2
View File
@@ -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,
}
+1219
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -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
+12 -8
View File
@@ -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",
+2 -16
View File
@@ -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"] }
+1 -1
View File
@@ -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", &[]);
+1 -1
View File
@@ -11,7 +11,7 @@ pub mod helpers {
use erc20_bridge_contract::payment::Payment;
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies(&[]);
let mut deps = mock_dependencies();
let msg = InstantiateMsg {};
let env = mock_env();
let info = mock_info("creator", &[]);
+26 -10
View File
@@ -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",
+5 -20
View File
@@ -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]
+57 -32
View File
@@ -1,6 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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<Response, ContractError> {
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<Response, ContractError> {
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<QueryResponse, Cont
to_binary(&mixnode_queries::query_owns_mixnode(deps, address)?)
}
QueryMsg::OwnsGateway { address } => 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<QueryResponse, Cont
start_after,
limit,
)?),
QueryMsg::GetAllMixDelegations { start_after, limit } => 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", &[]);
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod queries;
pub(crate) mod storage;
pub(crate) mod transactions;
@@ -1,62 +1,74 @@
use super::delegation_helpers;
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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<Vec<u8>>,
start_after: Option<(IdentityKey, String)>,
limit: Option<u32>,
) -> StdResult<PagedAllDelegationsResponse<RawDelegationData>> {
) -> StdResult<PagedAllDelegationsResponse> {
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::<RawDelegationData>(deps.storage);
let start = start_after.map(|mut v| {
v.push(0);
v
});
delegation_helpers::get_all_delegations_paged::<RawDelegationData>(&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::<StdResult<Vec<_>>>()?;
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<IdentityKey>,
limit: Option<u32>,
) -> StdResult<PagedReverseMixDelegationsResponse> {
) -> StdResult<PagedDelegatorDelegationsResponse> {
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::<StdResult<Vec<IdentityKey>>>()?;
.map(|record| record.map(|r| r.1))
.collect::<StdResult<Vec<_>>>()?;
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<Delegation, ContractError> {
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<String>,
limit: Option<u32>,
) -> StdResult<PagedMixDelegationsResponse> {
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::<StdResult<Vec<_>>>()?;
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());
}
}
}
+180
View File
@@ -0,0 +1,180 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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<u8>;
pub(crate) struct DelegationIndex<'a> {
pub(crate) owner: MultiIndex<'a, (Addr, PrimaryKey), Delegation>,
pub(crate) mixnode: MultiIndex<'a, (IdentityKey, PrimaryKey), Delegation>,
}
impl<'a> IndexList<Delegation> for DelegationIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<Delegation>> + '_> {
let v: Vec<&dyn Index<Delegation>> = 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<u8>. 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::<Vec<_>>();
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));
}
}
}
@@ -1,12 +1,19 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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<Response, ContractError> {
// 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<Addr>,
) -> Result<Response, ContractError> {
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<Response, ContractError> {
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<Response, ContractError> {
_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<Addr>,
) -> Result<Response, ContractError> {
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::<RawDelegationData>(&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::<RawDelegationData>(&deps.storage);
// let mix_delegations = helpers::Delegations::new(mix_bucket);
// assert_eq!(
// storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10,
// mix_delegations.count() as u32
// );
// }
// }
}
+3
View File
@@ -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 },
}
+45 -50
View File
@@ -1,7 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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::<StdResult<Vec<GatewayBond>>>()?;
@@ -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<GatewayOwnershipResponse> {
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<GatewayOwnershipResponse> {
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<Gateway
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::gateways::storage;
use crate::support::tests::test_helpers;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{Addr, Storage};
use mixnet_contract::Gateway;
#[test]
@@ -52,22 +59,14 @@ pub(crate) mod tests {
assert_eq!(0, response.nodes.len());
}
fn store_n_gateway_fixtures(n: u32, storage: &mut dyn Storage) {
for i in 0..n {
let key = format!("bond{}", i);
let node = test_helpers::gateway_bond_fixture();
storage::gateways(storage)
.save(key.as_bytes(), &node)
.unwrap();
}
}
#[test]
fn gateways_paged_retrieval_obeys_limits() {
let mut deps = test_helpers::init_contract();
let storage = deps.as_mut().storage;
let limit = 2;
store_n_gateway_fixtures(100, storage);
for n in 0..10000 {
let key = format!("bond{}", n);
test_helpers::add_gateway(&key, test_helpers::good_gateway_bond(), deps.as_mut());
}
let page1 = query_gateways_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.nodes.len() as u32);
@@ -76,8 +75,10 @@ pub(crate) mod tests {
#[test]
fn gateways_paged_retrieval_has_default_limit() {
let mut deps = test_helpers::init_contract();
let storage = deps.as_mut().storage;
store_n_gateway_fixtures(10 * BOND_PAGE_DEFAULT_LIMIT, storage);
for n in 0..1000 {
let key = format!("bond{}", n);
test_helpers::add_gateway(&key, test_helpers::good_gateway_bond(), deps.as_mut());
}
// query without explicitly setting a limit
let page1 = query_gateways_paged(deps.as_ref(), None, None).unwrap();
@@ -88,8 +89,10 @@ pub(crate) mod tests {
#[test]
fn gateways_paged_retrieval_has_max_limit() {
let mut deps = test_helpers::init_contract();
let storage = deps.as_mut().storage;
store_n_gateway_fixtures(100, storage);
for n in 0..10000 {
let key = format!("bond{}", n);
test_helpers::add_gateway(&key, test_helpers::good_gateway_bond(), deps.as_mut());
}
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * BOND_PAGE_DEFAULT_LIMIT;
@@ -108,13 +111,8 @@ pub(crate) mod tests {
let addr4 = "nym103";
let mut deps = test_helpers::init_contract();
let node = test_helpers::gateway_bond_fixture();
// TODO: note for JS when doing a deep review for the contract: add a similar test_helper as there is for mixnodes,
// i.e. don't interact with storage here, but get the helper to call an actual transaction
storage::gateways(&mut deps.storage)
.save(addr1.as_bytes(), &node)
.unwrap();
let _identity1 =
test_helpers::add_gateway(&addr1, test_helpers::good_gateway_bond(), deps.as_mut());
let per_page = 2;
let page1 = query_gateways_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
@@ -123,24 +121,22 @@ pub(crate) mod tests {
assert_eq!(1, page1.nodes.len());
// save another
storage::gateways(&mut deps.storage)
.save(addr2.as_bytes(), &node)
.unwrap();
let identity2 =
test_helpers::add_gateway(&addr2, test_helpers::good_gateway_bond(), deps.as_mut());
// page1 should have 2 results on it
let page1 = query_gateways_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.nodes.len());
storage::gateways(&mut deps.storage)
.save(addr3.as_bytes(), &node)
.unwrap();
let _identity3 =
test_helpers::add_gateway(&addr3, test_helpers::good_gateway_bond(), deps.as_mut());
// page1 still has 2 results
let page1 = query_gateways_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.nodes.len());
// retrieving the next page should start after the last key on this page
let start_after = String::from(addr2);
let start_after = identity2.clone();
let page2 = query_gateways_paged(
deps.as_ref(),
Option::from(start_after),
@@ -151,11 +147,10 @@ pub(crate) mod tests {
assert_eq!(1, page2.nodes.len());
// save another one
storage::gateways(&mut deps.storage)
.save(addr4.as_bytes(), &node)
.unwrap();
let _identity4 =
test_helpers::add_gateway(&addr4, test_helpers::good_gateway_bond(), deps.as_mut());
let start_after = String::from(addr2);
let start_after = identity2;
let page2 = query_gateways_paged(
deps.as_ref(),
Option::from(start_after),
@@ -172,7 +167,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_gateway(deps.as_ref(), Addr::unchecked("fred")).unwrap();
let res = query_owns_gateway(deps.as_ref(), "fred".to_string()).unwrap();
assert!(!res.has_gateway);
// mixnode was added to "bob", "fred" still does not own one
@@ -188,7 +183,7 @@ pub(crate) mod tests {
)
.unwrap();
let res = query_owns_gateway(deps.as_ref(), Addr::unchecked("fred")).unwrap();
let res = query_owns_gateway(deps.as_ref(), "fred".to_string()).unwrap();
assert!(!res.has_gateway);
// "fred" now owns a gateway!
@@ -204,14 +199,14 @@ pub(crate) mod tests {
)
.unwrap();
let res = query_owns_gateway(deps.as_ref(), Addr::unchecked("fred")).unwrap();
let res = query_owns_gateway(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.has_gateway);
// but after unbonding it, he doesn't own one anymore
crate::gateways::transactions::try_remove_gateway(deps.as_mut(), mock_info("fred", &[]))
.unwrap();
let res = query_owns_gateway(deps.as_ref(), Addr::unchecked("fred")).unwrap();
let res = query_owns_gateway(deps.as_ref(), "fred".to_string()).unwrap();
assert!(!res.has_gateway);
}
}
+43 -41
View File
@@ -1,71 +1,73 @@
use cosmwasm_std::Storage;
use cosmwasm_storage::bucket;
use cosmwasm_storage::bucket_read;
use cosmwasm_storage::Bucket;
use cosmwasm_storage::ReadonlyBucket;
use mixnet_contract::GatewayBond;
use mixnet_contract::IdentityKey;
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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<GatewayBond> {
bucket(storage, PREFIX_GATEWAYS)
pub(crate) struct GatewayBondIndex<'a> {
pub(crate) owner: UniqueIndex<'a, Addr, GatewayBond>,
}
pub fn gateways_read(storage: &dyn Storage) -> ReadonlyBucket<GatewayBond> {
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<GatewayBond> for GatewayBondIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<GatewayBond>> + '_> {
let v: Vec<&dyn Index<GatewayBond>> = vec![&self.owner];
Box::new(v.into_iter())
}
}
// owner address -> node identity
pub fn gateways_owners(storage: &mut dyn Storage) -> Bucket<IdentityKey> {
bucket(storage, PREFIX_GATEWAYS_OWNERS)
}
pub fn gateways_owners_read(storage: &dyn Storage) -> ReadonlyBucket<IdentityKey> {
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<cosmwasm_std::Uint128> {
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()
);
}
}
+75 -80
View File
@@ -1,10 +1,13 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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<Response, ContractError> {
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<Response, ContractError> {
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<u8> 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());
+1 -1
View File
@@ -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;
@@ -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<ContractSettingsParams> {
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<RewardingIntervalResponse> {
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()
)
}
@@ -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<ContractSettings> {
singleton(storage, CONFIG_KEY)
}
pub fn contract_settings_read(storage: &dyn Storage) -> ReadonlySingleton<ContractSettings> {
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<LayerDistribution> {
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<LayerDistribution> {
singleton_read(storage, LAYER_DISTRIBUTION_KEY)
}
pub(crate) const CONTRACT_SETTINGS: Item<ContractSettings> = Item::new("config");
pub(crate) const LAYERS: Item<LayerDistribution> = 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(|_| ())
}
@@ -13,7 +13,7 @@ pub(crate) fn try_update_contract_settings(
info: MessageInfo,
params: ContractSettingsParams,
) -> Result<Response, ContractError> {
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);
@@ -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<MixOwnershipResponse> {
let has_node = storage::mixnodes_owners_read(deps.storage)
.may_load(address.as_bytes())?
pub fn query_owns_mixnode(deps: Deps, address: String) -> StdResult<MixOwnershipResponse> {
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<Addr>,
limit: Option<u32>,
) -> StdResult<PagedMixDelegationsResponse> {
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::<StdResult<Vec<Delegation>>>()?;
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);
}
}
@@ -1,269 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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<T>(
bucket: &ReadonlyBucket<T>,
start_after: &Option<Vec<u8>>,
limit: usize,
) -> StdResult<PagedAllDelegationsResponse<T>>
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::<StdResult<Vec<UnpackedDelegation<T>>>>()?;
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<u8>) -> 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<u8> = 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<u8> = 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<UnpackedDelegation<T>>,
curr_index: usize,
start_after: Option<Vec<u8>>,
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<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.curr_index == OLD_DELEGATIONS_CHUNK_SIZE && !self.last_page {
self.start_after = self.start_after.clone().map(|mut v: Vec<u8>| {
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::<RawDelegationData>(&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::<RawDelegationData>(&deps.storage);
let response =
get_all_delegations_paged::<RawDelegationData>(&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::<RawDelegationData>(&deps.storage);
let response =
get_all_delegations_paged::<RawDelegationData>(&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::<RawDelegationData>(&deps.storage);
let response =
get_all_delegations_paged::<RawDelegationData>(&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()),
);
}
}
@@ -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<LayerDistribution> {
mixnet_params_storage::LAYERS.load(deps.storage)
}
+1 -4
View File
@@ -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;
+47 -222
View File
@@ -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<IdentityKeyRef, Uint128> =
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<StoredMixnodeBond> for MixnodeBondIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<StoredMixnodeBond>> + '_> {
let v: Vec<&dyn Index<StoredMixnodeBond>> = 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<u8>,
pub proxy: Option<Addr>,
}
impl StoredMixnodeBond {
@@ -46,6 +61,7 @@ impl StoredMixnodeBond {
block_height: u64,
mix_node: MixNode,
profit_margin_percent: Option<u8>,
proxy: Option<Addr>,
) -> 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<StoredMixnodeBond> {
bucket(storage, PREFIX_MIXNODES)
}
pub(crate) fn mixnodes_read(storage: &dyn Storage) -> ReadonlyBucket<StoredMixnodeBond> {
bucket_read(storage, PREFIX_MIXNODES)
}
// owner address -> node identity
pub fn mixnodes_owners(storage: &mut dyn Storage) -> Bucket<IdentityKey> {
bucket(storage, PREFIX_MIXNODES_OWNERS)
}
pub fn mixnodes_owners_read(storage: &dyn Storage) -> ReadonlyBucket<IdentityKey> {
bucket_read(storage, PREFIX_MIXNODES_OWNERS)
}
pub fn total_delegation(storage: &mut dyn Storage) -> Bucket<Uint128> {
bucket(storage, PREFIX_TOTAL_DELEGATION)
}
pub fn total_delegation_read(storage: &dyn Storage) -> ReadonlyBucket<Uint128> {
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<RewardingStatus> {
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<RewardingStatus> {
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<Option<MixNodeBond>> {
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<T>(storage: &dyn Storage) -> ReadonlyBucket<T>
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::<RawDelegationData>(&deps.storage)
.load(&*test_helpers::identity_and_owner_to_bytes(
&node_identity1,
&delegation_owner1,
))
.unwrap();
let res2 = storage::all_mix_delegations_read::<RawDelegationData>(&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());
}
}
}
@@ -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<Response, ContractError> {
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<Response, ContractError> {
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<Addr>,
) -> Result<Response, ContractError> {
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<Response, ContractError> {
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<Response, ContractError> {
_try_remove_mixnode(deps, info.sender.as_ref(), None)
}
pub(crate) fn _try_remove_mixnode(
deps: DepsMut,
owner: &str,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
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<u8> 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<Coin, ContractError> {
// 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());
-17
View File
@@ -1,17 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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<S: AsRef<str>>(start_after: Option<S>) -> Option<Vec<u8>> {
start_after.as_ref().map(|identity| {
identity
.as_ref()
.as_bytes()
.iter()
.cloned()
.chain(std::iter::once(0))
.collect()
})
}
+18 -20
View File
@@ -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<Uint128> {
storage::REWARD_POOL.load(deps.storage)
}
pub(crate) fn query_circulating_supply(deps: Deps) -> Uint128 {
pub(crate) fn query_circulating_supply(deps: Deps) -> StdResult<Uint128> {
storage::circulating_supply(deps.storage)
}
@@ -20,8 +19,10 @@ pub(crate) fn query_rewarding_status(
mix_identity: IdentityKey,
rewarding_interval_nonce: u32,
) -> StdResult<MixnodeRewardingStatusResponse> {
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();
+21 -35
View File
@@ -1,13 +1,14 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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<Uint128> = 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<Uint128> {
singleton_read(storage, REWARD_POOL_PREFIX)
}
pub fn mut_reward_pool(storage: &mut dyn Storage) -> Singleton<Uint128> {
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<Uint128, ContractError> {
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<Uint128, ContractError> {
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<Uint128> {
let reward_pool = REWARD_POOL.load(storage)?;
Ok(Uint128::new(TOTAL_SUPPLY) - reward_pool)
}
File diff suppressed because it is too large Load Diff
+47
View File
@@ -0,0 +1,47 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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<u8> {
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(())
}
+5 -1
View File
@@ -1 +1,5 @@
pub mod tests;
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod helpers;
pub(crate) mod tests;
+50 -61
View File
@@ -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<Coin>, deps: DepsMut) -> String {
@@ -61,15 +63,11 @@ pub mod test_helpers {
page.nodes
}
pub fn add_gateway(
sender: &str,
stake: Vec<Coin>,
deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier>,
) -> String {
pub fn add_gateway(sender: &str, stake: Vec<Coin>, 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<MemoryStorage, MockApi, MockQuerier<Empty>> {
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<u8> {
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<cosmwasm_std::Uint128> {
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<String>,
owner: impl Into<String>,
) {
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<String>,
owner: impl Into<String>,
) -> Option<Delegation> {
delegations_storage::delegations()
.may_load(storage, (mix.into(), owner.into()).joined_key())
.unwrap()
}
}
+908
View File
@@ -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"
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "vesting-contract"
version = "0.1.0"
authors = ["Drazen Urch <durch@users.noreply.github.com>"]
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" }
+2
View File
@@ -0,0 +1,2 @@
wasm:
RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
+11
View File
@@ -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)
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 699 KiB

+356
View File
@@ -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<Response, ContractError> {
Ok(Response::default())
}
#[entry_point]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
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<Response, ContractError> {
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<Response, ContractError> {
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<Response, ContractError> {
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<Response, ContractError> {
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<Response, ContractError> {
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<Response, ContractError> {
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<Response, ContractError> {
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<u64>,
info: MessageInfo,
env: Env,
deps: DepsMut,
) -> Result<Response, ContractError> {
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<QueryResponse, ContractError> {
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<Timestamp>,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
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<Timestamp>,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
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<Timestamp>,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
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<Timestamp>,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
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<Timestamp, ContractError> {
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<Timestamp, ContractError> {
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<Coin, ContractError> {
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<Timestamp>,
vesting_account_address: &str,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
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<Timestamp>,
vesting_account_address: &str,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
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<Coin, ContractError> {
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())
}
+41
View File
@@ -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),
}
+7
View File
@@ -0,0 +1,7 @@
pub mod contract;
mod errors;
pub mod messages;
mod storage;
mod support;
mod traits;
mod vesting;
+78
View File
@@ -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<u64>,
},
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<Timestamp>,
},
SpendableCoins {
vesting_account_address: String,
block_time: Option<Timestamp>,
},
GetVestedCoins {
vesting_account_address: String,
block_time: Option<Timestamp>,
},
GetVestingCoins {
vesting_account_address: String,
block_time: Option<Timestamp>,
},
GetStartTime {
vesting_account_address: String,
},
GetEndTime {
vesting_account_address: String,
},
GetOriginalVesting {
vesting_account_address: String,
},
GetDelegatedFree {
block_time: Option<Timestamp>,
vesting_account_address: String,
},
GetDelegatedVesting {
block_time: Option<Timestamp>,
vesting_account_address: String,
},
}
+30
View File
@@ -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<Addr, Account> = 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<Option<Account>, ContractError> {
Ok(ACCOUNTS.may_load(storage, address.to_owned())?)
}
fn validate_account(address: &Addr, storage: &dyn Storage) -> Result<Account, ContractError> {
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<Account, ContractError> {
validate_account(&api.addr_validate(address)?, storage)
}
+1
View File
@@ -0,0 +1 @@
pub mod tests;
+35
View File
@@ -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<MemoryStorage, MockApi, MockQuerier<Empty>> {
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()
}
}
@@ -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<Response, ContractError>;
fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result<Response, ContractError>;
fn track_unbond(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError>;
}
@@ -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<Response, ContractError>;
fn try_undelegate_from_mixnode(
&self,
mix_identity: IdentityKey,
storage: &dyn Storage,
) -> Result<Response, ContractError>;
// 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>;
}
+7
View File
@@ -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;
@@ -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<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError>;
// Calculates the total spendable balance that can be sent to other accounts.
fn spendable_coins(
&self,
block_time: Option<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError>;
fn get_vested_coins(
&self,
block_time: Option<Timestamp>,
env: &Env,
) -> Result<Coin, ContractError>;
fn get_vesting_coins(
&self,
block_time: Option<Timestamp>,
env: &Env,
) -> Result<Coin, ContractError>;
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<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError>;
fn get_delegated_vesting(
&self,
block_time: Option<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError>;
fn get_bonded_free(
&self,
block_time: Option<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError>;
fn get_bonded_vesting(
&self,
block_time: Option<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError>;
}
@@ -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<Response, ContractError> {
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<Response, ContractError> {
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(())
}
}
@@ -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<Response, ContractError> {
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<Response, ContractError> {
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::<Vec<u64>>();
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(())
}
}
@@ -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<VestingPeriod>,
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<VestingPeriod>,
storage: &mut dyn Storage,
) -> Result<Self, ContractError> {
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<VestingPeriod> {
self.periods.clone()
}
#[allow(dead_code)]
pub fn start_time(&self) -> Timestamp {
self.start_time
}
pub fn tokens_per_period(&self) -> Result<u128, ContractError> {
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::<Vec<u64>>()
.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<Uint128, ContractError> {
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<Uint128> {
Item::new(self.balance_key.as_ref())
}
pub fn load_bond(&self, storage: &dyn Storage) -> Result<Option<BondData>, 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<BondData> {
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<u64> {
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::<Vec<u64>>()
}
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<Vec<Uint128>, 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<Uint128, ContractError> {
Ok(self
.delegations_for_mix(mix, storage)?
.iter()
.fold(Uint128::zero(), |acc, x| acc + *x))
}
pub fn total_delegations(&self, storage: &dyn Storage) -> Result<Uint128, ContractError> {
Ok(self
.delegations()
.range(storage, None, None, Order::Ascending)
.scan((), |_, x| x.ok())
.fold(Uint128::zero(), |acc, (_, x)| acc + x))
}
}
@@ -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<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
// 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<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
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<Timestamp>,
env: &Env,
) -> Result<Coin, ContractError> {
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<Timestamp>,
env: &Env,
) -> Result<Coin, ContractError> {
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<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
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::<Vec<(Vec<u8>, 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<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
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<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
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<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError> {
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(),
})
}
}
}
+344
View File
@@ -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<VestingPeriod> {
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);
}
}
+3 -4
View File
@@ -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("/<pubkey>/delegations")]
pub(crate) async fn get_delegations(pubkey: &str) -> Json<Vec<mixnet_contract::Delegation>> {
pub(crate) async fn get_delegations(pubkey: &str) -> Json<Vec<Delegation>> {
Json(get_single_mixnode_delegations(pubkey).await)
}
#[openapi(tag = "mix_node")]
#[get("/all_mix_delegations")]
pub(crate) async fn get_all_delegations(
) -> Json<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>> {
pub(crate) async fn get_all_delegations() -> Json<Vec<Delegation>> {
Json(get_mixnode_delegations().await)
}
+3 -3
View File
@@ -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<Delegati
delegates
}
pub(crate) async fn get_mixnode_delegations() -> Vec<UnpackedDelegation<RawDelegationData>> {
pub(crate) async fn get_mixnode_delegations() -> Vec<Delegation> {
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);
+9 -6
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -1,4 +1,4 @@
use tauri::{Menu, MenuItem, Submenu};
use tauri::Menu;
pub trait AddDefaultSubmenus {
fn add_default_app_submenu_if_macos(self) -> Self;
@@ -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<RwLock<State>>>,
) -> Result<PagedReverseMixDelegationsResponse, String> {
) -> Result<PagedDelegatorDelegationsResponse, String> {
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))
}