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:
+2
-1
@@ -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
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {},
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Generated
+1219
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
Generated
+12
-8
@@ -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",
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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", &[]);
|
||||
|
||||
@@ -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", &[]);
|
||||
|
||||
Generated
+26
-10
@@ -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",
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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", &[]);
|
||||
|
||||
@@ -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;
|
||||
+184
-156
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+318
-224
@@ -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
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -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 },
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+157
-89
@@ -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());
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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(())
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+908
@@ -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"
|
||||
@@ -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" }
|
||||
@@ -0,0 +1,2 @@
|
||||
wasm:
|
||||
RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 699 KiB |
@@ -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())
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod contract;
|
||||
mod errors;
|
||||
pub mod messages;
|
||||
mod storage;
|
||||
mod support;
|
||||
mod traits;
|
||||
mod vesting;
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod tests;
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Generated
+9
-6
@@ -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",
|
||||
|
||||
@@ -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,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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user