Feature/delegated staking (#635)

* Initial delegation

* Queries for delegation

* Validation + delegation tests

* Removing delegation tests + fixes

* Tests for query related functions

* Adjusted delegation queries page limits

* Added delegation queries to validator client

* Methods for delegation inside validator client

* Some comments

* missing return types

* Post merge fixes

* Rewarding mix delegations

* Renaming

* Moved storage keys around

* Gateway delegation

* ibid

* Removed needlessly commented lines
This commit is contained in:
Jędrzej Stuczyński
2021-06-10 12:55:58 +01:00
committed by GitHub
parent 29fd8b8805
commit 1fb26b68c6
12 changed files with 2467 additions and 38 deletions
+151
View File
@@ -279,6 +279,70 @@ export default class ValidatorClient {
}
}
/**
* Delegates specified amount of stake to particular mixnode.
*
* @param mixOwner address of the owner of the node to which the delegation should be applied
* @param amount desired amount of coins to delegate to the node
*/
// requires coin type to ensure correct denomination (
async delegateToMixnode(mixOwner: string, amount: Coin): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { delegate_to_mixnode: { mix_owner: mixOwner } }, `delegating to ${mixOwner}`, [amount]).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} delegated ${amount} to mixnode owned by ${mixOwner}`);
return result;
} else {
throw new Error("Tried to delegate stake with a query client")
}
}
/**
* Removes stake delegation from a particular mixnode.
*
* @param mixOwner address of the owner of the node from which the delegation should get removed
*/
async removeMixnodeDelegation(mixOwner: string): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { undelegate_from_mixnode: { mix_owner: mixOwner } }).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} removed delegation from mixnode owned by ${mixOwner}`);
return result;
} else {
throw new Error("Tried to remove stake delegation with a query client")
}
}
/**
* Delegates specified amount of stake to particular gateway.
*
* @param gatewayOwner address of the owner of the node to which the delegation should be applied
* @param amount desired amount of coins to delegate to the node
*/
// requires coin type to ensure correct denomination (
async delegateToGateway(gatewayOwner: string, amount: Coin): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { delegate_to_gateway: { gateway_owner: gatewayOwner } }, `delegating to ${gatewayOwner}`, [amount]).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} delegated ${amount} to gateway owned by ${gatewayOwner}`);
return result;
} else {
throw new Error("Tried to delegate stake with a query client")
}
}
/**
* Removes stake delegation from a particular gateway.
*
* @param gatewayOwner address of the owner of the node from which the delegation should get removed
*/
async removeGatewayDelegation(gatewayOwner: string): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { undelegate_from_gateway: { gateway_owner: gatewayOwner } }).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} removed delegation from gateway owned by ${gatewayOwner}`);
return result;
} else {
throw new Error("Tried to remove stake delegation with a query client")
}
}
/**
* Checks whether there is already a bonded mixnode associated with this client's address
*/
@@ -369,6 +433,76 @@ export default class ValidatorClient {
}
}
/**
* Gets list of all delegations towards particular mixnode.
*
* @param mixOwner address of the owner of the node to which the delegation was sent
*/
public async getMixDelegations(mixOwner: string): Promise<Delegation[]> {
// make this configurable somewhere
const limit = 500
let delegations: Delegation[] = [];
let response: PagedMixDelegationsResponse
let next: string | undefined = undefined;
for (;;) {
response = await this.client.getMixDelegations(this.contractAddress, mixOwner, limit, next)
delegations = delegations.concat(response.delegations)
next = response.start_next_after
// if `start_next_after` is not set, we're done
if (!next) {
break
}
}
return delegations
}
/**
* Checks value of delegation of given client towards particular mixnode.
*
* @param mixOwner address of the owner of the node to which the delegation was sent
* @param delegatorAddress address of the client who delegated the stake
*/
public getMixDelegation(mixOwner: string, delegatorAddress: string): Promise<Delegation> {
return this.client.getMixDelegation(this.contractAddress, mixOwner, delegatorAddress);
}
/**
* Gets list of all delegations towards particular mixnode.
*
* @param gatewayOwner address of the owner of the gateway to which the delegation was sent
*/
public async getGatewayDelegations(gatewayOwner: string): Promise<Delegation[]> {
// make this configurable somewhere
const limit = 500
let delegations: Delegation[] = [];
let response: PagedGatewayDelegationsResponse
let next: string | undefined = undefined;
for (;;) {
response = await this.client.getGatewayDelegations(this.contractAddress, gatewayOwner, limit, next)
delegations = delegations.concat(response.delegations)
next = response.start_next_after
// if `start_next_after` is not set, we're done
if (!next) {
break
}
}
return delegations
}
/**
* Checks value of delegation of given client towards particular gateway.
*
* @param gatewayOwner address of the owner of the node to which the delegation was sent
* @param delegatorAddress address of the client who delegated the stake
*/
public getGatewayDelegation(gatewayOwner: string, delegatorAddress: string): Promise<Delegation> {
return this.client.getGatewayDelegation(this.contractAddress, gatewayOwner, delegatorAddress);
}
// TODO: if we just keep a reference to the SigningCosmWasmClient somewhere we can probably go direct
// to it in the case of these methods below.
@@ -501,3 +635,20 @@ export type StateParams = {
gateway_bond_reward_rate: string,
mixnode_active_set_size: number,
}
export type Delegation = {
owner: string,
amount: Coin,
}
export type PagedMixDelegationsResponse = {
node_owner: string,
delegations: Delegation[],
start_next_after: string
}
export type PagedGatewayDelegationsResponse = {
node_owner: string,
delegations: Delegation[],
start_next_after: string
}
+32 -3
View File
@@ -1,13 +1,14 @@
import { SigningCosmWasmClient, SigningCosmWasmClientOptions } from "@cosmjs/cosmwasm-stargate";
import {
Delegation,
GatewayOwnershipResponse,
MixOwnershipResponse,
PagedGatewayResponse,
MixOwnershipResponse, PagedGatewayDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse,
PagedResponse,
StateParams
} from "./index";
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
import { Coin, GasPrice, StdFee } from "@cosmjs/launchpad";
import { Coin, StdFee } from "@cosmjs/launchpad";
import { BroadcastTxResponse } from "@cosmjs/stargate"
import { nymGasLimits, nymGasPrice } from "./stargate-helper"
import { ExecuteResult, InstantiateOptions, InstantiateResult, MigrateResult, UploadMeta, UploadResult } from "@cosmjs/cosmwasm";
@@ -18,6 +19,10 @@ export interface INetClient {
getBalance(address: string, stakeDenom: string): Promise<Coin | null>;
getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedResponse>;
getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse>;
getMixDelegations(contractAddress: string, mixOwner: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse>
getMixDelegation(contractAddress: string, mixOwner: string, delegatorAddress: string): Promise<Delegation>
getGatewayDelegations(contractAddress: string, gatewayOwner: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse>
getGatewayDelegation(contractAddress: string, gatewayOwner: string, delegatorAddress: string): Promise<Delegation>
ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(contractAddress: string): Promise<StateParams>;
@@ -82,6 +87,30 @@ export default class NetClient implements INetClient {
}
}
public getMixDelegations(contractAddress: string, mixOwner: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_delegations: { mix_owner: mixOwner, limit } });
} else {
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_delegations: { mix_owner: mixOwner, limit, start_after } });
}
}
public getMixDelegation(contractAddress: string, mixOwner: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_delegation: { mix_owner: mixOwner, address: delegatorAddress } });
}
public getGatewayDelegations(contractAddress: string, gatewayOwner: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, { get_gateway_delegations: { gateway_owner: gatewayOwner, limit } });
} else {
return this.cosmClient.queryContractSmart(contractAddress, { get_gateway_delegations: { gateway_owner: gatewayOwner, limit, start_after } });
}
}
public getGatewayDelegation(contractAddress: string, gatewayOwner: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, { get_gateway_delegation: { gateway_owner: gatewayOwner, address: delegatorAddress } });
}
public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, { owns_mixnode: { address } });
}
+32 -3
View File
@@ -1,9 +1,10 @@
import {Coin} from "@cosmjs/launchpad";
import { CosmWasmClient, SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import {
Delegation,
GatewayOwnershipResponse,
MixOwnershipResponse,
PagedGatewayResponse,
MixOwnershipResponse, PagedGatewayDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse,
PagedResponse,
StateParams
} from "./index";
@@ -12,6 +13,10 @@ export interface IQueryClient {
getBalance(address: string, stakeDenom: string): Promise<Coin | null>;
getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedResponse>;
getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse>;
getMixDelegations(contractAddress: string, mixOwner: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse>
getMixDelegation(contractAddress: string, mixOwner: string, delegatorAddress: string): Promise<Delegation>
getGatewayDelegations(contractAddress: string, gatewayOwner: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse>
getGatewayDelegation(contractAddress: string, gatewayOwner: string, delegatorAddress: string): Promise<Delegation>
ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(contractAddress: string): Promise<StateParams>;
@@ -58,6 +63,30 @@ export default class QueryClient implements IQueryClient {
}
}
public getMixDelegations(contractAddress: string, mixOwner: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_delegations: { mix_owner: mixOwner, limit } });
} else {
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_delegations: { mix_owner: mixOwner, limit, start_after } });
}
}
public getMixDelegation(contractAddress: string, mixOwner: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_delegation: { mix_owner: mixOwner, address: delegatorAddress } });
}
public getGatewayDelegations(contractAddress: string, gatewayOwner: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, { get_gateway_delegations: { gateway_owner: gatewayOwner, limit } });
} else {
return this.cosmClient.queryContractSmart(contractAddress, { get_gateway_delegations: { gateway_owner: gatewayOwner, limit, start_after } });
}
}
public getGatewayDelegation(contractAddress: string, gatewayOwner: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, { get_gateway_delegation: { gateway_owner: gatewayOwner, address: delegatorAddress } });
}
public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, { owns_mixnode: { address } });
}
+79
View File
@@ -0,0 +1,79 @@
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
use cosmwasm_std::{Coin, HumanAddr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct Delegation {
owner: HumanAddr,
amount: Coin,
}
impl Delegation {
pub fn new(owner: HumanAddr, amount: Coin) -> Self {
Delegation { owner, amount }
}
pub fn amount(&self) -> &Coin {
&self.amount
}
pub fn owner(&self) -> &HumanAddr {
&self.owner
}
}
impl Display for Delegation {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{} {} delegated by {}",
self.amount.amount, self.amount.denom, self.owner
)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedMixDelegationsResponse {
pub node_owner: HumanAddr,
pub delegations: Vec<Delegation>,
pub start_next_after: Option<HumanAddr>,
}
impl PagedMixDelegationsResponse {
pub fn new(
node_owner: HumanAddr,
delegations: Vec<Delegation>,
start_next_after: Option<HumanAddr>,
) -> Self {
PagedMixDelegationsResponse {
node_owner,
delegations,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedGatewayDelegationsResponse {
pub node_owner: HumanAddr,
pub delegations: Vec<Delegation>,
pub start_next_after: Option<HumanAddr>,
}
impl PagedGatewayDelegationsResponse {
pub fn new(
node_owner: HumanAddr,
delegations: Vec<Delegation>,
start_next_after: Option<HumanAddr>,
) -> Self {
PagedGatewayDelegationsResponse {
node_owner,
delegations,
start_next_after,
}
}
}
+2
View File
@@ -1,9 +1,11 @@
use serde::{Deserialize, Serialize};
mod delegation;
mod gateway;
mod mixnode;
pub use cosmwasm_std::{Coin, HumanAddr};
pub use delegation::{Delegation, PagedGatewayDelegationsResponse, PagedMixDelegationsResponse};
pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse};
pub use mixnode::{MixNode, MixNodeBond, MixOwnershipResponse, PagedResponse};
+19
View File
@@ -46,8 +46,27 @@ impl MixNode {
}
}
/*
By the way, I've also been thinking of some potential changes in the contract we could introduce once we have to make an incompatible upgrade (say to cosmwasm 0.14+). Is there some place I could write them down so that we would not forget about them? Should I make a GitHub issue, a google doc or maybe put it on GitLab?
*/
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeBond {
// TODO:
// JS: When we go onto the next testnet and we have to make incompatible changes in the contract (such as upgrade to cosmwasm 0.14+)
// I'd change `amount` from `Vec<Coin>` to just `Coin` (or maybe even `Uint128` since denomination is implicit)
// I would also put here field like `total_delegation` which would also be a `Coin` or `Uint128` that
// indicates the sum of all delegations towards this node
//
// I would also modify the `MixNode` struct:
// - remove `location` field
// - repurpose `host` field to hold either ip or hostname (WITHOUT port information)
// - introduce `mix_port` field
// - introduce `rest_api_port` field
// - [POTENTIALLY] introduce `verloc_port` field or keep it accessible via http api
//
// I would also introduce the identical changes to GatewayBond
pub amount: Vec<Coin>,
pub owner: HumanAddr,
pub mix_node: MixNode,
+43
View File
@@ -93,6 +93,18 @@ pub fn handle(
HandleMsg::RewardGateway { owner, uptime } => {
transactions::try_reward_gateway(deps, info, owner, uptime)
}
HandleMsg::DelegateToMixnode { mix_owner } => {
transactions::try_delegate_to_mixnode(deps, info, mix_owner)
}
HandleMsg::UndelegateFromMixnode { mix_owner } => {
transactions::try_remove_delegation_from_mixnode(deps, info, env, mix_owner)
}
HandleMsg::DelegateToGateway { gateway_owner } => {
transactions::try_delegate_to_gateway(deps, info, gateway_owner)
}
HandleMsg::UndelegateFromGateway { gateway_owner } => {
transactions::try_remove_delegation_from_gateway(deps, info, env, gateway_owner)
}
}
}
@@ -112,6 +124,37 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
}
QueryMsg::StateParams {} => to_binary(&queries::query_state_params(deps)),
QueryMsg::LayerDistribution {} => to_binary(&queries::query_layer_distribution(deps)),
QueryMsg::GetMixDelegations {
mix_owner,
start_after,
limit,
} => to_binary(&queries::query_mixnode_delegations_paged(
deps,
mix_owner,
start_after,
limit,
)?),
QueryMsg::GetMixDelegation { mix_owner, address } => to_binary(
&queries::query_mixnode_delegation(deps, mix_owner, address)?,
),
QueryMsg::GetGatewayDelegations {
gateway_owner,
start_after,
limit,
} => to_binary(&queries::query_gateway_delegations_paged(
deps,
gateway_owner,
start_after,
limit,
)?),
QueryMsg::GetGatewayDelegation {
gateway_owner,
address,
} => to_binary(&queries::query_gateway_delegation(
deps,
gateway_owner,
address,
)?),
};
Ok(query_res?)
+10 -1
View File
@@ -33,7 +33,7 @@ pub enum ContractError {
#[error("Wrong coin denomination, you must send {}", DENOM)]
WrongDenom {},
#[error("Received multiple coin types during bond")]
#[error("Received multiple coin types during staking")]
MultipleDenoms,
#[error("No coin was sent for the bonding, you must send {}", DENOM)]
@@ -59,4 +59,13 @@ pub enum ContractError {
#[error("Gateway with this identity already exists. Its owner is {owner:?}")]
DuplicateGateway { owner: HumanAddr },
#[error("No funds were provided for the delegation")]
EmptyDelegation,
#[error("Could not find any delegation information associated with mixnode owned by {mixnode_owner:?}")]
NoMixnodeDelegationFound { mixnode_owner: HumanAddr },
#[error("Could not find any delegation information associated with gateway owned by {gateway_owner:?}")]
NoGatewayDelegationFound { gateway_owner: HumanAddr },
}
+34
View File
@@ -20,6 +20,22 @@ pub enum HandleMsg {
UnbondGateway {},
UpdateStateParams(StateParams),
DelegateToMixnode {
mix_owner: HumanAddr,
},
UndelegateFromMixnode {
mix_owner: HumanAddr,
},
DelegateToGateway {
gateway_owner: HumanAddr,
},
UndelegateFromGateway {
gateway_owner: HumanAddr,
},
RewardMixnode {
owner: HumanAddr,
// percentage value in range 0-100
@@ -51,6 +67,24 @@ pub enum QueryMsg {
address: HumanAddr,
},
StateParams {},
GetMixDelegations {
mix_owner: HumanAddr,
start_after: Option<HumanAddr>,
limit: Option<u32>,
},
GetMixDelegation {
mix_owner: HumanAddr,
address: HumanAddr,
},
GetGatewayDelegations {
gateway_owner: HumanAddr,
start_after: Option<HumanAddr>,
limit: Option<u32>,
},
GetGatewayDelegation {
gateway_owner: HumanAddr,
address: HumanAddr,
},
LayerDistribution {},
}
+548 -14
View File
@@ -1,23 +1,35 @@
use crate::contract::DENOM;
use crate::error::ContractError;
use crate::state::StateParams;
use crate::storage::{gateways_read, mixnodes_read, read_layer_distribution, read_state_params};
use crate::storage::{
gateway_delegations_read, gateways_read, mix_delegations_read, mixnodes_read,
read_layer_distribution, read_state_params,
};
use cosmwasm_std::Deps;
use cosmwasm_std::HumanAddr;
use cosmwasm_std::Order;
use cosmwasm_std::StdResult;
use cosmwasm_std::{coin, HumanAddr};
use mixnet_contract::{
GatewayBond, GatewayOwnershipResponse, LayerDistribution, MixNodeBond, MixOwnershipResponse,
PagedGatewayResponse, PagedResponse,
Delegation, GatewayBond, GatewayOwnershipResponse, LayerDistribution, MixNodeBond,
MixOwnershipResponse, PagedGatewayDelegationsResponse, PagedGatewayResponse,
PagedMixDelegationsResponse, PagedResponse,
};
const MAX_LIMIT: u32 = 100;
const DEFAULT_LIMIT: u32 = 50;
const BOND_PAGE_MAX_LIMIT: u32 = 100;
const BOND_PAGE_DEFAULT_LIMIT: u32 = 50;
// currently the maximum limit before running into memory issue is somewhere between 1150 and 1200
pub(crate) const DELEGATION_PAGE_MAX_LIMIT: u32 = 750;
const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 500;
pub fn query_mixnodes_paged(
deps: Deps,
start_after: Option<HumanAddr>,
limit: Option<u32>,
) -> StdResult<PagedResponse> {
let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
let limit = limit
.unwrap_or(BOND_PAGE_DEFAULT_LIMIT)
.min(BOND_PAGE_MAX_LIMIT) as usize;
let start = calculate_start_value(start_after);
let nodes = mixnodes_read(deps.storage)
@@ -36,7 +48,9 @@ pub(crate) fn query_gateways_paged(
start_after: Option<HumanAddr>,
limit: Option<u32>,
) -> StdResult<PagedGatewayResponse> {
let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
let limit = limit
.unwrap_or(BOND_PAGE_DEFAULT_LIMIT)
.min(BOND_PAGE_MAX_LIMIT) as usize;
let start = calculate_start_value(start_after);
let nodes = gateways_read(deps.storage)
@@ -93,13 +107,115 @@ fn calculate_start_value(
})
}
pub(crate) fn query_mixnode_delegations_paged(
deps: Deps,
mix_owner: HumanAddr,
start_after: Option<HumanAddr>,
limit: Option<u32>,
) -> StdResult<PagedMixDelegationsResponse> {
let limit = limit
.unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT)
.min(DELEGATION_PAGE_MAX_LIMIT) as usize;
let start = calculate_start_value(start_after);
let delegations = mix_delegations_read(deps.storage, &mix_owner)
.range(start.as_deref(), None, Order::Ascending)
.take(limit)
.map(|res| {
res.map(|entry| {
Delegation::new(
HumanAddr::from(String::from_utf8(entry.0).unwrap()),
coin(entry.1.u128(), DENOM),
)
})
})
.collect::<StdResult<Vec<Delegation>>>()?;
let start_next_after = delegations
.last()
.map(|delegation| delegation.owner().clone());
Ok(PagedMixDelegationsResponse::new(
mix_owner,
delegations,
start_next_after,
))
}
// queries for delegation value of given address for particular node
pub(crate) fn query_mixnode_delegation(
deps: Deps,
mix_owner: HumanAddr,
address: HumanAddr,
) -> Result<Delegation, ContractError> {
match mix_delegations_read(deps.storage, &mix_owner).may_load(address.as_bytes())? {
Some(delegation_value) => Ok(Delegation::new(
address,
coin(delegation_value.u128(), DENOM),
)),
None => Err(ContractError::NoMixnodeDelegationFound {
mixnode_owner: mix_owner,
}),
}
}
pub(crate) fn query_gateway_delegations_paged(
deps: Deps,
gateway_owner: HumanAddr,
start_after: Option<HumanAddr>,
limit: Option<u32>,
) -> StdResult<PagedGatewayDelegationsResponse> {
let limit = limit
.unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT)
.min(DELEGATION_PAGE_MAX_LIMIT) as usize;
let start = calculate_start_value(start_after);
let delegations = gateway_delegations_read(deps.storage, &gateway_owner)
.range(start.as_deref(), None, Order::Ascending)
.take(limit)
.map(|res| {
res.map(|entry| {
Delegation::new(
HumanAddr::from(String::from_utf8(entry.0).unwrap()),
coin(entry.1.u128(), DENOM),
)
})
})
.collect::<StdResult<Vec<Delegation>>>()?;
let start_next_after = delegations
.last()
.map(|delegation| delegation.owner().clone());
Ok(PagedGatewayDelegationsResponse::new(
gateway_owner,
delegations,
start_next_after,
))
}
// queries for delegation value of given address for particular node
pub(crate) fn query_gateway_delegation(
deps: Deps,
gateway_owner: HumanAddr,
address: HumanAddr,
) -> Result<Delegation, ContractError> {
match gateway_delegations_read(deps.storage, &gateway_owner).may_load(address.as_bytes())? {
Some(delegation_value) => Ok(Delegation::new(
address,
coin(delegation_value.u128(), DENOM),
)),
None => Err(ContractError::NoGatewayDelegationFound { gateway_owner }),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::State;
use crate::storage::{config, gateways, mixnodes};
use crate::storage::{config, gateway_delegations, gateways, mix_delegations, mixnodes};
use crate::support::tests::helpers;
use cosmwasm_std::Storage;
use cosmwasm_std::{Storage, Uint128};
#[test]
fn mixnodes_empty_on_init() {
@@ -248,12 +364,12 @@ mod tests {
fn gateways_paged_retrieval_has_default_limit() {
let mut deps = helpers::init_contract();
let storage = deps.as_mut().storage;
store_n_gateway_fixtures(10 * DEFAULT_LIMIT, storage);
store_n_gateway_fixtures(10 * BOND_PAGE_DEFAULT_LIMIT, storage);
// query without explicitly setting a limit
let page1 = query_gateways_paged(deps.as_ref(), None, None).unwrap();
assert_eq!(DEFAULT_LIMIT, page1.nodes.len() as u32);
assert_eq!(BOND_PAGE_DEFAULT_LIMIT, page1.nodes.len() as u32);
}
#[test]
@@ -263,11 +379,11 @@ mod tests {
store_n_gateway_fixtures(100, storage);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * DEFAULT_LIMIT;
let crazy_limit = 1000 * BOND_PAGE_DEFAULT_LIMIT;
let page1 = query_gateways_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap();
// we default to a decent sized upper bound instead
let expected_limit = MAX_LIMIT;
let expected_limit = BOND_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.nodes.len() as u32);
}
@@ -417,4 +533,422 @@ mod tests {
assert_eq!(dummy_state.params, query_state_params(deps.as_ref()))
}
#[cfg(test)]
mod querying_for_mixnode_delegations_paged {
use super::*;
use crate::storage::mix_delegations;
use cosmwasm_std::Uint128;
fn store_n_delegations(n: u32, storage: &mut dyn Storage, node_owner: &HumanAddr) {
for i in 0..n {
let address = format!("address{}", i);
mix_delegations(storage, node_owner)
.save(address.as_bytes(), &Uint128(42))
.unwrap();
}
}
#[test]
fn retrieval_obeys_limits() {
let mut deps = helpers::init_contract();
let limit = 2;
let node_owner: HumanAddr = "foo".into();
store_n_delegations(100, &mut deps.storage, &node_owner);
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_owner,
None,
Option::from(limit),
)
.unwrap();
assert_eq!(limit, page1.delegations.len() as u32);
}
#[test]
fn retrieval_has_default_limit() {
let mut deps = helpers::init_contract();
let node_owner: HumanAddr = "foo".into();
store_n_delegations(
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&node_owner,
);
// query without explicitly setting a limit
let page1 =
query_mixnode_delegations_paged(deps.as_ref(), node_owner, None, None).unwrap();
assert_eq!(
DELEGATION_PAGE_DEFAULT_LIMIT,
page1.delegations.len() as u32
);
}
#[test]
fn retrieval_has_max_limit() {
let mut deps = helpers::init_contract();
let node_owner: HumanAddr = "foo".into();
store_n_delegations(
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&node_owner,
);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT;
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_owner,
None,
Option::from(crazy_limit),
)
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = DELEGATION_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.delegations.len() as u32);
}
#[test]
fn pagination_works() {
let mut deps = helpers::init_contract();
let node_owner: HumanAddr = "foo".into();
mix_delegations(&mut deps.storage, &node_owner)
.save("1".as_bytes(), &Uint128(42))
.unwrap();
let per_page = 2;
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_owner.clone(),
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_owner)
.save("2".as_bytes(), &Uint128(42))
.unwrap();
// page1 should have 2 results on it
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegations.len());
mix_delegations(&mut deps.storage, &node_owner)
.save("3".as_bytes(), &Uint128(42))
.unwrap();
// page1 still has 2 results
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_owner.clone(),
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 = HumanAddr::from("2");
let page2 = query_mixnode_delegations_paged(
deps.as_ref(),
node_owner.clone(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.delegations.len());
// save another one
mix_delegations(&mut deps.storage, &node_owner)
.save("4".as_bytes(), &Uint128(42))
.unwrap();
let start_after = HumanAddr::from("2");
let page2 = query_mixnode_delegations_paged(
deps.as_ref(),
node_owner,
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.delegations.len());
}
}
#[test]
fn mix_deletion_query_returns_current_delegation_value() {
let mut deps = helpers::init_contract();
let node_owner: HumanAddr = "foo".into();
mix_delegations(&mut deps.storage, &node_owner)
.save("foo".as_bytes(), &Uint128(42))
.unwrap();
assert_eq!(
Ok(Delegation::new(node_owner.clone(), coin(42, DENOM))),
query_mixnode_delegation(deps.as_ref(), node_owner, "foo".into())
)
}
#[test]
fn mix_deletion_query_returns_error_if_delegation_doesnt_exist() {
let mut deps = helpers::init_contract();
let node_owner: HumanAddr = "foo".into();
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
mixnode_owner: node_owner.clone(),
}),
query_mixnode_delegation(deps.as_ref(), node_owner.clone(), "foo".into())
);
// add delegation from a different address
mix_delegations(&mut deps.storage, &node_owner)
.save("bar".as_bytes(), &Uint128(42))
.unwrap();
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
mixnode_owner: node_owner.clone(),
}),
query_mixnode_delegation(deps.as_ref(), node_owner.clone(), "foo".into())
);
// add delegation for a different node
mix_delegations(&mut deps.storage, &"differentnode".into())
.save("foo".as_bytes(), &Uint128(42))
.unwrap();
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
mixnode_owner: node_owner.clone(),
}),
query_mixnode_delegation(deps.as_ref(), node_owner.clone(), "foo".into())
)
}
#[cfg(test)]
mod querying_for_gateway_delegations_paged {
use super::*;
use crate::storage::gateway_delegations;
use cosmwasm_std::Uint128;
fn store_n_delegations(n: u32, storage: &mut dyn Storage, node_owner: &HumanAddr) {
for i in 0..n {
let address = format!("address{}", i);
gateway_delegations(storage, node_owner)
.save(address.as_bytes(), &Uint128(42))
.unwrap();
}
}
#[test]
fn retrieval_obeys_limits() {
let mut deps = helpers::init_contract();
let limit = 2;
let node_owner: HumanAddr = "foo".into();
store_n_delegations(100, &mut deps.storage, &node_owner);
let page1 = query_gateway_delegations_paged(
deps.as_ref(),
node_owner,
None,
Option::from(limit),
)
.unwrap();
assert_eq!(limit, page1.delegations.len() as u32);
}
#[test]
fn retrieval_has_default_limit() {
let mut deps = helpers::init_contract();
let node_owner: HumanAddr = "foo".into();
store_n_delegations(
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&node_owner,
);
// query without explicitly setting a limit
let page1 =
query_gateway_delegations_paged(deps.as_ref(), node_owner, None, None).unwrap();
assert_eq!(
DELEGATION_PAGE_DEFAULT_LIMIT,
page1.delegations.len() as u32
);
}
#[test]
fn retrieval_has_max_limit() {
let mut deps = helpers::init_contract();
let node_owner: HumanAddr = "foo".into();
store_n_delegations(
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&node_owner,
);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT;
let page1 = query_gateway_delegations_paged(
deps.as_ref(),
node_owner,
None,
Option::from(crazy_limit),
)
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = DELEGATION_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.delegations.len() as u32);
}
#[test]
fn pagination_works() {
let mut deps = helpers::init_contract();
let node_owner: HumanAddr = "foo".into();
gateway_delegations(&mut deps.storage, &node_owner)
.save("1".as_bytes(), &Uint128(42))
.unwrap();
let per_page = 2;
let page1 = query_gateway_delegations_paged(
deps.as_ref(),
node_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
// page should have 1 result on it
assert_eq!(1, page1.delegations.len());
// save another
gateway_delegations(&mut deps.storage, &node_owner)
.save("2".as_bytes(), &Uint128(42))
.unwrap();
// page1 should have 2 results on it
let page1 = query_gateway_delegations_paged(
deps.as_ref(),
node_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegations.len());
gateway_delegations(&mut deps.storage, &node_owner)
.save("3".as_bytes(), &Uint128(42))
.unwrap();
// page1 still has 2 results
let page1 = query_gateway_delegations_paged(
deps.as_ref(),
node_owner.clone(),
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 = HumanAddr::from("2");
let page2 = query_gateway_delegations_paged(
deps.as_ref(),
node_owner.clone(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.delegations.len());
// save another one
gateway_delegations(&mut deps.storage, &node_owner)
.save("4".as_bytes(), &Uint128(42))
.unwrap();
let start_after = HumanAddr::from("2");
let page2 = query_gateway_delegations_paged(
deps.as_ref(),
node_owner,
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.delegations.len());
}
}
#[test]
fn gateway_deletion_query_returns_current_delegation_value() {
let mut deps = helpers::init_contract();
let node_owner: HumanAddr = "foo".into();
gateway_delegations(&mut deps.storage, &node_owner)
.save("foo".as_bytes(), &Uint128(42))
.unwrap();
assert_eq!(
Ok(Delegation::new(node_owner.clone(), coin(42, DENOM))),
query_gateway_delegation(deps.as_ref(), node_owner, "foo".into())
)
}
#[test]
fn gateway_deletion_query_returns_error_if_delegation_doesnt_exist() {
let mut deps = helpers::init_contract();
let node_owner: HumanAddr = "foo".into();
assert_eq!(
Err(ContractError::NoGatewayDelegationFound {
gateway_owner: node_owner.clone(),
}),
query_gateway_delegation(deps.as_ref(), node_owner.clone(), "foo".into())
);
// add delegation from a different address
gateway_delegations(&mut deps.storage, &node_owner)
.save("bar".as_bytes(), &Uint128(42))
.unwrap();
assert_eq!(
Err(ContractError::NoGatewayDelegationFound {
gateway_owner: node_owner.clone(),
}),
query_gateway_delegation(deps.as_ref(), node_owner.clone(), "foo".into())
);
// add delegation for a different node
gateway_delegations(&mut deps.storage, &"differentnode".into())
.save("foo".as_bytes(), &Uint128(42))
.unwrap();
assert_eq!(
Err(ContractError::NoGatewayDelegationFound {
gateway_owner: node_owner.clone(),
}),
query_gateway_delegation(deps.as_ref(), node_owner.clone(), "foo".into())
)
}
}
+343 -11
View File
@@ -1,13 +1,35 @@
use crate::queries;
use crate::state::{State, StateParams};
use cosmwasm_std::{Decimal, HumanAddr, StdError, StdResult, Storage};
use cosmwasm_std::{Decimal, HumanAddr, Order, StdError, StdResult, Storage, Uint128};
use cosmwasm_storage::{
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
Singleton,
};
use mixnet_contract::{GatewayBond, LayerDistribution, MixNodeBond};
// Contract-level stuff
// storage prefixes
// all of them must be unique and presumably not be a prefix od a different one
// keeping them as short as possible is also desirable as they are part of each stored key
// it's not as important for singletons, but is a nice optimisation for buckets
// the legacy prefixes can't be changed without redeploying contract or doing fancy migration
// for time being let's leave them as they are, but when we're going to make incompatible
// contract changes, let's shorten all of them
// singletons
const CONFIG_KEY: &[u8] = b"config";
const LAYER_DISTRIBUTION_KEY: &[u8] = b"layers";
// buckets
const PREFIX_MIXNODES: &[u8] = b"mixnodes";
const PREFIX_MIXNODES_OWNERS: &[u8] = b"mix-owners";
const PREFIX_GATEWAYS: &[u8] = b"gateways";
const PREFIX_GATEWAYS_OWNERS: &[u8] = b"gateway-owners";
const PREFIX_MIX_DELEGATION: &[u8] = b"md";
const PREFIX_GATEWAY_DELEGATION: &[u8] = b"gd";
// Contract-level stuff
pub fn config(storage: &mut dyn Storage) -> Singleton<State> {
singleton(storage, CONFIG_KEY)
@@ -40,8 +62,6 @@ pub(crate) fn read_gateway_epoch_reward_rate(storage: &dyn Storage) -> Decimal {
.gateway_epoch_bond_reward
}
const LAYER_DISTRIBUTION_KEY: &[u8] = b"layers";
pub fn layer_distribution(storage: &mut dyn Storage) -> Singleton<LayerDistribution> {
singleton(storage, LAYER_DISTRIBUTION_KEY)
}
@@ -127,7 +147,6 @@ pub fn decrement_layer_count(storage: &mut dyn Storage, layer: Layer) -> StdResu
}
// Mixnode-related stuff
const PREFIX_MIXNODES: &[u8] = b"mixnodes";
pub fn mixnodes(storage: &mut dyn Storage) -> Bucket<MixNodeBond> {
bucket(storage, PREFIX_MIXNODES)
@@ -137,8 +156,6 @@ pub fn mixnodes_read(storage: &dyn Storage) -> ReadonlyBucket<MixNodeBond> {
bucket_read(storage, PREFIX_MIXNODES)
}
const PREFIX_MIXNODES_OWNERS: &[u8] = b"mix-owners";
pub fn mixnodes_owners(storage: &mut dyn Storage) -> Bucket<HumanAddr> {
bucket(storage, PREFIX_MIXNODES_OWNERS)
}
@@ -164,6 +181,90 @@ pub(crate) fn increase_mixnode_bond(
mixnodes(storage).save(bond.owner.as_bytes(), &bond)
}
pub(crate) fn increase_mix_delegated_stakes(
storage: &mut dyn Storage,
mix_owner: &HumanAddr,
scaled_reward_rate: Decimal,
) -> StdResult<()> {
let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize;
let mut chunk_start: Option<Vec<_>> = None;
loop {
// get `chunk_size` of delegations
let delegations_chunk = mix_delegations_read(storage, mix_owner)
.range(chunk_start.as_deref(), None, Order::Ascending)
.take(chunk_size)
.collect::<StdResult<Vec<_>>>()?;
if delegations_chunk.is_empty() {
break;
}
// append 0 byte to the last value to start with whatever is the next suceeding key
chunk_start = Some(
delegations_chunk
.last()
.unwrap()
.0
.iter()
.cloned()
.chain(std::iter::once(0u8))
.collect(),
);
// and for each of them increase the stake proportionally to the reward
for (delegator_address, amount) in delegations_chunk.into_iter() {
let reward = amount * scaled_reward_rate;
let new_amount = amount + reward;
mix_delegations(storage, mix_owner).save(&delegator_address, &new_amount)?;
}
}
Ok(())
}
pub(crate) fn increase_gateway_delegated_stakes(
storage: &mut dyn Storage,
gateway_owner: &HumanAddr,
scaled_reward_rate: Decimal,
) -> StdResult<()> {
let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize;
let mut chunk_start: Option<Vec<_>> = None;
loop {
// get `chunk_size` of delegations
let delegations_chunk = gateway_delegations_read(storage, gateway_owner)
.range(chunk_start.as_deref(), None, Order::Ascending)
.take(chunk_size)
.collect::<StdResult<Vec<_>>>()?;
if delegations_chunk.is_empty() {
break;
}
// append 0 byte to the last value to start with whatever is the next suceeding key
chunk_start = Some(
delegations_chunk
.last()
.unwrap()
.0
.iter()
.cloned()
.chain(std::iter::once(0u8))
.collect(),
);
// and for each of them increase the stake proportionally to the reward
for (delegator_address, amount) in delegations_chunk.into_iter() {
let reward = amount * scaled_reward_rate;
let new_amount = amount + reward;
gateway_delegations(storage, gateway_owner).save(&delegator_address, &new_amount)?;
}
}
Ok(())
}
// currently not used outside tests
#[cfg(test)]
pub(crate) fn read_mixnode_bond(
@@ -182,8 +283,6 @@ pub(crate) fn read_mixnode_bond(
// Gateway-related stuff
const PREFIX_GATEWAYS: &[u8] = b"gateways";
pub fn gateways(storage: &mut dyn Storage) -> Bucket<GatewayBond> {
bucket(storage, PREFIX_GATEWAYS)
}
@@ -192,8 +291,6 @@ pub fn gateways_read(storage: &dyn Storage) -> ReadonlyBucket<GatewayBond> {
bucket_read(storage, PREFIX_GATEWAYS)
}
const PREFIX_GATEWAYS_OWNERS: &[u8] = b"gateway-owners";
pub fn gateways_owners(storage: &mut dyn Storage) -> Bucket<HumanAddr> {
bucket(storage, PREFIX_GATEWAYS_OWNERS)
}
@@ -218,6 +315,41 @@ pub(crate) fn increase_gateway_bond(
gateways(storage).save(bond.owner.as_bytes(), &bond)
}
// delegation related
pub fn mix_delegations<'a>(
storage: &'a mut dyn Storage,
mix_owner: &'a HumanAddr,
) -> Bucket<'a, Uint128> {
Bucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_owner.as_bytes()])
}
pub fn mix_delegations_read<'a>(
storage: &'a dyn Storage,
mix_owner: &'a HumanAddr,
) -> ReadonlyBucket<'a, Uint128> {
ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_owner.as_bytes()])
}
pub fn gateway_delegations<'a>(
storage: &'a mut dyn Storage,
gateway_owner: &'a HumanAddr,
) -> Bucket<'a, Uint128> {
Bucket::multilevel(
storage,
&[PREFIX_GATEWAY_DELEGATION, gateway_owner.as_bytes()],
)
}
pub fn gateway_delegations_read<'a>(
storage: &'a dyn Storage,
gateway_owner: &'a HumanAddr,
) -> ReadonlyBucket<'a, Uint128> {
ReadonlyBucket::multilevel(
storage,
&[PREFIX_GATEWAY_DELEGATION, gateway_owner.as_bytes()],
)
}
// currently not used outside tests
#[cfg(test)]
pub(crate) fn read_gateway_bond(
@@ -373,4 +505,204 @@ mod tests {
read_gateway_bond(&storage, node_owner).unwrap()
);
}
#[cfg(test)]
mod increasing_mix_delegated_stakes {
use super::*;
use crate::queries::query_mixnode_delegations_paged;
use cosmwasm_std::testing::mock_dependencies;
#[test]
fn when_there_are_no_delegations() {
let mut deps = mock_dependencies(&[]);
let node_owner = HumanAddr::from("owner");
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
increase_mix_delegated_stakes(&mut deps.storage, &node_owner, reward).unwrap();
// there are no 'new' delegations magically added
assert!(
query_mixnode_delegations_paged(deps.as_ref(), node_owner, None, None)
.unwrap()
.delegations
.is_empty()
)
}
#[test]
fn when_there_is_a_single_delegation() {
let mut deps = mock_dependencies(&[]);
let node_owner = HumanAddr::from("owner");
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
let delegator_address = HumanAddr::from("bob");
mix_delegations(&mut deps.storage, &node_owner)
.save(delegator_address.as_bytes(), &Uint128(1000))
.unwrap();
increase_mix_delegated_stakes(&mut deps.storage, &node_owner, reward).unwrap();
assert_eq!(
Uint128(1001),
mix_delegations_read(&mut deps.storage, &node_owner)
.load(delegator_address.as_bytes())
.unwrap()
)
}
#[test]
fn when_there_are_multiple_delegations() {
let mut deps = mock_dependencies(&[]);
let node_owner = HumanAddr::from("owner");
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
for i in 0..100 {
let delegator_address = HumanAddr::from(format!("address{}", i));
mix_delegations(&mut deps.storage, &node_owner)
.save(delegator_address.as_bytes(), &Uint128(1000))
.unwrap();
}
increase_mix_delegated_stakes(&mut deps.storage, &node_owner, reward).unwrap();
for i in 0..100 {
let delegator_address = HumanAddr::from(format!("address{}", i));
assert_eq!(
Uint128(1001),
mix_delegations_read(&mut deps.storage, &node_owner)
.load(delegator_address.as_bytes())
.unwrap()
)
}
}
#[test]
fn when_there_are_more_delegations_than_page_size() {
let mut deps = mock_dependencies(&[]);
let node_owner = HumanAddr::from("owner");
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
let delegator_address = HumanAddr::from(format!("address{}", i));
mix_delegations(&mut deps.storage, &node_owner)
.save(delegator_address.as_bytes(), &Uint128(1000))
.unwrap();
}
increase_mix_delegated_stakes(&mut deps.storage, &node_owner, reward).unwrap();
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
let delegator_address = HumanAddr::from(format!("address{}", i));
assert_eq!(
Uint128(1001),
mix_delegations_read(&mut deps.storage, &node_owner)
.load(delegator_address.as_bytes())
.unwrap()
)
}
}
}
#[cfg(test)]
mod increasing_gateway_delegated_stakes {
use super::*;
use crate::queries::query_gateway_delegations_paged;
use cosmwasm_std::testing::mock_dependencies;
#[test]
fn when_there_are_no_delegations() {
let mut deps = mock_dependencies(&[]);
let node_owner = HumanAddr::from("owner");
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
increase_gateway_delegated_stakes(&mut deps.storage, &node_owner, reward).unwrap();
// there are no 'new' delegations magically added
assert!(
query_gateway_delegations_paged(deps.as_ref(), node_owner, None, None)
.unwrap()
.delegations
.is_empty()
)
}
#[test]
fn when_there_is_a_single_delegation() {
let mut deps = mock_dependencies(&[]);
let node_owner = HumanAddr::from("owner");
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
let delegator_address = HumanAddr::from("bob");
gateway_delegations(&mut deps.storage, &node_owner)
.save(delegator_address.as_bytes(), &Uint128(1000))
.unwrap();
increase_gateway_delegated_stakes(&mut deps.storage, &node_owner, reward).unwrap();
assert_eq!(
Uint128(1001),
gateway_delegations_read(&mut deps.storage, &node_owner)
.load(delegator_address.as_bytes())
.unwrap()
)
}
#[test]
fn when_there_are_multiple_delegations() {
let mut deps = mock_dependencies(&[]);
let node_owner = HumanAddr::from("owner");
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
for i in 0..100 {
let delegator_address = HumanAddr::from(format!("address{}", i));
gateway_delegations(&mut deps.storage, &node_owner)
.save(delegator_address.as_bytes(), &Uint128(1000))
.unwrap();
}
increase_gateway_delegated_stakes(&mut deps.storage, &node_owner, reward).unwrap();
for i in 0..100 {
let delegator_address = HumanAddr::from(format!("address{}", i));
assert_eq!(
Uint128(1001),
gateway_delegations_read(&mut deps.storage, &node_owner)
.load(delegator_address.as_bytes())
.unwrap()
)
}
}
#[test]
fn when_there_are_more_delegations_than_page_size() {
let mut deps = mock_dependencies(&[]);
let node_owner = HumanAddr::from("owner");
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
let delegator_address = HumanAddr::from(format!("address{}", i));
gateway_delegations(&mut deps.storage, &node_owner)
.save(delegator_address.as_bytes(), &Uint128(1000))
.unwrap();
}
increase_gateway_delegated_stakes(&mut deps.storage, &node_owner, reward).unwrap();
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
let delegator_address = HumanAddr::from(format!("address{}", i));
assert_eq!(
Uint128(1001),
gateway_delegations_read(&mut deps.storage, &node_owner)
.load(delegator_address.as_bytes())
.unwrap()
)
}
}
}
}
File diff suppressed because it is too large Load Diff