Compare commits

..

1 Commits

Author SHA1 Message Date
Mark Sinclair 4ca8cdcf5a Testing CI 2021-11-24 18:01:32 +00:00
278 changed files with 9923 additions and 27651 deletions
@@ -0,0 +1,58 @@
name: ERC20 Bridge Contract
on: [ push, pull_request ]
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/contract_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
erc20-bridge-contract:
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.rust == 'nightly' }}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- uses: actions-rs/cargo@v1
env:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/erc20-bridge/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path contracts/erc20-bridge/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path contracts/erc20-bridge/Cargo.toml -- --check
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/erc20-bridge/Cargo.toml -- -D warnings
@@ -1,4 +1,4 @@
name: Contracts
name: Mixnet Contract
on:
push:
@@ -21,7 +21,7 @@ jobs:
with:
inputFile: '.github/workflows/contract_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
contracts:
mixnet-contract:
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
@@ -45,20 +45,20 @@ jobs:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/Cargo.toml --all --target wasm32-unknown-unknown
args: --manifest-path contracts/mixnet/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path contracts/Cargo.toml
args: --manifest-path contracts/mixnet/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path contracts/Cargo.toml --all -- --check
args: --manifest-path contracts/mixnet/Cargo.toml -- --check
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/Cargo.toml --all -- -D warnings
args: --manifest-path contracts/mixnet/Cargo.toml -- -D warnings
-2
View File
@@ -22,8 +22,6 @@ jobs:
node-version: '14'
- run: npm install
continue-on-error: true
- name: Set environment from the example
run: cp .env.prod .env
- run: npm run test
continue-on-error: true
- run: npm run build
@@ -1,4 +1,4 @@
name: Publish Nym Wallet
name: Publish Tauri Wallet
on:
push:
tags:
+1 -2
View File
@@ -34,5 +34,4 @@ contracts/mixnet/code_id
contracts/mixnet/Justfile
contracts/mixnet/Makefile
validator-config
*.patch
validator-api-config.toml
*.patch
Generated
+10 -40
View File
@@ -868,7 +868,8 @@ dependencies = [
[[package]]
name = "cosmos-sdk-proto"
version = "0.8.0"
source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb5204c6ddc4352c74297638b5561f2929d6334866c156e5f3c75e1e1a1436a"
dependencies = [
"prost",
"prost-types",
@@ -878,7 +879,8 @@ dependencies = [
[[package]]
name = "cosmrs"
version = "0.3.0"
source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d31147fe89e547e74e2692e4bec35387e1ea406fe8cfb14c0ea177b58fd2a8a9"
dependencies = [
"bip32",
"cosmos-sdk-proto",
@@ -899,9 +901,8 @@ dependencies = [
[[package]]
name = "cosmwasm-crypto"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
@@ -912,18 +913,16 @@ dependencies = [
[[package]]
name = "cosmwasm-derive"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"syn",
]
[[package]]
name = "cosmwasm-std"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"base64",
"cosmwasm-crypto",
@@ -1208,17 +1207,6 @@ 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 = "darling"
version = "0.10.2"
@@ -3574,10 +3562,8 @@ name = "nym-gateway"
version = "0.11.0"
dependencies = [
"bip39",
"bs58",
"clap",
"coconut-interface",
"colored",
"config",
"credentials",
"crypto",
@@ -3599,7 +3585,6 @@ dependencies = [
"rand 0.7.3",
"serde",
"sqlx",
"subtle-encoding",
"thiserror",
"tokio",
"tokio-stream",
@@ -3635,7 +3620,6 @@ dependencies = [
"rocket",
"serde",
"serial_test",
"subtle-encoding",
"tokio",
"tokio-util",
"toml",
@@ -7145,7 +7129,6 @@ dependencies = [
"thiserror",
"ts-rs",
"url",
"vesting-contract",
]
[[package]]
@@ -7185,19 +7168,6 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "vesting-contract"
version = "0.1.0"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus",
"mixnet-contract",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "waker-fn"
version = "1.1.0"
-1
View File
@@ -62,7 +62,6 @@ default-members = [
"service-providers/network-requester",
"mixnode",
"validator-api",
"explorer-api",
]
exclude = ["explorer", "contracts", "tokenomics-py"]
-31
View File
@@ -1,31 +0,0 @@
all: clippy test fmt
clippy: clippy-main clippy-contracts clippy-wallet
test: test-main test-contracts test-wallet
fmt: fmt-main fmt-contracts fmt-wallet
clippy-main:
cargo clippy
clippy-contracts:
cargo clippy --manifest-path contracts/Cargo.toml
clippy-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml
test-main:
cargo test
test-contracts:
cargo test --manifest-path contracts/Cargo.toml
test-wallet:
cargo test --manifest-path nym-wallet/Cargo.toml
fmt-main:
cargo fmt --all
fmt-contracts:
cargo fmt --manifest-path contracts/Cargo.toml --all
fmt-wallet:
cargo fmt --manifest-path nym-wallet/Cargo.toml --all
+1 -1
View File
@@ -13,7 +13,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
* nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices.
* nym-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
* nym-explorer - a (projected) block explorer and (existing) mixnet viewer.
* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
* nym-wallet (currently in development)- a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0)
[![Build Status](https://img.shields.io/github/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
@@ -3,5 +3,6 @@ module github.com/nymtech/nym/clients/native/examples/go
go 1.14
require (
github.com/btcsuite/btcutil v1.0.2 // indirect
github.com/gorilla/websocket v1.4.2
)
+26 -26
View File
@@ -1,6 +1,6 @@
import NetClient, { INetClient } from "./net-client";
import NetClient, {INetClient} from "./net-client";
import {
ContractSettingsParams,
StateParams,
Delegation,
PagedMixDelegationsResponse,
PagedGatewayDelegationsResponse,
@@ -10,10 +10,10 @@ import {
Gateway,
SendRequest
} from "./types";
import { Bip39, Random } from "@cosmjs/crypto";
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
import {Bip39, Random} from "@cosmjs/crypto";
import {DirectSecp256k1HdWallet, EncodeObject} from "@cosmjs/proto-signing";
import MixnodesCache from "./caches/mixnodes";
import { buildFeeTable, coin, Coin, coins, StdFee } from "@cosmjs/stargate";
import {buildFeeTable, coin, Coin, coins, StdFee} from "@cosmjs/stargate";
import {
ExecuteResult,
InstantiateOptions,
@@ -32,17 +32,17 @@ import {
nativeToPrintable
} from "./currency";
import GatewaysCache from "./caches/gateways";
import QueryClient, { IQueryClient } from "./query-client";
import { nymGasLimits, nymGasPrice } from "./stargate-helper";
import { BroadcastTxSuccess, isBroadcastTxFailure } from "@cosmjs/stargate";
import { makeBankMsgSend } from "./utils";
import QueryClient, {IQueryClient} from "./query-client";
import {nymGasLimits, nymGasPrice} from "./stargate-helper";
import {BroadcastTxSuccess, isBroadcastTxFailure} from "@cosmjs/stargate";
import {makeBankMsgSend} from "./utils";
export const VALIDATOR_API_PORT = "8080";
export const VALIDATOR_API_GATEWAYS = "v1/gateways";
export const VALIDATOR_API_MIXNODES = "v1/mixnodes";
export { coins, coin };
export { Coin };
export {coins, coin};
export {Coin};
export {
displayAmountToNative,
nativeCoinToDisplay,
@@ -52,7 +52,7 @@ export {
MappedCoin,
CoinMap
}
export { nymGasLimits, nymGasPrice }
export {nymGasLimits, nymGasPrice}
export default class ValidatorClient {
private readonly client: INetClient | IQueryClient
@@ -226,12 +226,12 @@ export default class ValidatorClient {
*/
static async mnemonicToAddress(mnemonic: string, prefix: string): Promise<string> {
const wallet = await ValidatorClient.buildWallet(mnemonic, prefix);
const [{ address }] = await wallet.getAccounts()
const [{address}] = await wallet.getAccounts()
return address
}
static async buildWallet(mnemonic: string, prefix: string): Promise<DirectSecp256k1HdWallet> {
const signerOptions = { prefix: prefix };
const signerOptions = {prefix: prefix};
return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, signerOptions);
}
@@ -239,7 +239,7 @@ export default class ValidatorClient {
return this.client.getBalance(address, this.denom).catch((err) => this.handleRequestFailure(err));
}
async getStateParams(): Promise<ContractSettingsParams> {
async getStateParams(): Promise<StateParams> {
return this.client.getStateParams(this.contractAddress).catch((err) => this.handleRequestFailure(err))
}
@@ -293,7 +293,7 @@ export default class ValidatorClient {
*/
async bondMixnode(mixNode: MixNode, bond: Coin): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { bond_mixnode: { mix_node: mixNode } }, "adding mixnode", [bond]).catch((err) => this.handleRequestFailure(err));
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {bond_mixnode: {mix_node: mixNode}}, "adding mixnode", [bond]).catch((err) => this.handleRequestFailure(err));
console.log(`account ${this.client.clientAddress} added mixnode with ${mixNode.host}`);
return result;
} else {
@@ -307,7 +307,7 @@ export default class ValidatorClient {
*/
async unbondMixnode(): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { unbond_mixnode: {} }).catch((err) => this.handleRequestFailure(err))
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {unbond_mixnode: {}}).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} unbonded mixnode`);
return result;
} else {
@@ -324,7 +324,7 @@ export default class ValidatorClient {
// requires coin type to ensure correct denomination (
async delegateToMixnode(mixIdentity: 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_identity: mixIdentity } }, `delegating to ${mixIdentity}`, [amount]).catch((err) => this.handleRequestFailure(err))
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {delegate_to_mixnode: {mix_identity: mixIdentity}}, `delegating to ${mixIdentity}`, [amount]).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} delegated ${amount} to mixnode ${mixIdentity}`);
return result;
} else {
@@ -339,7 +339,7 @@ export default class ValidatorClient {
*/
async removeMixnodeDelegation(mixIdentity: string): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { undelegate_from_mixnode: { mix_identity: mixIdentity } }).catch((err) => this.handleRequestFailure(err))
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {undelegate_from_mixnode: {mix_identity: mixIdentity}}).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} removed delegation from mixnode ${mixIdentity}`);
return result;
} else {
@@ -356,7 +356,7 @@ export default class ValidatorClient {
// requires coin type to ensure correct denomination (
async delegateToGateway(gatewayIdentity: 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_identity: gatewayIdentity } }, `delegating to ${gatewayIdentity}`, [amount]).catch((err) => this.handleRequestFailure(err))
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {delegate_to_gateway: {gateway_identity: gatewayIdentity}}, `delegating to ${gatewayIdentity}`, [amount]).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} delegated ${amount} to gateway ${gatewayIdentity}`);
return result;
} else {
@@ -371,7 +371,7 @@ export default class ValidatorClient {
*/
async removeGatewayDelegation(gatewayIdentity: string): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { undelegate_from_gateway: { gateway_identity: gatewayIdentity } }).catch((err) => this.handleRequestFailure(err))
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {undelegate_from_gateway: {gateway_identity: gatewayIdentity}}).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} removed delegation from gateway ${gatewayIdentity}`);
return result;
} else {
@@ -450,7 +450,7 @@ export default class ValidatorClient {
*/
async bondGateway(gateway: Gateway, bond: Coin): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { bond_gateway: { gateway: gateway } }, "adding gateway", [bond]).catch((err) => this.handleRequestFailure(err));
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {bond_gateway: {gateway: gateway}}, "adding gateway", [bond]).catch((err) => this.handleRequestFailure(err));
console.log(`account ${this.client.clientAddress} added gateway with ${gateway.host}`);
return result;
} else {
@@ -463,7 +463,7 @@ export default class ValidatorClient {
*/
async unbondGateway(): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { unbond_gateway: {} }).catch((err) => this.handleRequestFailure(err))
const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {unbond_gateway: {}}).catch((err) => this.handleRequestFailure(err))
console.log(`account ${this.client.clientAddress} unbonded gateway`);
return result;
} else {
@@ -471,9 +471,9 @@ export default class ValidatorClient {
}
}
async updateStateParams(newParams: ContractSettingsParams): Promise<ExecuteResult> {
async updateStateParams(newParams: StateParams): Promise<ExecuteResult> {
if (this.client instanceof NetClient) {
return await this.client.executeContract(this.client.clientAddress, this.contractAddress, { update_contract_settings: newParams }, "updating contract settings").catch((err) => this.handleRequestFailure(err));
return await this.client.executeContract(this.client.clientAddress, this.contractAddress, {update_state_params: newParams}, "updating contract state").catch((err) => this.handleRequestFailure(err));
} else {
throw new Error("Tried to update state params with a query client")
}
@@ -580,7 +580,7 @@ export default class ValidatorClient {
// the function to calculate fee for a single entry is not exposed...
console.log(`this.denom is ${this.denom}`);
const table = buildFeeTable(nymGasPrice(this.prefix), { sendMultiple: nymGasLimits.send * data.length }, { sendMultiple: nymGasLimits.send * data.length })
const table = buildFeeTable(nymGasPrice(this.prefix), {sendMultiple: nymGasLimits.send * data.length}, {sendMultiple: nymGasLimits.send * data.length})
const fee = table.sendMultiple
const result = await this.client.signAndBroadcast(senderAddress, encoded, fee, memo)
if (isBroadcastTxFailure(result)) {
+8 -8
View File
@@ -80,7 +80,7 @@ export default class NetClient implements INetClient {
}
public static async connect(wallet: DirectSecp256k1HdWallet, url: string, prefix: string): Promise<INetClient> {
const [{ address }] = await wallet.getAccounts();
const [{address}] = await wallet.getAccounts();
const signerOptions: SigningCosmWasmClientOptions = {
gasPrice: nymGasPrice(prefix),
gasLimits: nymGasLimits,
@@ -95,17 +95,17 @@ export default class NetClient implements INetClient {
public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse> {
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_nodes: { limit } });
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_nodes: { limit, start_after } });
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit, start_after}});
}
}
public getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse> {
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_gateways: { limit } });
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, { get_gateways: { limit, start_after } });
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit, start_after}});
}
}
@@ -166,11 +166,11 @@ export default class NetClient implements INetClient {
}
public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, { owns_mixnode: { address } });
return this.cosmClient.queryContractSmart(contractAddress, {owns_mixnode: {address}});
}
public ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, { owns_gateway: { address } });
return this.cosmClient.queryContractSmart(contractAddress, {owns_gateway: {address}});
}
public getBalance(address: string, denom: string): Promise<Coin | null> {
@@ -178,7 +178,7 @@ export default class NetClient implements INetClient {
}
public getStateParams(contractAddress: string): Promise<StateParams> {
return this.cosmClient.queryContractSmart(contractAddress, { contract_settings_params: {} });
return this.cosmClient.queryContractSmart(contractAddress, {state_params: {}});
}
public executeContract(senderAddress: string, contractAddress: string, handleMsg: Record<string, unknown>, memo?: string, transferAmount?: readonly Coin[]): Promise<ExecuteResult> {
+10 -10
View File
@@ -6,7 +6,7 @@ import {
MixOwnershipResponse, PagedGatewayDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse,
ContractSettingsParams
StateParams
} from "./types";
export interface IQueryClient {
@@ -28,7 +28,7 @@ export interface IQueryClient {
ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(contractAddress: string): Promise<ContractSettingsParams>;
getStateParams(contractAddress: string): Promise<StateParams>;
changeValidator(newUrl: string): Promise<void>
}
@@ -59,17 +59,17 @@ export default class QueryClient implements IQueryClient {
public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse> {
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_nodes: { limit } });
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_nodes: { limit, start_after } });
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit, start_after}});
}
}
public getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse> {
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_gateways: { limit } });
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, { get_gateways: { limit, start_after } });
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit, start_after}});
}
}
@@ -130,18 +130,18 @@ export default class QueryClient implements IQueryClient {
}
public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, { owns_mixnode: { address } });
return this.cosmClient.queryContractSmart(contractAddress, {owns_mixnode: {address}});
}
public ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, { owns_gateway: { address } });
return this.cosmClient.queryContractSmart(contractAddress, {owns_gateway: {address}});
}
public getBalance(address: string, stakeDenom: string): Promise<Coin | null> {
return this.cosmClient.getBalance(address, stakeDenom);
}
public getStateParams(contractAddress: string): Promise<ContractSettingsParams> {
return this.cosmClient.queryContractSmart(contractAddress, { contract_settings_params: {} });
public getStateParams(contractAddress: string): Promise<StateParams> {
return this.cosmClient.queryContractSmart(contractAddress, {state_params: {}});
}
}
+2 -2
View File
@@ -42,7 +42,7 @@ export type GatewayOwnershipResponse = {
has_gateway: boolean,
}
export type ContractSettingsParams = {
export type StateParams = {
epoch_length: number,
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and Decimal that don't have
@@ -103,7 +103,7 @@ export type MixNode = {
export type GatewayBond = {
owner: string
gateway: Gateway,
bond_amount: Coin,
total_delegation: Coin,
}
@@ -10,7 +10,6 @@ rust-version = "1.56"
[dependencies]
base64 = "0.13"
mixnet-contract = { path="../../../common/mixnet-contract" }
vesting-contract = { path="../../../contracts/vesting" }
serde = { version="1", features=["derive"] }
serde_json = "1"
reqwest = { version="0.11", features=["json"] }
@@ -27,13 +26,12 @@ network-defaults = { path = "../../network-defaults" }
async-trait = { version = "0.1.51", optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
config = { path = "../../config", optional = true }
#cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"], optional = true }
cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev="e5a1872083abb3d88fa62dda966e7f5408deba58", features = ["rpc", "bip32", "cosmwasm"], optional = true }
cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"], optional = true }
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 = { version = "1.0.0-beta2", optional = true }
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true }
ts-rs = {version = "5.1", optional = true}
[features]
@@ -6,18 +6,13 @@ use crate::nymd::{
error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
};
#[cfg(feature = "nymd-client")]
use mixnet_contract::ContractStateParams;
use mixnet_contract::StateParams;
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract::{GatewayBond, MixNodeBond, MixnodeRewardingStatusResponse};
#[cfg(feature = "nymd-client")]
use mixnet_contract::{
Delegation, MixnetContractVersion, MixnodeRewardingStatusResponse, RewardingIntervalResponse,
};
use mixnet_contract::{GatewayBond, MixNodeBond};
#[cfg(feature = "nymd-client")]
use std::str::FromStr;
use mixnet_contract::{RawDelegationData, RewardingIntervalResponse};
use url::Url;
#[cfg(feature = "nymd-client")]
@@ -25,7 +20,6 @@ pub struct Config {
api_url: Url,
nymd_url: Url,
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
@@ -38,12 +32,10 @@ impl Config {
nymd_url: Url,
api_url: Url,
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
) -> Self {
Config {
nymd_url,
mixnet_contract_address,
vesting_contract_address,
api_url,
mixnode_page_limit: None,
gateway_page_limit: None,
@@ -70,7 +62,6 @@ impl Config {
#[cfg(feature = "nymd-client")]
pub struct Client<C> {
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
mnemonic: Option<bip39::Mnemonic>,
mixnode_page_limit: Option<u32>,
@@ -92,14 +83,11 @@ impl Client<SigningNymdClient> {
let nymd_client = NymdClient::connect_with_mnemonic(
config.nymd_url.as_str(),
config.mixnet_contract_address.clone(),
config.vesting_contract_address.clone(),
mnemonic.clone(),
None,
)?;
Ok(Client {
mixnet_contract_address: config.mixnet_contract_address,
vesting_contract_address: config.vesting_contract_address,
mnemonic: Some(mnemonic),
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
@@ -113,9 +101,7 @@ impl Client<SigningNymdClient> {
self.nymd = NymdClient::connect_with_mnemonic(
new_endpoint.as_ref(),
self.mixnet_contract_address.clone(),
self.vesting_contract_address.clone(),
self.mnemonic.clone().unwrap(),
None,
)?;
Ok(())
}
@@ -127,19 +113,16 @@ impl Client<QueryNymdClient> {
let validator_api_client = validator_api::Client::new(config.api_url.clone());
let nymd_client = NymdClient::connect(
config.nymd_url.as_str(),
config.mixnet_contract_address.clone().unwrap_or_else(|| {
cosmrs::AccountId::from_str(network_defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS)
.unwrap()
}),
config.vesting_contract_address.clone().unwrap_or_else(|| {
cosmrs::AccountId::from_str(network_defaults::DEFAULT_VESTING_CONTRACT_ADDRESS)
.unwrap()
}),
config
.mixnet_contract_address
.clone()
.ok_or(ValidatorClientError::NymdError(
NymdError::NoContractAddressAvailable,
))?,
)?;
Ok(Client {
mixnet_contract_address: config.mixnet_contract_address,
vesting_contract_address: config.vesting_contract_address,
mnemonic: None,
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
@@ -153,7 +136,6 @@ impl Client<QueryNymdClient> {
self.nymd = NymdClient::connect(
new_endpoint.as_ref(),
self.mixnet_contract_address.clone().unwrap(),
self.vesting_contract_address.clone().unwrap(),
)?;
Ok(())
}
@@ -183,18 +165,11 @@ impl<C> Client<C> {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn get_contract_settings(&self) -> Result<ContractStateParams, ValidatorClientError>
pub async fn get_state_params(&self) -> Result<StateParams, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_contract_settings().await?)
}
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_mixnet_contract_version().await?)
Ok(self.nymd.get_state_params().await?)
}
pub async fn get_current_rewarding_interval(
@@ -325,7 +300,9 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_network_delegations(&self) -> Result<Vec<Delegation>, ValidatorClientError>
pub async fn get_all_nymd_mixnode_delegations(
&self,
) -> Result<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
@@ -334,7 +311,7 @@ impl<C> Client<C> {
loop {
let mut paged_response = self
.nymd
.get_all_network_delegations_paged(
.get_all_mix_delegations_paged(
start_after.take(),
self.mixnode_delegations_page_limit,
)
@@ -351,10 +328,10 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_delegator_delegations(
pub async fn get_all_nymd_reverse_mixnode_delegations(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<Delegation>, ValidatorClientError>
) -> Result<Vec<mixnet_contract::IdentityKey>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
@@ -363,13 +340,13 @@ impl<C> Client<C> {
loop {
let mut paged_response = self
.nymd
.get_delegator_delegations_paged(
delegation_owner.to_string(),
.get_reverse_mix_delegations_paged(
mixnet_contract::Addr::unchecked(delegation_owner.as_ref()),
start_after.take(),
self.mixnode_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegations);
delegations.append(&mut paged_response.delegated_nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
@@ -381,6 +358,28 @@ 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,
@@ -4,7 +4,7 @@
use crate::nymd::cosmwasm_client::helpers::create_pagination;
use crate::nymd::cosmwasm_client::types::{
Account, Code, CodeDetails, Contract, ContractCodeHistoryEntry, ContractCodeId,
SequenceResponse, SimulateResponse,
SequenceResponse,
};
use crate::nymd::error::NymdError;
use async_trait::async_trait;
@@ -14,19 +14,15 @@ use cosmrs::proto::cosmos::auth::v1beta1::{
use cosmrs::proto::cosmos::bank::v1beta1::{
QueryAllBalancesRequest, QueryAllBalancesResponse, QueryBalanceRequest, QueryBalanceResponse,
};
use cosmrs::proto::cosmos::tx::v1beta1::{
SimulateRequest, SimulateResponse as ProtoSimulateResponse,
};
use cosmrs::proto::cosmwasm::wasm::v1::*;
use cosmrs::proto::cosmwasm::wasm::v1beta1::*;
use cosmrs::rpc::endpoint::block::Response as BlockResponse;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::endpoint::tx::Response as TxResponse;
use cosmrs::rpc::query::Query;
use cosmrs::rpc::{self, HttpClient, Order};
use cosmrs::tendermint::abci::Code as AbciCode;
use cosmrs::tendermint::abci::Transaction;
use cosmrs::tendermint::{abci, block, chain};
use cosmrs::{tx, AccountId, Coin, Denom, Tx};
use cosmrs::{tx, AccountId, Coin, Denom};
use prost::Message;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
@@ -53,11 +49,6 @@ pub trait CosmWasmClient: rpc::Client {
let res = self.abci_query(path, buf, None, false).await?;
match res.code {
AbciCode::Err(code) => return Err(NymdError::AbciError(code, res.log)),
AbciCode::Ok => (),
}
Ok(Res::decode(res.value.as_ref())?)
}
@@ -222,7 +213,7 @@ pub trait CosmWasmClient: rpc::Client {
}
async fn get_codes(&self) -> Result<Vec<Code>, NymdError> {
let path = Some("/cosmwasm.wasm.v1.Query/Codes".parse().unwrap());
let path = Some("/cosmwasm.wasm.v1beta1.Query/Codes".parse().unwrap());
let mut raw_codes = Vec::new();
let mut pagination = None;
@@ -249,7 +240,7 @@ pub trait CosmWasmClient: rpc::Client {
}
async fn get_code_details(&self, code_id: ContractCodeId) -> Result<CodeDetails, NymdError> {
let path = Some("/cosmwasm.wasm.v1.Query/Code".parse().unwrap());
let path = Some("/cosmwasm.wasm.v1beta1.Query/Code".parse().unwrap());
let req = QueryCodeRequest { code_id };
@@ -264,7 +255,11 @@ pub trait CosmWasmClient: rpc::Client {
}
}
async fn get_contracts(&self, code_id: ContractCodeId) -> Result<Vec<AccountId>, NymdError> {
let path = Some("/cosmwasm.wasm.v1.Query/ContractsByCode".parse().unwrap());
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/ContractsByCode"
.parse()
.unwrap(),
);
let mut raw_contracts = Vec::new();
let mut pagination = None;
@@ -295,7 +290,7 @@ pub trait CosmWasmClient: rpc::Client {
}
async fn get_contract(&self, address: &AccountId) -> Result<Contract, NymdError> {
let path = Some("/cosmwasm.wasm.v1.Query/ContractInfo".parse().unwrap());
let path = Some("/cosmwasm.wasm.v1beta1.Query/ContractInfo".parse().unwrap());
let req = QueryContractInfoRequest {
address: address.to_string(),
@@ -320,7 +315,11 @@ pub trait CosmWasmClient: rpc::Client {
&self,
address: &AccountId,
) -> Result<Vec<ContractCodeHistoryEntry>, NymdError> {
let path = Some("/cosmwasm.wasm.v1.Query/ContractHistory".parse().unwrap());
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/ContractHistory"
.parse()
.unwrap(),
);
let mut raw_entries = Vec::new();
let mut pagination = None;
@@ -354,7 +353,11 @@ pub trait CosmWasmClient: rpc::Client {
address: &AccountId,
query_data: Vec<u8>,
) -> Result<Vec<u8>, NymdError> {
let path = Some("/cosmwasm.wasm.v1.Query/RawContractState".parse().unwrap());
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/RawContractState"
.parse()
.unwrap(),
);
let req = QueryRawContractStateRequest {
address: address.to_string(),
@@ -378,7 +381,7 @@ pub trait CosmWasmClient: rpc::Client {
for<'a> T: Deserialize<'a>,
{
let path = Some(
"/cosmwasm.wasm.v1.Query/SmartContractState"
"/cosmwasm.wasm.v1beta1.Query/SmartContractState"
.parse()
.unwrap(),
);
@@ -397,27 +400,4 @@ pub trait CosmWasmClient: rpc::Client {
Ok(serde_json::from_slice(&res.data)?)
}
// deprecation warning is due to the fact the protobuf files built were based on cosmos-sdk 0.44,
// where they prefer using tx_bytes directly. However, in 0.42, which we are using at the time
// of writing this, the option does not work
#[allow(deprecated)]
async fn query_simulate(
&self,
tx: Option<Tx>,
tx_bytes: Vec<u8>,
) -> Result<SimulateResponse, NymdError> {
let path = Some("/cosmos.tx.v1beta1.Service/Simulate".parse().unwrap());
let req = SimulateRequest {
tx: tx.map(Into::into),
tx_bytes,
};
let res = self
.make_abci_query::<_, ProtoSimulateResponse>(path, req)
.await?;
res.try_into()
}
}
@@ -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.ty == event_type)?
.find(|event| event.kind == 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].ty, "message");
assert_eq!(parsed[0].events[0].kind, "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].ty, "message");
assert_eq!(parsed[0].events[0].kind, "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].ty, "message");
assert_eq!(parsed[2].events[0].kind, "message");
assert_eq!(parsed[2].events[0].attributes[2].key, "signer");
assert_eq!(
parsed[2].events[0].attributes[2].value,
@@ -3,7 +3,6 @@
use crate::nymd::error::NymdError;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use crate::nymd::GasPrice;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl};
use std::convert::TryInto;
@@ -24,10 +23,9 @@ where
pub fn connect_with_signer<U>(
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: Option<GasPrice>,
) -> Result<signing_client::Client, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
signing_client::Client::connect_with_signer(endpoint, signer, gas_price)
signing_client::Client::connect_with_signer(endpoint, signer)
}
@@ -1,100 +1,37 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::convert::TryInto;
use async_trait::async_trait;
use cosmrs::bank::MsgSend;
use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{self, Msg, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, AccountId, Any, Coin, Tx};
use log::debug;
use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse};
use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs};
use crate::nymd::cosmwasm_client::types::*;
use crate::nymd::error::NymdError;
use crate::nymd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER};
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use crate::nymd::{CosmosCoin, GasPrice};
// we need to have **a** valid secp256k1 signature for simulation purposes.
// it doesn't matter what it is as long as it parses correctly
const DUMMY_SECP256K1_SIGNATURE: &[u8] = &[
54, 167, 169, 61, 100, 173, 231, 87, 1, 113, 179, 49, 102, 141, 67, 22, 170, 153, 52, 88, 178,
159, 200, 11, 37, 138, 76, 221, 187, 70, 104, 123, 98, 216, 190, 249, 149, 81, 1, 158, 0, 220,
32, 147, 101, 60, 64, 77, 44, 83, 221, 119, 170, 124, 109, 177, 73, 116, 46, 57, 102, 181, 98,
91,
];
use async_trait::async_trait;
use cosmrs::bank::MsgSend;
use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{Fee, Msg, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, tx, AccountId, Any, Coin};
use log::debug;
use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
use std::convert::TryInto;
#[async_trait]
pub trait SigningCosmWasmClient: CosmWasmClient {
fn signer(&self) -> &DirectSecp256k1HdWallet;
fn gas_price(&self) -> &GasPrice;
fn signer_public_key(&self, signer_address: &AccountId) -> Option<tx::SignerPublicKey> {
let signer_accounts = self.signer().try_derive_accounts().ok()?;
let account_from_signer = signer_accounts
.iter()
.find(|account| &account.address == signer_address)?;
let public_key = account_from_signer.public_key;
Some(public_key.into())
}
async fn simulate(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
memo: impl Into<String> + Send + 'static,
) -> Result<SimulateResponse, NymdError> {
let public_key = self.signer_public_key(signer_address);
let sequence_response = self.get_sequence(signer_address).await?;
let partial_tx = Tx {
body: tx::Body {
messages,
memo: memo.into(),
timeout_height: 0u32.into(),
extension_options: vec![],
non_critical_extension_options: vec![],
},
auth_info: tx::AuthInfo {
signer_infos: vec![tx::SignerInfo {
public_key,
mode_info: tx::ModeInfo::Single(tx::mode_info::Single {
mode: SignMode::Unspecified,
}),
sequence: sequence_response.sequence,
}],
fee: tx::Fee::from_amount_and_gas(
CosmosCoin {
denom: "".parse().unwrap(),
amount: 0u64.into(),
},
0,
),
},
signatures: vec![DUMMY_SECP256K1_SIGNATURE.try_into().unwrap()],
};
self.query_simulate(Some(partial_tx), Vec::new()).await
}
async fn upload(
&self,
sender_address: &AccountId,
wasm_code: Vec<u8>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
mut meta: Option<UploadMeta>,
) -> Result<UploadResult, NymdError> {
let compressed = compress_wasm_code(&wasm_code)?;
let compressed_size = compressed.len();
@@ -105,6 +42,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
let upload_msg = cosmwasm::MsgStoreCode {
sender: sender_address.clone(),
wasm_byte_code: compressed,
source: meta
.as_mut()
.map(|meta| meta.source.take())
.unwrap_or_default(),
builder: meta
.as_mut()
.map(|meta| meta.builder.take())
.unwrap_or_default(),
instantiate_permission: Default::default(),
}
.to_any()
@@ -116,13 +61,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.check_response()?;
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
// TODO: should those strings be extracted into some constants?
// the reason I think unwrap here is fine is that if the transaction succeeded and those
// fields do not exist or code_id is not a number, there's no way we can recover, we're probably connected
// to wrong validator or something
let code_id = logs::find_attribute(&logs, "store_code", "code_id")
let code_id = logs::find_attribute(&logs, "message", "code_id")
.unwrap()
.value
.parse()
@@ -136,7 +80,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
code_id,
logs,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -168,7 +111,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
// now this is a weird one. the protobuf files say this field is optional,
// but if you omit it, the initialisation will fail CheckTx
label: Some(label),
msg: serde_json::to_vec(msg)?,
init_msg: serde_json::to_vec(msg)?,
funds: options.map(|options| options.funds).unwrap_or_default(),
}
.to_any()
@@ -180,13 +123,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.check_response()?;
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
// TODO: should those strings be extracted into some constants?
// the reason I think unwrap here is fine is that if the transaction succeeded and those
// fields do not exist or address is malformed, there's no way we can recover, we're probably connected
// to wrong validator or something
let contract_address = logs::find_attribute(&logs, "instantiate", "_contract_address")
let contract_address = logs::find_attribute(&logs, "message", "contract_address")
.unwrap()
.value
.parse()
@@ -196,7 +138,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
contract_address,
logs,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -221,12 +162,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(ChangeAdminResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -249,12 +187,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(ChangeAdminResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -274,7 +209,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
sender: sender_address.clone(),
contract: contract_address.clone(),
code_id,
msg: serde_json::to_vec(msg)?,
migrate_msg: serde_json::to_vec(msg)?,
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?;
@@ -284,12 +219,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(MigrateResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -319,12 +251,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -359,12 +288,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
debug!(
"gas wanted: {:?}, gas used: {:?}",
tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used
);
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -385,36 +316,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo)
.await?
.check_response()
}
async fn send_tokens_multiple<I>(
&self,
sender_address: &AccountId,
msgs: I,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError>
where
I: IntoIterator<Item = (AccountId, Vec<Coin>)> + Send,
{
let messages = msgs
.into_iter()
.map(|(to_address, amount)| {
MsgSend {
from_address: sender_address.clone(),
to_address,
amount,
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))
})
.collect::<Result<_, _>>()?;
self.sign_and_broadcast_commit(sender_address, messages, fee, memo)
.await?
.check_response()
.await
}
async fn delegate_tokens(
@@ -434,8 +336,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo)
.await?
.check_response()
.await
}
async fn undelegate_tokens(
@@ -455,8 +356,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo)
.await?
.check_response()
.await
}
async fn withdraw_rewards(
@@ -474,45 +374,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo)
.await?
.check_response()
}
// in this particular case we cannot generalise the argument to `&str` due to lifetime constraints
#[allow(clippy::ptr_arg)]
async fn determine_transaction_fee(
&self,
signer_address: &AccountId,
messages: &[Any],
fee: Fee,
memo: &String,
) -> Result<tx::Fee, NymdError> {
let fee = match fee {
Fee::Manual(fee) => fee,
Fee::Auto(multiplier) => {
debug!("Trying to simulate gas costs...");
// from what I've seen in manual testing, gas estimation does not exist if transaction
// fails to get executed (for example if you send 'BondMixnode" with invalid signature)
let gas_estimation = self
.simulate(signer_address, messages.to_vec(), memo.clone())
.await?
.gas_info
.ok_or(NymdError::GasEstimationFailure)?
.gas_used;
let multiplier = multiplier.unwrap_or(DEFAULT_SIMULATED_GAS_MULTIPLIER);
let gas = ((gas_estimation.value() as f32 * multiplier) as u64).into();
debug!("Gas estimation: {}", gas_estimation);
debug!("Multiplying the estimation by {}", multiplier);
debug!("Final gas limit used: {}", gas);
let fee = self.gas_price() * gas;
tx::Fee::from_amount_and_gas(fee, gas)
}
};
debug!("Fee used for the transaction: {:?}", fee);
Ok(fee)
.await
}
/// Broadcast a transaction, returning immediately.
@@ -523,10 +385,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_async::Response, NymdError> {
let memo = memo.into();
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
@@ -543,10 +401,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_sync::Response, NymdError> {
let memo = memo.into();
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
@@ -563,11 +417,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError> {
let memo = memo.into();
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
@@ -580,7 +429,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
&self,
signer_address: &AccountId,
messages: Vec<Any>,
fee: tx::Fee,
fee: Fee,
memo: impl Into<String> + Send + 'static,
signer_data: SignerData,
) -> Result<tx::Raw, NymdError> {
@@ -618,7 +467,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
&self,
signer_address: &AccountId,
messages: Vec<Any>,
fee: tx::Fee,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<tx::Raw, NymdError> {
// TODO: Future optimisation: rather than grabbing current account_number and sequence
@@ -636,28 +485,22 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
}
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct Client {
rpc_client: HttpClient,
signer: DirectSecp256k1HdWallet,
gas_price: GasPrice,
}
impl Client {
pub fn connect_with_signer<U>(
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: Option<GasPrice>,
) -> Result<Self, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let rpc_client = HttpClient::new(endpoint)?;
Ok(Client {
rpc_client,
signer,
gas_price: gas_price.unwrap_or_default(),
})
Ok(Client { rpc_client, signer })
}
}
@@ -679,8 +522,4 @@ impl SigningCosmWasmClient for Client {
fn signer(&self) -> &DirectSecp256k1HdWallet {
&self.signer
}
fn gas_price(&self) -> &GasPrice {
&self.gas_price
}
}
@@ -7,19 +7,15 @@ use crate::nymd::cosmwasm_client::logs::Log;
use crate::nymd::error::NymdError;
use cosmrs::crypto::PublicKey;
use cosmrs::proto::cosmos::auth::v1beta1::BaseAccount;
use cosmrs::proto::cosmos::base::abci::v1beta1::{
GasInfo as ProtoGasInfo, Result as ProtoAbciResult,
};
use cosmrs::proto::cosmos::tx::v1beta1::SimulateResponse as ProtoSimulateResponse;
use cosmrs::proto::cosmwasm::wasm::v1::{
use cosmrs::proto::cosmwasm::wasm::v1beta1::{
CodeInfoResponse, ContractCodeHistoryEntry as ProtoContractCodeHistoryEntry,
ContractCodeHistoryOperationType, ContractInfo as ProtoContractInfo,
};
use cosmrs::tendermint::{abci, chain};
use cosmrs::tx::{AccountNumber, Gas, SequenceNumber};
use cosmrs::tendermint::chain;
use cosmrs::tx::{AccountNumber, SequenceNumber};
use cosmrs::{tx, AccountId, Coin};
use serde::Serialize;
use std::convert::{TryFrom, TryInto};
use std::convert::TryFrom;
pub type ContractCodeId = u64;
@@ -74,6 +70,18 @@ pub struct Code {
/// sha256 hash of the code stored
pub data_hash: Vec<u8>,
/// An URL to a .tar.gz archive of the source code of the contract,
/// which can be used to reproducibly build the Wasm bytecode.
///
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub source: Option<String>,
/// A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
///
/// @example ```cosmwasm/rust-optimizer:0.8.0```
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub builder: Option<String>,
}
impl TryFrom<CodeInfoResponse> for Code {
@@ -84,16 +92,31 @@ impl TryFrom<CodeInfoResponse> for Code {
code_id,
creator,
data_hash,
source,
builder,
} = value;
let creator = creator
.parse()
.map_err(|_| NymdError::MalformedAccountAddress(creator))?;
let source = if source.is_empty() {
None
} else {
Some(source)
};
let builder = if builder.is_empty() {
None
} else {
Some(builder)
};
Ok(Code {
code_id,
creator,
data_hash,
source,
builder,
})
}
}
@@ -219,101 +242,6 @@ impl TryFrom<ProtoContractCodeHistoryEntry> for ContractCodeHistoryEntry {
}
}
#[derive(Debug)]
pub struct GasInfo {
/// GasWanted is the maximum units of work we allow this tx to perform.
pub gas_wanted: Gas,
/// GasUsed is the amount of gas actually consumed.
pub gas_used: Gas,
}
impl From<ProtoGasInfo> for GasInfo {
fn from(value: ProtoGasInfo) -> Self {
GasInfo {
gas_wanted: value.gas_wanted.into(),
gas_used: value.gas_used.into(),
}
}
}
impl GasInfo {
pub fn new(gas_wanted: Gas, gas_used: Gas) -> Self {
GasInfo {
gas_wanted,
gas_used,
}
}
}
#[derive(Debug)]
pub struct AbciResult {
/// Data is any data returned from message or handler execution. It MUST be
/// length prefixed in order to separate data from multiple message executions.
pub data: Vec<u8>,
/// Log contains the log information from message or handler execution.
// todo: try to parse into Log?
pub log: String,
/// Events contains a slice of Event objects that were emitted during message
/// or handler execution.
pub events: Vec<abci::Event>,
}
impl TryFrom<ProtoAbciResult> for AbciResult {
type Error = NymdError;
fn try_from(value: ProtoAbciResult) -> Result<Self, Self::Error> {
let mut events = Vec::with_capacity(value.events.len());
for proto_event in value.events.into_iter() {
let type_str = proto_event.r#type;
let mut attributes = Vec::with_capacity(proto_event.attributes.len());
for proto_attribute in proto_event.attributes.into_iter() {
let stringified_ked = String::from_utf8(proto_attribute.key)
.map_err(|_| NymdError::DeserializationError("EventAttributeKey".to_owned()))?;
let stringified_value = String::from_utf8(proto_attribute.value)
.map_err(|_| NymdError::DeserializationError("EventAttributeKey".to_owned()))?;
attributes.push(abci::tag::Tag {
key: stringified_ked.parse().unwrap(),
value: stringified_value.parse().unwrap(),
})
}
events.push(abci::Event {
type_str,
attributes,
})
}
Ok(AbciResult {
data: value.data,
log: value.log,
events,
})
}
}
#[derive(Debug)]
pub struct SimulateResponse {
pub gas_info: Option<GasInfo>,
pub result: Option<AbciResult>,
}
impl TryFrom<ProtoSimulateResponse> for SimulateResponse {
type Error = NymdError;
fn try_from(value: ProtoSimulateResponse) -> Result<Self, Self::Error> {
Ok(SimulateResponse {
gas_info: value.gas_info.map(|gas_info| gas_info.into()),
result: value.result.map(|result| result.try_into()).transpose()?,
})
}
}
// ##############################################################################
// types specific to the signing client (perhaps they should go to separate file)
// ##############################################################################
@@ -326,6 +254,21 @@ pub struct SignerData {
pub chain_id: chain::Id,
}
#[derive(Debug)]
pub struct UploadMeta {
/// An URL to a .tar.gz archive of the source code of the contract,
/// which can be used to reproducibly build the Wasm bytecode.
///
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub source: Option<String>,
/// A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
///
/// @example ```cosmwasm/rust-optimizer:0.8.0```
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub builder: Option<String>,
}
#[derive(Debug)]
pub struct UploadResult {
/// Size of the original wasm code in bytes
@@ -347,8 +290,6 @@ pub struct UploadResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -375,8 +316,6 @@ pub struct InstantiateResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -385,8 +324,6 @@ pub struct ChangeAdminResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -395,8 +332,6 @@ pub struct MigrateResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -405,6 +340,4 @@ pub struct ExecuteResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::cosmwasm_client::types::ContractCodeId;
use cosmrs::tendermint::{abci, block};
use cosmrs::tendermint::block;
use cosmrs::{bip32, tx, AccountId};
use std::io;
use thiserror::Error;
@@ -102,12 +102,6 @@ pub enum NymdError {
#[error("The provided gas price is malformed")]
MalformedGasPrice,
#[error("Failed to estimate gas price for the transaction")]
GasEstimationFailure,
#[error("Abci query failed with code {0} - {1}")]
AbciError(u32, abci::Log),
}
impl NymdError {
@@ -1,33 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmrs::tx;
pub mod gas_price;
pub mod helpers;
pub const DEFAULT_SIMULATED_GAS_MULTIPLIER: f32 = 1.3;
#[derive(Debug, Clone)]
pub enum Fee {
Manual(tx::Fee),
Auto(Option<f32>),
}
impl From<tx::Fee> for Fee {
fn from(fee: tx::Fee) -> Self {
Fee::Manual(fee)
}
}
impl From<f32> for Fee {
fn from(multiplier: f32) -> Self {
Fee::Auto(Some(multiplier))
}
}
impl Default for Fee {
fn default() -> Self {
Fee::Auto(Some(DEFAULT_SIMULATED_GAS_MULTIPLIER))
}
}
@@ -17,29 +17,17 @@ pub enum Operation {
Send,
BondMixnode,
BondMixnodeOnBehalf,
UnbondMixnode,
UnbondMixnodeOnBehalf,
DelegateToMixnode,
DelegateToMixnodeOnBehalf,
UndelegateFromMixnode,
UndelegateFromMixnodeOnBehalf,
BondGateway,
BondGatewayOnBehalf,
UnbondGateway,
UnbondGatewayOnBehalf,
UpdateContractSettings,
UpdateStateParams,
BeginMixnodeRewarding,
FinishMixnodeRewarding,
TrackUnbondGateway,
TrackUnbondMixnode,
WithdrawVestedCoins,
TrackUndelegation,
CreatePeriodicVestingAccount,
}
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
@@ -55,27 +43,14 @@ impl fmt::Display for Operation {
Operation::ChangeAdmin => f.write_str("ChangeAdmin"),
Operation::Send => f.write_str("Send"),
Operation::BondMixnode => f.write_str("BondMixnode"),
Operation::BondMixnodeOnBehalf => f.write_str("BondMixnodeOnBehalf"),
Operation::UnbondMixnode => f.write_str("UnbondMixnode"),
Operation::UnbondMixnodeOnBehalf => f.write_str("UnbondMixnodeOnBehalf"),
Operation::BondGateway => f.write_str("BondGateway"),
Operation::BondGatewayOnBehalf => f.write_str("BondGatewayOnBehalf"),
Operation::UnbondGateway => f.write_str("UnbondGateway"),
Operation::UnbondGatewayOnBehalf => f.write_str("UnbondGatewayOnBehalf"),
Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"),
Operation::DelegateToMixnodeOnBehalf => f.write_str("DelegateToMixnodeOnBehalf"),
Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
Operation::UndelegateFromMixnodeOnBehalf => {
f.write_str("UndelegateFromMixnodeOnBehalf")
}
Operation::UpdateContractSettings => f.write_str("UpdateContractSettings"),
Operation::UpdateStateParams => f.write_str("UpdateStateParams"),
Operation::BeginMixnodeRewarding => f.write_str("BeginMixnodeRewarding"),
Operation::FinishMixnodeRewarding => f.write_str("FinishMixnodeRewarding"),
Operation::TrackUnbondGateway => f.write_str("TrackUnbondGateway"),
Operation::TrackUnbondMixnode => f.write_str("TrackUnbondMixnode"),
Operation::WithdrawVestedCoins => f.write_str("WithdrawVestedCoins"),
Operation::TrackUndelegation => f.write_str("TrackUndelegation"),
Operation::CreatePeriodicVestingAccount => f.write_str("CreatePeriodicVestingAccount"),
}
}
}
@@ -84,34 +59,23 @@ impl Operation {
// TODO: some value tweaking
pub fn default_gas_limit(&self) -> Gas {
match self {
Operation::Upload => 3_000_000u64.into(),
Operation::Upload => 2_500_000u64.into(),
Operation::Init => 500_000u64.into(),
Operation::Migrate => 200_000u64.into(),
Operation::ChangeAdmin => 80_000u64.into(),
Operation::Send => 80_000u64.into(),
Operation::BondMixnode => 175_000u64.into(),
Operation::BondMixnodeOnBehalf => 200_000u64.into(),
Operation::UnbondMixnode => 175_000u64.into(),
Operation::UnbondMixnodeOnBehalf => 175_000u64.into(),
Operation::DelegateToMixnode => 175_000u64.into(),
Operation::DelegateToMixnodeOnBehalf => 175_000u64.into(),
Operation::UndelegateFromMixnode => 175_000u64.into(),
Operation::UndelegateFromMixnodeOnBehalf => 175_000u64.into(),
Operation::BondGateway => 175_000u64.into(),
Operation::BondGatewayOnBehalf => 200_000u64.into(),
Operation::UnbondGateway => 175_000u64.into(),
Operation::UnbondGatewayOnBehalf => 200_000u64.into(),
Operation::UpdateContractSettings => 175_000u64.into(),
Operation::UpdateStateParams => 175_000u64.into(),
Operation::BeginMixnodeRewarding => 175_000u64.into(),
Operation::FinishMixnodeRewarding => 175_000u64.into(),
Operation::TrackUnbondGateway => 175_000u64.into(),
Operation::TrackUnbondMixnode => 175_000u64.into(),
Operation::WithdrawVestedCoins => 175_000u64.into(),
Operation::TrackUndelegation => 175_000u64.into(),
Operation::CreatePeriodicVestingAccount => 175_000u64.into(),
}
}
@@ -125,8 +89,9 @@ impl Operation {
Fee::from_amount_and_gas(fee, gas_limit)
}
pub fn default_fee(&self, gas_price: &GasPrice) -> Fee {
Self::determine_custom_fee(gas_price, self.default_gas_limit())
pub(crate) fn determine_fee(&self, gas_price: &GasPrice, gas_limit: Option<Gas>) -> Fee {
let gas_limit = gas_limit.unwrap_or_else(|| self.default_gas_limit());
Self::determine_custom_fee(gas_price, gas_limit)
}
}
@@ -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 * gas_price_numerator > amount * gas_price_denominator {
if limit_uint128.u128() * gas_price_numerator > amount.u128() * gas_price_denominator {
amount += Uint128::new(1);
}
File diff suppressed because it is too large Load Diff
@@ -1,8 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod vesting_query_client;
mod vesting_signing_client;
pub use vesting_query_client::VestingQueryClient;
pub use vesting_signing_client::VestingSigningClient;
@@ -1,176 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::error::NymdError;
use crate::nymd::NymdClient;
use async_trait::async_trait;
use cosmwasm_std::{Coin, Timestamp};
use vesting_contract::messages::QueryMsg as VestingQueryMsg;
#[async_trait]
pub trait VestingQueryClient {
async fn locked_coins(
&self,
address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn spendable_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn vested_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn vesting_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn vesting_start_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NymdError>;
async fn vesting_end_time(&self, vesting_account_address: &str)
-> Result<Timestamp, NymdError>;
async fn original_vesting(&self, vesting_account_address: &str) -> Result<Coin, NymdError>;
async fn delegated_free(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn delegated_vesting(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
}
#[async_trait]
impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn locked_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::LockedCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn spendable_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::SpendableCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vested_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetVestedCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vesting_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetVestingCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vesting_start_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NymdError> {
let request = VestingQueryMsg::GetStartTime {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vesting_end_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NymdError> {
let request = VestingQueryMsg::GetEndTime {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn original_vesting(&self, vesting_account_address: &str) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetOriginalVesting {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn delegated_free(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetDelegatedFree {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn delegated_vesting(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetDelegatedVesting {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
}
@@ -1,294 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::fee::helpers::Operation;
use crate::nymd::{cosmwasm_coin_to_cosmos_coin, NymdClient};
use async_trait::async_trait;
use cosmwasm_std::Coin;
use mixnet_contract::{Gateway, IdentityKey, IdentityKeyRef, MixNode};
use vesting_contract::messages::ExecuteMsg as VestingExecuteMsg;
#[async_trait]
pub trait VestingSigningClient {
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_gateway(&self) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_gateway(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_mixnode(&self) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_mixnode(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn withdraw_vested_coins(&self, amount: Coin) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
amount: &Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_undelegate_from_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
) -> Result<ExecuteResult, NymdError>;
async fn create_periodic_vesting_account(
&self,
address: &str,
start_time: Option<u64>,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient<C> {
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::BondGateway);
let req = VestingExecuteMsg::BondGateway {
gateway,
owner_signature: owner_signature.to_string(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::BondGateway",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
)
.await
}
async fn vesting_unbond_gateway(&self) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UnbondGateway);
let req = VestingExecuteMsg::UnbondGateway {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UnbondGateway",
vec![],
)
.await
}
async fn vesting_track_unbond_gateway(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUnbondGateway);
let req = VestingExecuteMsg::TrackUnbondGateway {
owner: owner.to_string(),
amount,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::TrackUnbondGateway",
vec![],
)
.await
}
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::BondMixnode);
let req = VestingExecuteMsg::BondMixnode {
mix_node,
owner_signature: owner_signature.to_string(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::BondMixnode",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
)
.await
}
async fn vesting_unbond_mixnode(&self) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UnbondMixnode);
let req = VestingExecuteMsg::UnbondMixnode {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UnbondMixnode",
vec![],
)
.await
}
async fn vesting_track_unbond_mixnode(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUnbondMixnode);
let req = VestingExecuteMsg::TrackUnbondMixnode {
owner: owner.to_string(),
amount,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::TrackUnbondMixnode",
vec![],
)
.await
}
async fn withdraw_vested_coins(&self, amount: Coin) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::WithdrawVestedCoins);
let req = VestingExecuteMsg::WithdrawVestedCoins { amount };
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::WithdrawVested",
vec![],
)
.await
}
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUndelegation);
let req = VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_identity,
amount,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::TrackUndelegation",
vec![],
)
.await
}
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
amount: &Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::DelegateToMixnode);
let req = VestingExecuteMsg::DelegateToMixnode {
mix_identity: mix_identity.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::DeledateToMixnode",
vec![cosmwasm_coin_to_cosmos_coin(amount.to_owned())],
)
.await
}
async fn vesting_undelegate_from_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UndelegateFromMixnode);
let req = VestingExecuteMsg::UndelegateFromMixnode {
mix_identity: mix_identity.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UndelegateFromMixnode",
vec![],
)
.await
}
async fn create_periodic_vesting_account(
&self,
address: &str,
start_time: Option<u64>,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::CreatePeriodicVestingAccount);
let req = VestingExecuteMsg::CreateAccount {
address: address.to_string(),
start_time,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::CreatePeriodicVestingAccount",
vec![cosmwasm_coin_to_cosmos_coin(amount)],
)
.await
}
}
@@ -10,7 +10,7 @@ use cosmrs::tx::SignDoc;
use cosmrs::{tx, AccountId};
/// Derivation information required to derive a keypair and an address from a mnemonic.
#[derive(Debug, Clone)]
#[derive(Debug)]
struct Secp256k1Derivation {
hd_path: DerivationPath,
prefix: String,
@@ -40,7 +40,7 @@ impl AccountData {
type Secp256k1Keypair = (SigningKey, PublicKey);
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct DirectSecp256k1HdWallet {
/// Base secret
secret: bip39::Mnemonic,
+18 -27
View File
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use ed25519_dalek::ed25519::signature::Signature as SignatureTrait;
use ed25519_dalek::ed25519::signature::Signature as SignatureTrait;
pub use ed25519_dalek::SignatureError;
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
use nymsphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
@@ -10,33 +10,33 @@ use rand::{CryptoRng, RngCore};
use std::fmt::{self, Display, Formatter};
#[derive(Debug)]
pub enum Ed25519RecoveryError {
pub enum KeyRecoveryError {
MalformedBytes(SignatureError),
MalformedString(bs58::decode::Error),
}
impl From<SignatureError> for Ed25519RecoveryError {
impl From<SignatureError> for KeyRecoveryError {
fn from(err: SignatureError) -> Self {
Ed25519RecoveryError::MalformedBytes(err)
KeyRecoveryError::MalformedBytes(err)
}
}
impl From<bs58::decode::Error> for Ed25519RecoveryError {
impl From<bs58::decode::Error> for KeyRecoveryError {
fn from(err: bs58::decode::Error) -> Self {
Ed25519RecoveryError::MalformedString(err)
KeyRecoveryError::MalformedString(err)
}
}
impl fmt::Display for Ed25519RecoveryError {
impl fmt::Display for KeyRecoveryError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Ed25519RecoveryError::MalformedBytes(err) => write!(f, "malformed bytes - {}", err),
Ed25519RecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err),
KeyRecoveryError::MalformedBytes(err) => write!(f, "malformed bytes - {}", err),
KeyRecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err),
}
}
}
impl std::error::Error for Ed25519RecoveryError {}
impl std::error::Error for KeyRecoveryError {}
/// Keypair for usage in ed25519 EdDSA.
pub struct KeyPair {
@@ -62,7 +62,7 @@ impl KeyPair {
&self.public_key
}
pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result<Self, Ed25519RecoveryError> {
pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result<Self, KeyRecoveryError> {
Ok(KeyPair {
private_key: PrivateKey::from_bytes(priv_bytes)?,
public_key: PublicKey::from_bytes(pub_bytes)?,
@@ -115,7 +115,7 @@ impl PublicKey {
self.0.to_bytes()
}
pub fn from_bytes(b: &[u8]) -> Result<Self, Ed25519RecoveryError> {
pub fn from_bytes(b: &[u8]) -> Result<Self, KeyRecoveryError> {
Ok(PublicKey(ed25519_dalek::PublicKey::from_bytes(b)?))
}
@@ -123,7 +123,7 @@ impl PublicKey {
bs58::encode(self.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, KeyRecoveryError> {
let bytes = bs58::decode(val).into_vec()?;
Self::from_bytes(&bytes)
}
@@ -134,7 +134,7 @@ impl PublicKey {
}
impl PemStorableKey for PublicKey {
type Error = Ed25519RecoveryError;
type Error = KeyRecoveryError;
fn pem_type() -> &'static str {
"ED25519 PUBLIC KEY"
@@ -170,7 +170,7 @@ impl PrivateKey {
self.0.to_bytes()
}
pub fn from_bytes(b: &[u8]) -> Result<Self, Ed25519RecoveryError> {
pub fn from_bytes(b: &[u8]) -> Result<Self, KeyRecoveryError> {
Ok(PrivateKey(ed25519_dalek::SecretKey::from_bytes(b)?))
}
@@ -178,7 +178,7 @@ impl PrivateKey {
bs58::encode(&self.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, KeyRecoveryError> {
let bytes = bs58::decode(val).into_vec()?;
Self::from_bytes(&bytes)
}
@@ -192,7 +192,7 @@ impl PrivateKey {
}
impl PemStorableKey for PrivateKey {
type Error = Ed25519RecoveryError;
type Error = KeyRecoveryError;
fn pem_type() -> &'static str {
"ED25519 PRIVATE KEY"
@@ -211,20 +211,11 @@ impl PemStorableKey for PrivateKey {
pub struct Signature(ed25519_dalek::Signature);
impl Signature {
pub fn to_base58_string(&self) -> String {
bs58::encode(&self.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
let bytes = bs58::decode(val).into_vec()?;
Self::from_bytes(&bytes)
}
pub fn to_bytes(&self) -> [u8; SIGNATURE_LENGTH] {
self.0.to_bytes()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Ed25519RecoveryError> {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignatureError> {
Ok(Signature(ed25519_dalek::Signature::from_bytes(bytes)?))
}
}
+3 -1
View File
@@ -7,7 +7,9 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = "1.0.0-beta2"
# 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" }
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
+72 -60
View File
@@ -1,6 +1,3 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
@@ -10,41 +7,51 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
pub struct Delegation {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct UnpackedDelegation<T> {
pub owner: Addr,
pub node_identity: IdentityKey,
pub amount: Coin,
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 block_height: u64,
pub proxy: Option<Addr>, // proxy address used to delegate the funds on behalf of anouther address
}
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,
}
impl Delegation {
pub fn new(
owner: Addr,
node_identity: IdentityKey,
amount: Coin,
block_height: u64,
proxy: Option<Addr>,
) -> Self {
pub fn new(owner: Addr, amount: Coin, block_height: u64) -> 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;
}
}
@@ -52,10 +59,6 @@ impl Delegation {
&self.amount
}
pub fn node_identity(&self) -> IdentityKey {
self.node_identity.clone()
}
pub fn owner(&self) -> Addr {
self.owner.clone()
}
@@ -69,36 +72,27 @@ impl Display for Delegation {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{} delegated towards {} by {} at block {}",
self.amount, self.node_identity, self.owner, self.block_height
"{} {} delegated by {} at block {}",
self.amount.amount, self.amount.denom, 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<String>,
pub start_next_after: Option<Addr>,
}
impl PagedMixDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<Addr>) -> Self {
pub fn new(
node_identity: IdentityKey,
delegations: Vec<Delegation>,
start_next_after: Option<Addr>,
) -> Self {
PagedMixDelegationsResponse {
delegations,
start_next_after: start_next_after.map(|s| s.to_string()),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedDelegatorDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<IdentityKey>,
}
impl PagedDelegatorDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<IdentityKey>) -> Self {
PagedDelegatorDelegationsResponse {
node_identity,
delegations,
start_next_after,
}
@@ -106,19 +100,37 @@ impl PagedDelegatorDelegationsResponse {
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<(IdentityKey, String)>,
pub struct PagedReverseMixDelegationsResponse {
pub delegation_owner: Addr,
pub delegated_nodes: Vec<IdentityKey>,
pub start_next_after: Option<IdentityKey>,
}
impl PagedAllDelegationsResponse {
impl PagedReverseMixDelegationsResponse {
pub fn new(
delegations: Vec<Delegation>,
start_next_after: Option<(IdentityKey, Addr)>,
delegation_owner: Addr,
delegated_nodes: Vec<IdentityKey>,
start_next_after: Option<IdentityKey>,
) -> Self {
PagedAllDelegationsResponse {
delegations,
start_next_after: start_next_after.map(|(id, addr)| (id, addr.to_string())),
PagedReverseMixDelegationsResponse {
delegation_owner,
delegated_nodes,
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>>,
}
impl<T> PagedAllDelegationsResponse<T> {
pub fn new(delegations: Vec<UnpackedDelegation<T>>, start_next_after: Option<Vec<u8>>) -> Self {
PagedAllDelegationsResponse {
delegations,
start_next_after,
}
}
}
+19 -35
View File
@@ -23,27 +23,19 @@ pub struct Gateway {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct GatewayBond {
pub pledge_amount: Coin,
pub bond_amount: Coin,
pub owner: Addr,
pub block_height: u64,
pub gateway: Gateway,
pub proxy: Option<Addr>,
}
impl GatewayBond {
pub fn new(
pledge_amount: Coin,
owner: Addr,
block_height: u64,
gateway: Gateway,
proxy: Option<Addr>,
) -> Self {
pub fn new(bond_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
GatewayBond {
pledge_amount,
bond_amount,
owner,
block_height,
gateway,
proxy,
}
}
@@ -51,8 +43,8 @@ impl GatewayBond {
&self.gateway.identity_key
}
pub fn pledge_amount(&self) -> Coin {
self.pledge_amount.clone()
pub fn bond_amount(&self) -> Coin {
self.bond_amount.clone()
}
pub fn owner(&self) -> &Addr {
@@ -67,17 +59,17 @@ impl GatewayBond {
impl PartialOrd for GatewayBond {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// first remove invalid cases
if self.pledge_amount.denom != other.pledge_amount.denom {
if self.bond_amount.denom != other.bond_amount.denom {
return None;
}
// try to order by total pledge
let pledge_cmp = self
.pledge_amount
// try to order by total bond
let bond_cmp = self
.bond_amount
.amount
.partial_cmp(&other.pledge_amount.amount)?;
if pledge_cmp != Ordering::Equal {
return Some(pledge_cmp);
.partial_cmp(&other.bond_amount.amount)?;
if bond_cmp != Ordering::Equal {
return Some(bond_cmp);
}
// then check block height
@@ -102,10 +94,7 @@ impl Display for GatewayBond {
write!(
f,
"amount: {} {}, owner: {}, identity: {}",
self.pledge_amount.amount,
self.pledge_amount.denom,
self.owner,
self.gateway.identity_key
self.bond_amount.amount, self.bond_amount.denom, self.owner, self.gateway.identity_key
)
}
}
@@ -134,7 +123,7 @@ impl PagedGatewayResponse {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct GatewayOwnershipResponse {
pub address: Addr,
pub gateway: Option<GatewayBond>,
pub has_gateway: bool,
}
#[cfg(test)]
@@ -161,43 +150,38 @@ mod tests {
let _0foos = Coin::new(0, "foo");
let gate1 = GatewayBond {
pledge_amount: _150foos.clone(),
bond_amount: _150foos.clone(),
owner: Addr::unchecked("foo1"),
block_height: 100,
gateway: gateway_fixture(),
proxy: None,
};
let gate2 = GatewayBond {
pledge_amount: _150foos,
bond_amount: _150foos,
owner: Addr::unchecked("foo2"),
block_height: 120,
gateway: gateway_fixture(),
proxy: None,
};
let gate3 = GatewayBond {
pledge_amount: _50foos,
bond_amount: _50foos,
owner: Addr::unchecked("foo3"),
block_height: 120,
gateway: gateway_fixture(),
proxy: None,
};
let gate4 = GatewayBond {
pledge_amount: _140foos,
bond_amount: _140foos,
owner: Addr::unchecked("foo4"),
block_height: 120,
gateway: gateway_fixture(),
proxy: None,
};
let gate5 = GatewayBond {
pledge_amount: _0foos,
bond_amount: _0foos,
owner: Addr::unchecked("foo5"),
block_height: 120,
gateway: gateway_fixture(),
proxy: None,
};
// summary:
+3 -3
View File
@@ -12,10 +12,10 @@ pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
pub use cosmwasm_std::{Addr, Coin};
pub use delegation::{
Delegation, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
PagedMixDelegationsResponse,
Delegation, PagedAllDelegationsResponse, PagedMixDelegationsResponse,
PagedReverseMixDelegationsResponse, RawDelegationData, UnpackedDelegation,
};
pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse};
pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
pub use msg::*;
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
pub use types::*;
+60 -99
View File
@@ -5,7 +5,7 @@ use crate::{IdentityKey, SphinxKey};
use az::CheckedCast;
use cosmwasm_std::{coin, Addr, Coin, Uint128};
use log::error;
use network_defaults::DEFAULT_OPERATOR_EPOCH_COST;
use network_defaults::{DEFAULT_OPERATOR_EPOCH_COST, DEFAULT_PROFIT_MARGIN};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
@@ -29,7 +29,6 @@ pub struct MixNode {
/// Base58 encoded ed25519 EdDSA public key.
pub identity_key: IdentityKey,
pub version: String,
pub profit_margin_percent: u8,
}
#[derive(
@@ -56,68 +55,32 @@ pub enum Layer {
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct NodeRewardParams {
period_reward_pool: Uint128,
rewarded_set_size: Uint128,
active_set_size: Uint128,
k: Uint128,
reward_blockstamp: u64,
circulating_supply: Uint128,
uptime: Uint128,
sybil_resistance_percent: u8,
in_active_set: bool,
active_set_work_factor: u8,
}
impl NodeRewardParams {
#[allow(clippy::too_many_arguments)]
pub fn new(
period_reward_pool: u128,
rewarded_set_size: u128,
active_set_size: u128,
k: u128,
reward_blockstamp: u64,
circulating_supply: u128,
uptime: u128,
sybil_resistance_percent: u8,
in_active_set: bool,
active_set_work_factor: u8,
) -> NodeRewardParams {
NodeRewardParams {
period_reward_pool: Uint128::new(period_reward_pool),
rewarded_set_size: Uint128::new(rewarded_set_size),
active_set_size: Uint128::new(active_set_size),
period_reward_pool: Uint128(period_reward_pool),
k: Uint128(k),
reward_blockstamp,
circulating_supply: Uint128::new(circulating_supply),
uptime: Uint128::new(uptime),
circulating_supply: Uint128(circulating_supply),
uptime: Uint128(uptime),
sybil_resistance_percent,
in_active_set,
active_set_work_factor,
}
}
pub fn omega(&self) -> U128 {
// As per keybase://chat/nymtech#tokeneconomics/1179
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
if self.in_active_set() {
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
self.active_set_work_factor() / denom * self.rewarded_set_size()
} else {
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
ONE / denom * self.rewarded_set_size()
}
}
pub fn idle_nodes(&self) -> Uint128 {
self.rewarded_set_size - self.active_set_size
}
pub fn active_set_work_factor(&self) -> U128 {
U128::from_num(self.active_set_work_factor)
}
pub fn in_active_set(&self) -> bool {
self.in_active_set
}
pub fn performance(&self) -> U128 {
U128::from_num(self.uptime.u128()) / U128::from_num(100)
}
@@ -134,8 +97,8 @@ impl NodeRewardParams {
self.period_reward_pool.u128()
}
pub fn rewarded_set_size(&self) -> u128 {
self.rewarded_set_size.u128()
pub fn k(&self) -> u128 {
self.k.u128()
}
pub fn circulating_supply(&self) -> u128 {
@@ -151,7 +114,7 @@ impl NodeRewardParams {
}
pub fn one_over_k(&self) -> U128 {
ONE / U128::from_num(self.rewarded_set_size.u128())
ONE / U128::from_num(self.k.u128())
}
pub fn alpha(&self) -> U128 {
@@ -266,45 +229,46 @@ impl NodeRewardResult {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeBond {
pub pledge_amount: Coin,
pub bond_amount: Coin,
pub total_delegation: Coin,
pub owner: Addr,
pub layer: Layer,
pub block_height: u64,
pub mix_node: MixNode,
pub proxy: Option<Addr>,
pub profit_margin_percent: Option<u8>,
}
impl MixNodeBond {
pub fn new(
pledge_amount: Coin,
bond_amount: Coin,
owner: Addr,
layer: Layer,
block_height: u64,
mix_node: MixNode,
proxy: Option<Addr>,
profit_margin_percent: Option<u8>,
) -> Self {
MixNodeBond {
total_delegation: coin(0, &pledge_amount.denom),
pledge_amount,
total_delegation: coin(0, &bond_amount.denom),
bond_amount,
owner,
layer,
block_height,
mix_node,
proxy,
profit_margin_percent,
}
}
pub fn profit_margin(&self) -> U128 {
U128::from_num(self.mix_node.profit_margin_percent) / U128::from_num(100)
U128::from_num(self.profit_margin_percent.unwrap_or(DEFAULT_PROFIT_MARGIN))
/ U128::from_num(100)
}
pub fn identity(&self) -> &String {
&self.mix_node.identity_key
}
pub fn pledge_amount(&self) -> Coin {
self.pledge_amount.clone()
pub fn bond_amount(&self) -> Coin {
self.bond_amount.clone()
}
pub fn owner(&self) -> &Addr {
@@ -316,10 +280,10 @@ impl MixNodeBond {
}
pub fn total_stake(&self) -> Option<u128> {
if self.pledge_amount.denom != self.total_delegation.denom {
if self.bond_amount.denom != self.total_delegation.denom {
None
} else {
Some(self.pledge_amount.amount.u128() + self.total_delegation.amount.u128())
Some(self.bond_amount.amount.u128() + self.total_delegation.amount.u128())
}
}
@@ -327,37 +291,38 @@ impl MixNodeBond {
self.total_delegation.clone()
}
pub fn pledge_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.pledge_amount().amount.u128()) / U128::from_num(circulating_supply)
pub fn bond_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.bond_amount().amount.u128()) / U128::from_num(circulating_supply)
}
pub fn total_bond_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.pledge_amount().amount.u128() + self.total_delegation().amount.u128())
pub fn total_stake_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.bond_amount().amount.u128() + self.total_delegation().amount.u128())
/ U128::from_num(circulating_supply)
}
pub fn lambda(&self, params: &NodeRewardParams) -> U128 {
// Ratio of a bond to the token circulating supply
let pledge_to_circulating_supply_ratio =
self.pledge_to_circulating_supply(params.circulating_supply());
pledge_to_circulating_supply_ratio.min(params.one_over_k())
let bond_to_circulating_supply_ratio =
self.bond_to_circulating_supply(params.circulating_supply());
bond_to_circulating_supply_ratio.min(params.one_over_k())
}
pub fn sigma(&self, params: &NodeRewardParams) -> U128 {
// Ratio of a delegation to the the token circulating supply
let total_bond_to_circulating_supply_ratio =
self.total_bond_to_circulating_supply(params.circulating_supply());
total_bond_to_circulating_supply_ratio.min(params.one_over_k())
let total_stake_to_circulating_supply_ratio =
self.total_stake_to_circulating_supply(params.circulating_supply());
total_stake_to_circulating_supply_ratio.min(params.one_over_k())
}
pub fn reward(&self, params: &NodeRewardParams) -> NodeRewardResult {
// Assuming uniform work distribution across the network this is one_over_k * k
let omega_k = ONE;
let lambda = self.lambda(params);
let sigma = self.sigma(params);
let reward = params.performance()
* params.period_reward_pool()
* (sigma * params.omega()
+ params.alpha() * lambda * sigma * params.rewarded_set_size())
* (sigma * omega_k + params.alpha() * lambda * sigma * params.k())
/ (ONE + params.alpha());
NodeRewardResult {
@@ -402,9 +367,9 @@ impl MixNodeBond {
}
pub fn sigma_ratio(&self, params: &NodeRewardParams) -> U128 {
if self.total_bond_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
if self.total_stake_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
{
self.total_bond_to_circulating_supply(params.circulating_supply())
self.total_stake_to_circulating_supply(params.circulating_supply())
} else {
params.one_over_k()
}
@@ -419,33 +384,33 @@ impl MixNodeBond {
impl PartialOrd for MixNodeBond {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// first remove invalid cases
if self.pledge_amount.denom != self.total_delegation.denom {
if self.bond_amount.denom != self.total_delegation.denom {
return None;
}
if other.pledge_amount.denom != other.total_delegation.denom {
if other.bond_amount.denom != other.total_delegation.denom {
return None;
}
if self.pledge_amount.denom != other.pledge_amount.denom {
if self.bond_amount.denom != other.bond_amount.denom {
return None;
}
// try to order by total bond + delegation
let total_cmp = (self.pledge_amount.amount + self.total_delegation.amount)
.partial_cmp(&(self.pledge_amount.amount + self.total_delegation.amount))?;
let total_cmp = (self.bond_amount.amount + self.total_delegation.amount)
.partial_cmp(&(self.bond_amount.amount + self.total_delegation.amount))?;
if total_cmp != Ordering::Equal {
return Some(total_cmp);
}
// then if those are equal, prefer higher bond over delegation
let pledge_cmp = self
.pledge_amount
let bond_cmp = self
.bond_amount
.amount
.partial_cmp(&other.pledge_amount.amount)?;
if pledge_cmp != Ordering::Equal {
return Some(pledge_cmp);
.partial_cmp(&other.bond_amount.amount)?;
if bond_cmp != Ordering::Equal {
return Some(bond_cmp);
}
// then look at delegation (I'm not sure we can get here, but better safe than sorry)
@@ -484,10 +449,7 @@ impl Display for MixNodeBond {
write!(
f,
"amount: {} {}, owner: {}, identity: {}",
self.pledge_amount.amount,
self.pledge_amount.denom,
self.owner,
self.mix_node.identity_key
self.bond_amount.amount, self.bond_amount.denom, self.owner, self.mix_node.identity_key
)
}
}
@@ -516,7 +478,7 @@ impl PagedMixnodeResponse {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixOwnershipResponse {
pub address: Addr,
pub mixnode: Option<MixNodeBond>,
pub has_node: bool,
}
#[cfg(test)]
@@ -532,7 +494,6 @@ mod tests {
sphinx_key: "sphinxkey".to_string(),
identity_key: "identitykey".to_string(),
version: "0.11.0".to_string(),
profit_margin_percent: 10,
}
}
@@ -543,53 +504,53 @@ mod tests {
let _0foos = Coin::new(0, "foo");
let mix1 = MixNodeBond {
pledge_amount: _150foos.clone(),
bond_amount: _150foos.clone(),
total_delegation: _50foos.clone(),
owner: Addr::unchecked("foo1"),
layer: Layer::One,
block_height: 100,
mix_node: mixnode_fixture(),
proxy: None,
profit_margin_percent: Some(10),
};
let mix2 = MixNodeBond {
pledge_amount: _150foos.clone(),
bond_amount: _150foos.clone(),
total_delegation: _50foos.clone(),
owner: Addr::unchecked("foo2"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
proxy: None,
profit_margin_percent: Some(10),
};
let mix3 = MixNodeBond {
pledge_amount: _50foos,
bond_amount: _50foos,
total_delegation: _150foos.clone(),
owner: Addr::unchecked("foo3"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
proxy: None,
profit_margin_percent: Some(10),
};
let mix4 = MixNodeBond {
pledge_amount: _150foos.clone(),
bond_amount: _150foos.clone(),
total_delegation: _0foos.clone(),
owner: Addr::unchecked("foo4"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
proxy: None,
profit_margin_percent: Some(10),
};
let mix5 = MixNodeBond {
pledge_amount: _0foos,
bond_amount: _0foos,
total_delegation: _150foos,
owner: Addr::unchecked("foo5"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
proxy: None,
profit_margin_percent: Some(10),
};
// summary:
+18 -54
View File
@@ -2,30 +2,27 @@
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::NodeRewardParams;
use crate::ContractStateParams;
use crate::StateParams;
use crate::{Gateway, IdentityKey, MixNode};
use cosmwasm_std::Addr;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub rewarding_validator_address: String,
}
pub struct InstantiateMsg {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
BondMixnode {
mix_node: MixNode,
owner_signature: String,
},
UnbondMixnode {},
BondGateway {
gateway: Gateway,
owner_signature: String,
},
UnbondGateway {},
UpdateContractStateParams(ContractStateParams),
UpdateStateParams(StateParams),
DelegateToMixnode {
mix_identity: IdentityKey,
@@ -45,7 +42,7 @@ pub enum ExecuteMsg {
rewarding_interval_nonce: u32,
},
RewardMixnode {
RewardMixnodeV2 {
identity: IdentityKey,
// percentage value in range 0-100
params: NodeRewardParams,
@@ -53,41 +50,17 @@ 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,
owner_signature: String,
},
UnbondMixnodeOnBehalf {
owner: String,
},
BondGatewayOnBehalf {
gateway: Gateway,
owner: String,
owner_signature: String,
},
UnbondGatewayOnBehalf {
owner: String,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetContractVersion {},
GetMixNodes {
limit: Option<u32>,
start_after: Option<IdentityKey>,
@@ -97,39 +70,30 @@ pub enum QueryMsg {
limit: Option<u32>,
},
OwnsMixnode {
address: String,
address: Addr,
},
OwnsGateway {
address: String,
address: Addr,
},
StateParams {},
CurrentRewardingInterval {},
// 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 {
GetMixDelegations {
mix_identity: IdentityKey,
// since `start_after` is user-provided input, we can't use `Addr` as we
// can't guarantee it's validated.
start_after: Option<String>,
start_after: Option<Addr>,
limit: Option<u32>,
},
// 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,
GetAllMixDelegations {
start_after: Option<Vec<u8>>,
limit: Option<u32>,
},
GetReverseMixDelegations {
delegation_owner: Addr,
start_after: Option<IdentityKey>,
limit: Option<u32>,
},
// gets delegation associated with particular mixnode, delegator pair
GetDelegationDetails {
GetMixDelegation {
mix_identity: IdentityKey,
delegator: String,
address: Addr,
},
LayerDistribution {},
GetRewardPool {},
+8 -43
View File
@@ -3,7 +3,7 @@
use crate::mixnode::DelegatorRewardParams;
use crate::Layer;
use cosmwasm_std::{Addr, Uint128};
use cosmwasm_std::Uint128;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
@@ -35,13 +35,13 @@ pub struct RewardingIntervalResponse {
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ContractStateParams {
pub struct StateParams {
// so currently epoch_length is being unused and validator API performs rewarding
// based on its own epoch length config value. I guess that's fine for time being
// however, in the future, the contract constant should be controlling it instead.
// pub epoch_length: u32, // length of a rewarding epoch/interval, expressed in hours
pub minimum_mixnode_pledge: Uint128, // minimum amount a mixnode must pledge to get into the system
pub minimum_gateway_pledge: Uint128, // minimum amount a gateway must pledge to get into the system
pub minimum_mixnode_bond: Uint128, // minimum amount a mixnode must bond to get into the system
pub minimum_gateway_bond: Uint128, // minimum amount a gateway must bond to get into the system
// number of mixnode that are going to get rewarded during current rewarding interval (k_m)
// based on overall demand for private bandwidth-
@@ -50,22 +50,13 @@ pub struct ContractStateParams {
// subset of rewarded mixnodes that are actively receiving mix traffic
// used to handle shorter-term (e.g. hourly) fluctuations of demand
pub mixnode_active_set_size: u32,
pub active_set_work_factor: u8,
}
impl Display for ContractStateParams {
impl Display for StateParams {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Contract state parameters: [ ")?;
write!(
f,
"minimum mixnode pledge: {}; ",
self.minimum_mixnode_pledge
)?;
write!(
f,
"minimum gateway pledge: {}; ",
self.minimum_gateway_pledge
)?;
write!(f, "minimum mixnode bond: {}; ", self.minimum_mixnode_bond)?;
write!(f, "minimum gateway bond: {}; ", self.minimum_gateway_bond)?;
write!(
f,
"mixnode rewarded set size: {}",
@@ -75,11 +66,6 @@ impl Display for ContractStateParams {
f,
"mixnode active set size: {}",
self.mixnode_active_set_size
)?;
write!(
f,
"mixnode active set work factor: {}",
self.active_set_work_factor
)
}
}
@@ -95,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: Addr,
pub next_start: String,
pub rewarding_params: DelegatorRewardParams,
}
@@ -111,27 +97,6 @@ pub struct MixnodeRewardingStatusResponse {
pub status: Option<RewardingStatus>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MixnetContractVersion {
// VERGEN_BUILD_TIMESTAMP
pub build_timestamp: String,
// VERGEN_BUILD_SEMVER
pub build_version: String,
// VERGEN_GIT_SHA
pub commit_sha: String,
// VERGEN_GIT_COMMIT_TIMESTAMP
pub commit_timestamp: String,
// VERGEN_GIT_BRANCH
pub commit_branch: String,
// VERGEN_RUSTC_SEMVER
pub rustc_version: String,
}
// type aliases for better reasoning about available data
pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
-1
View File
@@ -63,7 +63,6 @@ pub fn default_api_endpoints() -> Vec<Url> {
}
pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen";
pub const DEFAULT_VESTING_CONTRACT_ADDRESS: &str = "";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "punk1v9qauwdq5terag6uvfsdytcs2d0sdmfdy7hgk3";
/// How much bandwidth (in bytes) one token can buy
+2 -2
View File
@@ -22,9 +22,9 @@ const CLIENT_IDENTITY_SIZE: usize = identity::PUBLIC_KEY_LENGTH;
#[derive(Debug)]
pub enum RecipientFormattingError {
MalformedRecipientError,
MalformedIdentityError(identity::Ed25519RecoveryError),
MalformedIdentityError(identity::KeyRecoveryError),
MalformedEncryptionKeyError(encryption::KeyRecoveryError),
MalformedGatewayError(identity::Ed25519RecoveryError),
MalformedGatewayError(identity::KeyRecoveryError),
}
impl fmt::Display for RecipientFormattingError {
+4 -4
View File
@@ -13,7 +13,7 @@ use std::net::SocketAddr;
#[derive(Debug)]
pub enum GatewayConversionError {
InvalidIdentityKey(identity::Ed25519RecoveryError),
InvalidIdentityKey(identity::KeyRecoveryError),
InvalidSphinxKey(encryption::KeyRecoveryError),
InvalidAddress(String, io::Error),
InvalidStake,
@@ -26,8 +26,8 @@ impl From<encryption::KeyRecoveryError> for GatewayConversionError {
}
}
impl From<identity::Ed25519RecoveryError> for GatewayConversionError {
fn from(err: identity::Ed25519RecoveryError) -> Self {
impl From<identity::KeyRecoveryError> for GatewayConversionError {
fn from(err: identity::KeyRecoveryError) -> Self {
GatewayConversionError::InvalidIdentityKey(err)
}
}
@@ -125,7 +125,7 @@ impl<'a> TryFrom<&'a GatewayBond> for Node {
Ok(Node {
owner: bond.owner.as_str().to_owned(),
stake: bond.pledge_amount.amount.into(),
stake: bond.bond_amount.amount.into(),
location: bond.gateway.location.clone(),
host,
mix_host,
+4 -4
View File
@@ -13,7 +13,7 @@ use std::net::SocketAddr;
#[derive(Debug)]
pub enum MixnodeConversionError {
InvalidIdentityKey(identity::Ed25519RecoveryError),
InvalidIdentityKey(identity::KeyRecoveryError),
InvalidSphinxKey(encryption::KeyRecoveryError),
InvalidAddress(String, io::Error),
InvalidStake,
@@ -26,8 +26,8 @@ impl From<encryption::KeyRecoveryError> for MixnodeConversionError {
}
}
impl From<identity::Ed25519RecoveryError> for MixnodeConversionError {
fn from(err: identity::Ed25519RecoveryError) -> Self {
impl From<identity::KeyRecoveryError> for MixnodeConversionError {
fn from(err: identity::KeyRecoveryError) -> Self {
MixnodeConversionError::InvalidIdentityKey(err)
}
}
@@ -117,7 +117,7 @@ impl<'a> TryFrom<&'a MixNodeBond> for Node {
Ok(Node {
owner: bond.owner.as_str().to_owned(),
stake: bond.pledge_amount.amount.into(),
stake: bond.bond_amount.amount.into(),
delegation: bond.total_delegation.amount.into(),
host,
mix_host,
-1641
View File
File diff suppressed because it is too large Load Diff
-13
View File
@@ -1,13 +0,0 @@
[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
+8 -12
View File
@@ -89,9 +89,8 @@ dependencies = [
[[package]]
name = "cosmwasm-crypto"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
@@ -102,18 +101,16 @@ dependencies = [
[[package]]
name = "cosmwasm-derive"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"syn",
]
[[package]]
name = "cosmwasm-std"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"base64",
"cosmwasm-crypto",
@@ -127,9 +124,8 @@ dependencies = [
[[package]]
name = "cosmwasm-storage"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3b4efe3b4f86df668520a02e9a29c23eea99b64dfcacb0e59b98346418af7f"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"cosmwasm-std",
"serde",
+16 -2
View File
@@ -5,9 +5,22 @@ 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"]
@@ -18,8 +31,9 @@ config = { path = "../../common/config"}
[dependencies]
erc20-bridge-contract = { path = "../../common/erc20-bridge-contract" }
cosmwasm-std = "1.0.0-beta2"
cosmwasm-storage = "1.0.0-beta2"
# 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"] }
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
+1 -1
View File
@@ -68,7 +68,7 @@ pub mod tests {
#[test]
fn initialize_contract() {
let mut deps = mock_dependencies();
let mut deps = mock_dependencies(&[]);
let env = mock_env();
let msg = InstantiateMsg {};
let info = mock_info("creator", &[]);
+1 -1
View File
@@ -11,7 +11,7 @@ pub mod helpers {
use erc20_bridge_contract::payment::Payment;
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies();
let mut deps = mock_dependencies(&[]);
let msg = InstantiateMsg {};
let env = mock_env();
let info = mock_info("creator", &[]);
+20 -702
View File
@@ -2,43 +2,6 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aes"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
"ctr",
"opaque-debug 0.3.0",
]
[[package]]
name = "anyhow"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62e1f47f7dc0422027a4e370dd4548d4d66b26782e513e98dca1e689e058a80e"
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "az"
version = "1.1.2"
@@ -51,39 +14,6 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blake2"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330"
dependencies = [
"byte-tools",
"crypto-mac 0.7.0",
"digest 0.8.1",
"opaque-debug 0.2.3",
]
[[package]]
name = "blake3"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "526c210b4520e416420759af363083471656e819a75e831b8d2c9d5a584f2413"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"crypto-mac 0.11.1",
"digest 0.9.0",
]
[[package]]
name = "block-buffer"
version = "0.7.3"
@@ -114,18 +44,6 @@ dependencies = [
"byte-tools",
]
[[package]]
name = "bs58"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
[[package]]
name = "bumpalo"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
[[package]]
name = "byte-tools"
version = "0.3.1"
@@ -144,53 +62,12 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cc"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
dependencies = [
"jobserver",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chacha"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862"
dependencies = [
"byteorder",
"keystream",
]
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"time 0.1.43",
"winapi",
]
[[package]]
name = "cipher"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "config"
version = "0.1.0"
@@ -209,17 +86,10 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "cosmwasm-crypto"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
@@ -230,18 +100,17 @@ dependencies = [
[[package]]
name = "cosmwasm-derive"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"syn",
]
[[package]]
name = "cosmwasm-schema"
version = "1.0.0-beta2"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe52b19d45fe3f8359db6cc24df44dbe05e5ae32539afc0f5b7f790a21aa6fd0"
checksum = "04159eec9b583671db7923ff2b979736dfb8f0152347cab9fd02373c22e1a870"
dependencies = [
"schemars",
"serde_json",
@@ -249,9 +118,8 @@ dependencies = [
[[package]]
name = "cosmwasm-std"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"base64",
"cosmwasm-crypto",
@@ -265,9 +133,8 @@ dependencies = [
[[package]]
name = "cosmwasm-storage"
version = "1.0.0-beta2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3b4efe3b4f86df668520a02e9a29c23eea99b64dfcacb0e59b98346418af7f"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"cosmwasm-std",
"serde",
@@ -288,26 +155,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto"
version = "0.1.0"
dependencies = [
"aes",
"blake3",
"bs58",
"cipher",
"digest 0.9.0",
"ed25519-dalek",
"generic-array 0.14.4",
"hkdf",
"hmac",
"log",
"nymsphinx-types",
"pemstore",
"rand",
"x25519-dalek",
]
[[package]]
name = "crypto-bigint"
version = "0.2.11"
@@ -316,20 +163,10 @@ checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03"
dependencies = [
"generic-array 0.14.4",
"rand_core 0.6.3",
"subtle 2.4.1",
"subtle",
"zeroize",
]
[[package]]
name = "crypto-mac"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
dependencies = [
"generic-array 0.12.4",
"subtle 1.0.0",
]
[[package]]
name = "crypto-mac"
version = "0.11.1"
@@ -337,16 +174,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
dependencies = [
"generic-array 0.14.4",
"subtle 2.4.1",
]
[[package]]
name = "ctr"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
dependencies = [
"cipher",
"subtle",
]
[[package]]
@@ -358,21 +186,10 @@ dependencies = [
"byteorder",
"digest 0.9.0",
"rand_core 0.5.1",
"subtle 2.4.1",
"subtle",
"zeroize",
]
[[package]]
name = "cw-storage-plus"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3b8b840947313c1a1cccf056836cd79a60b4526bdcd6582995be37dc97be4ae"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "der"
version = "0.4.4"
@@ -418,29 +235,6 @@ dependencies = [
"signature",
]
[[package]]
name = "ed25519"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74e1069e39f1454367eb2de793ed062fac4c35c2934b76a81d90dd9abcd28816"
dependencies = [
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
dependencies = [
"curve25519-dalek",
"ed25519",
"rand",
"serde",
"sha2",
"zeroize",
]
[[package]]
name = "ed25519-zebra"
version = "2.2.0"
@@ -467,30 +261,10 @@ dependencies = [
"group",
"pkcs8",
"rand_core 0.6.3",
"subtle 2.4.1",
"subtle",
"zeroize",
]
[[package]]
name = "enum-iterator"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6"
dependencies = [
"enum-iterator-derive",
]
[[package]]
name = "enum-iterator-derive"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
@@ -504,7 +278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f"
dependencies = [
"rand_core 0.6.3",
"subtle 2.4.1",
"subtle",
]
[[package]]
@@ -556,10 +330,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@@ -573,31 +345,6 @@ dependencies = [
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "getset"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24b328c01a4d71d2d8173daa93562a73ab0fe85616876f02500f53d82948c504"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "git2"
version = "0.13.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "845e007a28f1fcac035715988a234e8ec5458fd825b20a20c7dec74237ef341f"
dependencies = [
"bitflags",
"libc",
"libgit2-sys",
"log",
"url",
]
[[package]]
name = "group"
version = "0.10.0"
@@ -606,7 +353,7 @@ checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
dependencies = [
"ff",
"rand_core 0.6.3",
"subtle 2.4.1",
"subtle",
]
[[package]]
@@ -641,23 +388,13 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21e4590e13640f19f249fe3e4eca5113bc4289f2497710378190e7f4bd96f45b"
[[package]]
name = "hkdf"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b"
dependencies = [
"digest 0.9.0",
"hmac",
]
[[package]]
name = "hmac"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
dependencies = [
"crypto-mac 0.11.1",
"crypto-mac",
"digest 0.9.0",
]
@@ -694,24 +431,6 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "jobserver"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "k256"
version = "0.9.6"
@@ -724,66 +443,12 @@ dependencies = [
"sha2",
]
[[package]]
name = "keystream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013"
[[package]]
name = "libgit2-sys"
version = "0.12.25+1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68169ef08d6519b2fe133ecc637408d933c0174b23b80bb2f79828966fbaab"
dependencies = [
"cc",
"libc",
"libz-sys",
"pkg-config",
]
[[package]]
name = "libm"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
[[package]]
name = "libz-sys"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "lioness"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9"
dependencies = [
"arrayref",
"blake2",
"chacha",
"keystream",
]
[[package]]
name = "log"
version = "0.4.14"
@@ -824,21 +489,15 @@ dependencies = [
name = "mixnet-contracts"
version = "0.1.0"
dependencies = [
"bs58",
"config",
"cosmwasm-schema",
"cosmwasm-std",
"cosmwasm-storage",
"crypto",
"cw-storage-plus",
"fixed",
"mixnet-contract",
"rand",
"rand_chacha",
"schemars",
"serde",
"thiserror",
"vergen",
]
[[package]]
@@ -847,43 +506,10 @@ version = "0.1.0"
dependencies = [
"hex-literal",
"serde",
"time 0.3.4",
"time",
"url",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
"libm",
]
[[package]]
name = "nymsphinx-types"
version = "0.1.0"
dependencies = [
"sphinx",
]
[[package]]
name = "once_cell"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "opaque-debug"
version = "0.2.3"
@@ -896,24 +522,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "pem"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb"
dependencies = [
"base64",
"once_cell",
"regex",
]
[[package]]
name = "pemstore"
version = "0.1.0"
dependencies = [
"pem",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
@@ -973,42 +581,6 @@ dependencies = [
"spki",
]
[[package]]
name = "pkg-config"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
[[package]]
name = "ppv-lite86"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.32"
@@ -1033,29 +605,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha",
"rand_core 0.5.1",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
]
[[package]]
name = "rand_core"
version = "0.5.1"
@@ -1074,55 +623,6 @@ dependencies = [
"getrandom 0.2.3",
]
[[package]]
name = "rand_distr"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56"
dependencies = [
"num-traits",
"rand",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "rustversion"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
[[package]]
name = "ryu"
version = "1.0.5"
@@ -1153,12 +653,6 @@ dependencies = [
"syn",
]
[[package]]
name = "semver"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
[[package]]
name = "serde"
version = "1.0.130"
@@ -1256,29 +750,6 @@ dependencies = [
"rand_core 0.6.3",
]
[[package]]
name = "sphinx"
version = "0.1.0"
source = "git+https://github.com/nymtech/sphinx?rev=c494250f2a78bed33a618d470792418eee932859#c494250f2a78bed33a618d470792418eee932859"
dependencies = [
"aes",
"arrayref",
"blake2",
"bs58",
"byteorder",
"chacha",
"curve25519-dalek",
"digest 0.9.0",
"hkdf",
"hmac",
"lioness",
"log",
"rand",
"rand_distr",
"sha2",
"subtle 2.4.1",
]
[[package]]
name = "spki"
version = "0.4.1"
@@ -1294,12 +765,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "subtle"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
[[package]]
name = "subtle"
version = "2.4.1"
@@ -1317,18 +782,6 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.30"
@@ -1349,16 +802,6 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "time"
version = "0.3.4"
@@ -1456,29 +899,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vergen"
version = "5.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cf88d94e969e7956d924ba70741316796177fa0c79a2c9f4ab04998d96e966e"
dependencies = [
"anyhow",
"cfg-if",
"chrono",
"enum-iterator",
"getset",
"git2",
"rustc_version",
"rustversion",
"thiserror",
]
[[package]]
name = "version_check"
version = "0.9.3"
@@ -1497,110 +917,8 @@ version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "x25519-dalek"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077"
dependencies = [
"curve25519-dalek",
"rand_core 0.5.1",
"zeroize",
]
[[package]]
name = "zeroize"
version = "1.3.0"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65f1a51723ec88c66d5d1fe80c841f17f63587d6691901d66be9bec6c3b51f73"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970"
+20 -12
View File
@@ -12,9 +12,22 @@ 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"]
@@ -22,23 +35,18 @@ backtraces = ["cosmwasm-std/backtraces"]
[dependencies]
mixnet-contract = { path = "../../common/mixnet-contract" }
config = { path = "../../common/config"}
vesting-contract = { path = "../vesting" }
cosmwasm-std = "1.0.0-beta2"
cosmwasm-storage = "1.0.0-beta2"
cw-storage-plus = "0.10.3"
# 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"] }
bs58 = "0.4.0"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
[dev-dependencies]
cosmwasm-schema = "1.0.0-beta2"
cosmwasm-schema = { version = "0.14.0" }
fixed = "1.1"
rand_chacha = "0.2"
rand = "0.7"
crypto = { path = "../../common/crypto" }
[build-dependencies]
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc"] }
-8
View File
@@ -1,8 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use vergen::{vergen, Config};
fn main() {
vergen(Config::default()).expect("failed to extract build metadata")
}
+2
View File
@@ -5,6 +5,7 @@ extern crate mixnet_contract;
use cosmwasm_schema::{export_schema, remove_schemas, schema_for};
use mixnet_contract::{ExecuteMsg, InstantiateMsg, MixNodeBond, QueryMsg};
use mixnet_contracts::state::State;
use std::env::current_dir;
use std::fs::create_dir_all;
@@ -17,5 +18,6 @@ fn main() {
export_schema(&schema_for!(InstantiateMsg), &out_dir);
export_schema(&schema_for!(ExecuteMsg), &out_dir);
export_schema(&schema_for!(QueryMsg), &out_dir);
export_schema(&schema_for!(State), &out_dir);
export_schema(&schema_for!(MixNodeBond), &out_dir);
}
+3 -3
View File
@@ -69,10 +69,10 @@
{
"type": "object",
"required": [
"update_contract_settings"
"update_state_params"
],
"properties": {
"update_contract_settings": {
"update_state_params": {
"$ref": "#/definitions/StateParams"
}
},
@@ -346,4 +346,4 @@
"type": "string"
}
}
}
}
+3 -3
View File
@@ -101,10 +101,10 @@
{
"type": "object",
"required": [
"contract_settings_params"
"state_params"
],
"properties": {
"contract_settings_params": {
"state_params": {
"type": "object"
}
},
@@ -321,4 +321,4 @@
"type": "string"
}
}
}
}
+70 -167
View File
@@ -1,35 +1,22 @@
// 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;
use crate::mixnet_contract_settings::models::ContractState;
use crate::mixnet_contract_settings::queries::query_rewarding_interval;
use crate::mixnet_contract_settings::queries::{
query_contract_settings_params, query_contract_version,
};
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::bonding_queries as mixnode_queries;
use crate::mixnodes::bonding_queries::query_mixnodes_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 std::u128;
use crate::state::State;
use crate::storage::{config, layer_distribution};
use crate::{error::ContractError, queries, transactions};
use config::defaults::REWARDING_VALIDATOR_ADDRESS;
use cosmwasm_std::{
entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128,
};
use mixnet_contract::{ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
use mixnet_contract::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StateParams};
/// Constant specifying minimum of coin required to bond a gateway
pub const INITIAL_GATEWAY_PLEDGE: Uint128 = Uint128::new(100_000_000);
pub const INITIAL_GATEWAY_BOND: Uint128 = Uint128(100_000000);
/// Constant specifying minimum of coin required to bond a mixnode
pub const INITIAL_MIXNODE_PLEDGE: Uint128 = Uint128::new(100_000_000);
pub const INITIAL_MIXNODE_BOND: Uint128 = Uint128(100_000000);
pub const INITIAL_MIXNODE_REWARDED_SET_SIZE: u32 = 200;
pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100;
@@ -37,22 +24,19 @@ pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100;
pub const INITIAL_REWARD_POOL: u128 = 250_000_000_000_000;
pub const EPOCH_REWARD_PERCENT: u8 = 2; // Used to calculate epoch reward pool
pub const DEFAULT_SYBIL_RESISTANCE_PERCENT: u8 = 30;
pub const DEFAULT_ACTIVE_SET_WORK_FACTOR: u8 = 10;
fn default_initial_state(
owner: Addr,
rewarding_validator_address: Addr,
env: Env,
) -> ContractState {
ContractState {
// We'll be assuming a few more things, profit margin and cost function. Since we don't have reliable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate epoch costs to Nyms. We'll also assume a cost of 40$ per epoch(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms
pub const DEFAULT_COST_PER_EPOCH: u32 = 40_000_000;
fn default_initial_state(owner: Addr, env: Env) -> State {
State {
owner,
rewarding_validator_address,
params: ContractStateParams {
minimum_mixnode_pledge: INITIAL_MIXNODE_PLEDGE,
minimum_gateway_pledge: INITIAL_GATEWAY_PLEDGE,
rewarding_validator_address: Addr::unchecked(REWARDING_VALIDATOR_ADDRESS), // we trust our hardcoded value
params: StateParams {
minimum_mixnode_bond: INITIAL_MIXNODE_BOND,
minimum_gateway_bond: INITIAL_GATEWAY_BOND,
mixnode_rewarded_set_size: INITIAL_MIXNODE_REWARDED_SET_SIZE,
mixnode_active_set_size: INITIAL_MIXNODE_ACTIVE_SET_SIZE,
active_set_work_factor: DEFAULT_ACTIVE_SET_WORK_FACTOR,
},
rewarding_interval_starting_block: env.block.height,
latest_rewarding_interval_nonce: 0,
@@ -70,15 +54,12 @@ pub fn instantiate(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: InstantiateMsg,
_msg: InstantiateMsg,
) -> Result<Response, ContractError> {
let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?;
let state = default_initial_state(info.sender, rewarding_validator_address, env);
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &state)?;
mixnet_params_storage::LAYERS.save(deps.storage, &Default::default())?;
rewards_storage::REWARD_POOL.save(deps.storage, &Uint128::new(INITIAL_REWARD_POOL))?;
let state = default_initial_state(info.sender, env);
config(deps.storage).save(&state)?;
layer_distribution(deps.storage).save(&Default::default())?;
Ok(Response::default())
}
@@ -91,42 +72,22 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::BondMixnode {
mix_node,
owner_signature,
} => crate::mixnodes::transactions::try_add_mixnode(
deps,
env,
info,
mix_node,
owner_signature,
),
ExecuteMsg::UnbondMixnode {} => {
crate::mixnodes::transactions::try_remove_mixnode(deps, info)
ExecuteMsg::BondMixnode { mix_node } => {
transactions::try_add_mixnode(deps, env, info, mix_node)
}
ExecuteMsg::BondGateway {
gateway,
owner_signature,
} => crate::gateways::transactions::try_add_gateway(
deps,
env,
info,
gateway,
owner_signature,
),
ExecuteMsg::UnbondGateway {} => {
crate::gateways::transactions::try_remove_gateway(deps, info)
ExecuteMsg::UnbondMixnode {} => transactions::try_remove_mixnode(deps, info),
ExecuteMsg::BondGateway { gateway } => {
transactions::try_add_gateway(deps, env, info, gateway)
}
ExecuteMsg::UpdateContractStateParams(params) => {
crate::mixnet_contract_settings::transactions::try_update_contract_settings(
deps, info, params,
)
ExecuteMsg::UnbondGateway {} => transactions::try_remove_gateway(deps, info),
ExecuteMsg::UpdateStateParams(params) => {
transactions::try_update_state_params(deps, info, params)
}
ExecuteMsg::RewardMixnode {
ExecuteMsg::RewardMixnodeV2 {
identity,
params,
rewarding_interval_nonce,
} => crate::rewards::transactions::try_reward_mixnode(
} => transactions::try_reward_mixnode_v2(
deps,
env,
info,
@@ -135,143 +96,88 @@ pub fn execute(
rewarding_interval_nonce,
),
ExecuteMsg::DelegateToMixnode { mix_identity } => {
crate::delegations::transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
}
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
crate::delegations::transactions::try_remove_delegation_from_mixnode(
deps,
info,
mix_identity,
)
transactions::try_remove_delegation_from_mixnode(deps, info, mix_identity)
}
ExecuteMsg::BeginMixnodeRewarding {
rewarding_interval_nonce,
} => crate::rewards::transactions::try_begin_mixnode_rewarding(
deps,
env,
info,
rewarding_interval_nonce,
),
} => transactions::try_begin_mixnode_rewarding(deps, env, info, rewarding_interval_nonce),
ExecuteMsg::FinishMixnodeRewarding {
rewarding_interval_nonce,
} => crate::rewards::transactions::try_finish_mixnode_rewarding(
deps,
info,
rewarding_interval_nonce,
),
} => transactions::try_finish_mixnode_rewarding(deps, info, rewarding_interval_nonce),
ExecuteMsg::RewardNextMixDelegators {
mix_identity,
rewarding_interval_nonce,
} => crate::rewards::transactions::try_reward_next_mixnode_delegators(
} => transactions::try_reward_next_mixnode_delegators_v2(
deps,
info,
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,
owner_signature,
} => crate::mixnodes::transactions::try_add_mixnode_on_behalf(
deps,
env,
info,
mix_node,
owner,
owner_signature,
),
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(deps, info, owner)
}
ExecuteMsg::BondGatewayOnBehalf {
gateway,
owner,
owner_signature,
} => crate::gateways::transactions::try_add_gateway_on_behalf(
deps,
env,
info,
gateway,
owner,
owner_signature,
),
ExecuteMsg::UnbondGatewayOnBehalf { owner } => {
crate::gateways::transactions::try_remove_gateway_on_behalf(deps, info, owner)
}
}
}
#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg {
QueryMsg::GetContractVersion {} => to_binary(&query_contract_version()),
QueryMsg::GetMixNodes { start_after, limit } => {
to_binary(&query_mixnodes_paged(deps, start_after, limit)?)
to_binary(&queries::query_mixnodes_paged(deps, start_after, limit)?)
}
QueryMsg::GetGateways { limit, start_after } => {
to_binary(&query_gateways_paged(deps, start_after, limit)?)
to_binary(&queries::query_gateways_paged(deps, start_after, limit)?)
}
QueryMsg::OwnsMixnode { address } => {
to_binary(&mixnode_queries::query_owns_mixnode(deps, address)?)
to_binary(&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::GetMixnodeDelegations {
QueryMsg::OwnsGateway { address } => {
to_binary(&queries::query_owns_gateway(deps, address)?)
}
QueryMsg::StateParams {} => to_binary(&queries::query_state_params(deps)),
QueryMsg::CurrentRewardingInterval {} => {
to_binary(&queries::query_rewarding_interval(deps))
}
QueryMsg::LayerDistribution {} => to_binary(&queries::query_layer_distribution(deps)),
QueryMsg::GetMixDelegations {
mix_identity,
start_after,
limit,
} => to_binary(&query_mixnode_delegations_paged(
} => to_binary(&queries::query_mixnode_delegations_paged(
deps,
mix_identity,
start_after,
limit,
)?),
QueryMsg::GetAllNetworkDelegations { start_after, limit } => to_binary(
&query_all_network_delegations_paged(deps, start_after, limit)?,
QueryMsg::GetAllMixDelegations { start_after, limit } => to_binary(
&queries::query_all_mixnode_delegations_paged(deps, start_after, limit)?,
),
QueryMsg::GetDelegatorDelegations {
delegator: delegation_owner,
QueryMsg::GetReverseMixDelegations {
delegation_owner,
start_after,
limit,
} => to_binary(&query_delegator_delegations_paged(
} => to_binary(&queries::query_reverse_mixnode_delegations_paged(
deps,
delegation_owner,
start_after,
limit,
)?),
QueryMsg::GetDelegationDetails {
QueryMsg::GetMixDelegation {
mix_identity,
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)?),
address,
} => to_binary(&queries::query_mixnode_delegation(
deps,
mix_identity,
address,
)?),
QueryMsg::GetRewardPool {} => to_binary(&queries::query_reward_pool(deps)),
QueryMsg::GetCirculatingSupply {} => to_binary(&queries::query_circulating_supply(deps)),
QueryMsg::GetEpochRewardPercent {} => to_binary(&EPOCH_REWARD_PERCENT),
QueryMsg::GetSybilResistancePercent {} => to_binary(&DEFAULT_SYBIL_RESISTANCE_PERCENT),
QueryMsg::GetRewardingStatus {
mix_identity,
rewarding_interval_nonce,
} => to_binary(&query_rewarding_status(
} => to_binary(&queries::query_rewarding_status(
deps,
mix_identity,
rewarding_interval_nonce,
@@ -282,14 +188,13 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
}
#[entry_point]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
todo!("ACTIVE_STATE_WORK_FACTOR to State");
// Ok(Default::default())
Ok(Default::default())
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::support::tests::test_helpers;
use crate::support::tests::helpers::*;
use config::defaults::DENOM;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, from_binary};
@@ -297,11 +202,9 @@ pub mod tests {
#[test]
fn initialize_contract() {
let mut deps = mock_dependencies();
let mut deps = mock_dependencies(&[]);
let env = mock_env();
let msg = InstantiateMsg {
rewarding_validator_address: config::defaults::REWARDING_VALIDATOR_ADDRESS.to_string(),
};
let msg = InstantiateMsg {};
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
@@ -323,7 +226,7 @@ pub mod tests {
// Contract balance should match what we initialized it as
assert_eq!(
coins(0, DENOM),
test_helpers::query_contract_balance(env.contract.address, deps)
query_contract_balance(env.contract.address, deps)
);
}
}
-6
View File
@@ -1,6 +0,0 @@
// 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;
-649
View File
@@ -1,649 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::error::ContractError;
use cosmwasm_std::Deps;
use cosmwasm_std::Order;
use cosmwasm_std::StdResult;
use cw_storage_plus::{Bound, PrimaryKey};
use mixnet_contract::PagedAllDelegationsResponse;
use mixnet_contract::PagedDelegatorDelegationsResponse;
use mixnet_contract::PagedMixDelegationsResponse;
use mixnet_contract::{Delegation, IdentityKey};
pub(crate) fn query_all_network_delegations_paged(
deps: Deps,
start_after: Option<(IdentityKey, String)>,
limit: Option<u32>,
) -> StdResult<PagedAllDelegationsResponse> {
let limit = limit
.unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT)
.min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize;
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_delegator_delegations_paged(
deps: Deps,
delegation_owner: String,
start_after: Option<IdentityKey>,
limit: Option<u32>,
) -> 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 = start_after
.map(|mix_identity| Bound::Exclusive((mix_identity, validated_owner.clone()).joined_key()));
let delegations = storage::delegations()
.idx
.owner
.prefix(validated_owner)
.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.node_identity());
Ok(PagedDelegatorDelegationsResponse::new(
delegations,
start_next_after,
))
}
// queries for delegation value of given address for particular node
pub(crate) fn query_mixnode_delegation(
deps: Deps,
mix_identity: IdentityKey,
delegator: String,
) -> Result<Delegation, ContractError> {
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: 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 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);
test_helpers::save_dummy_delegation(storage, node_identity, address);
}
}
#[cfg(test)]
mod querying_for_mixnode_delegations_paged {
use super::*;
use mixnet_contract::IdentityKey;
#[test]
fn retrieval_obeys_limits() {
let mut deps = test_helpers::init_contract();
let limit = 2;
let node_identity: IdentityKey = "foo".into();
store_n_mix_delegations(100, &mut deps.storage, &node_identity);
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity,
None,
Option::from(limit),
)
.unwrap();
assert_eq!(limit, page1.delegations.len() as u32);
}
#[test]
fn retrieval_has_default_limit() {
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,
);
// query without explicitly setting a limit
let page1 =
query_mixnode_delegations_paged(deps.as_ref(), node_identity, None, None).unwrap();
assert_eq!(
storage::DELEGATION_PAGE_DEFAULT_LIMIT,
page1.delegations.len() as u32
);
}
#[test]
fn retrieval_has_max_limit() {
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,
);
// 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_mixnode_delegations_paged(
deps.as_ref(),
node_identity,
None,
Option::from(crazy_limit),
)
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = storage::DELEGATION_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.delegations.len() as u32);
}
#[test]
fn pagination_works() {
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "100");
let per_page = 2;
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity.clone(),
None,
Option::from(per_page),
)
.unwrap();
// page should have 1 result on it
assert_eq!(1, page1.delegations.len());
// save another
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "200");
// page1 should have 2 results on it
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegations.len());
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "300");
// page1 still has 2 results
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity.clone(),
None,
Option::from(per_page),
)
.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 = "200".to_string();
let page2 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity.clone(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.delegations.len());
// save another one
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "400");
let start_after = "200".to_string();
let page2 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity,
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());
}
}
#[cfg(test)]
mod querying_for_all_mixnode_delegations_paged {
use super::*;
use crate::support::tests::test_helpers;
use mixnet_contract::IdentityKey;
#[test]
fn retrieval_obeys_limits() {
let mut deps = test_helpers::init_contract();
let limit = 2;
let node_identity: IdentityKey = "foo".into();
store_n_mix_delegations(100, &mut deps.storage, &node_identity);
let page1 =
query_all_network_delegations_paged(deps.as_ref(), None, Option::from(limit))
.unwrap();
assert_eq!(limit, page1.delegations.len() as u32);
}
#[test]
fn retrieval_has_default_limit() {
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,
);
// query without explicitly setting a limit
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
);
}
#[test]
fn retrieval_has_max_limit() {
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,
);
// 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_network_delegations_paged(deps.as_ref(), None, Option::from(crazy_limit))
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = storage::DELEGATION_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.delegations.len() as u32);
}
#[test]
fn pagination_works() {
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "100");
let per_page = 2;
let page1 =
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
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "200");
// page1 should have 2 results on it
let page1 =
query_all_network_delegations_paged(deps.as_ref(), None, Option::from(per_page))
.unwrap();
assert_eq!(2, page1.delegations.len());
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "300");
// page1 still has 2 results
let page1 =
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 = page1.start_next_after.unwrap();
let page2 = query_all_network_delegations_paged(
deps.as_ref(),
Option::from(start_after.clone()),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.delegations.len());
// save another one
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "400");
let page2 = query_all_network_delegations_paged(
deps.as_ref(),
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 = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
let delegation_owner = Addr::unchecked("bar");
let delegation = Delegation::new(
delegation_owner.clone(),
node_identity.clone(),
coin(1234, DENOM),
1234,
None,
);
storage::delegations()
.save(
deps.as_mut().storage,
delegation.storage_key().joined_key(),
&delegation,
)
.unwrap();
assert_eq!(
Ok(delegation),
query_mixnode_delegation(deps.as_ref(), node_identity, delegation_owner.to_string())
)
}
#[test]
fn mix_deletion_query_returns_error_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_eq!(
Err(ContractError::NoMixnodeDelegationFound {
identity: node_identity1.clone(),
address: delegation_owner1.clone(),
}),
query_mixnode_delegation(
deps.as_ref(),
node_identity1.clone(),
delegation_owner1.to_string()
)
);
// add delegation from a different address
let delegation = Delegation::new(
delegation_owner2.clone(),
node_identity1.clone(),
coin(1234, DENOM),
1234,
None,
);
storage::delegations()
.save(
deps.as_mut().storage,
delegation.storage_key().joined_key(),
&delegation,
)
.unwrap();
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
identity: node_identity1.clone(),
address: delegation_owner1.clone(),
}),
query_mixnode_delegation(
deps.as_ref(),
node_identity1.clone(),
delegation_owner1.to_string()
)
);
// add delegation for a different node
let delegation = Delegation::new(
delegation_owner1.clone(),
node_identity2.clone(),
coin(1234, DENOM),
1234,
None,
);
storage::delegations()
.save(
deps.as_mut().storage,
delegation.storage_key().joined_key(),
&delegation,
)
.unwrap();
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
identity: node_identity1.clone(),
address: Addr::unchecked(delegation_owner1.clone())
}),
query_mixnode_delegation(
deps.as_ref(),
node_identity1.clone(),
delegation_owner1.to_string()
)
)
}
#[cfg(test)]
mod querying_for_reverse_mixnode_delegations_paged {
use super::*;
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);
test_helpers::save_dummy_delegation(storage, node_identity, delegation_owner);
}
}
#[test]
fn retrieval_obeys_limits() {
let mut deps = test_helpers::init_contract();
let limit = 2;
let delegation_owner = "foo".to_string();
store_n_reverse_delegations(100, &mut deps.storage, &delegation_owner);
let page1 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner,
None,
Option::from(limit),
)
.unwrap();
assert_eq!(limit, page1.delegations.len() as u32);
}
#[test]
fn retrieval_has_default_limit() {
let mut deps = test_helpers::init_contract();
let delegation_owner = "foo".to_string();
store_n_reverse_delegations(
storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&delegation_owner,
);
// query without explicitly setting a limit
let page1 =
query_delegator_delegations_paged(deps.as_ref(), delegation_owner, None, None)
.unwrap();
assert_eq!(
storage::DELEGATION_PAGE_DEFAULT_LIMIT,
page1.delegations.len() as u32
);
}
#[test]
fn retrieval_has_max_limit() {
let mut deps = test_helpers::init_contract();
let delegation_owner = "foo".to_string();
store_n_reverse_delegations(
storage::DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&delegation_owner,
);
// 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_delegator_delegations_paged(
deps.as_ref(),
delegation_owner,
None,
Option::from(crazy_limit),
)
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = storage::DELEGATION_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.delegations.len() as u32);
}
#[test]
fn pagination_works() {
let mut deps = test_helpers::init_contract();
let delegation_owner = "bar".to_string();
test_helpers::save_dummy_delegation(&mut deps.storage, "100", &delegation_owner);
let per_page = 2;
let page1 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
// page should have 1 result on it
assert_eq!(1, page1.delegations.len());
// save another
test_helpers::save_dummy_delegation(&mut deps.storage, "200", &delegation_owner);
// page1 should have 2 results on it
let page1 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegations.len());
test_helpers::save_dummy_delegation(&mut deps.storage, "300", &delegation_owner);
// page1 still has 2 results
let page1 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_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: IdentityKey = page1.start_next_after.unwrap();
let page2 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.delegations.len());
// save another one
test_helpers::save_dummy_delegation(&mut deps.storage, "400", &delegation_owner);
let start_after = String::from("2");
let page2 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_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());
}
}
}
-180
View File
@@ -1,180 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex};
use mixnet_contract::{Addr, Delegation, IdentityKey};
// storage prefixes
const DELEGATION_PK_NAMESPACE: &str = "dl";
const DELEGATION_OWNER_IDX_NAMESPACE: &str = "dlo";
const DELEGATION_MIXNODE_IDX_NAMESPACE: &str = "dlm";
// paged retrieval limits for all queries and transactions
pub(crate) const DELEGATION_PAGE_MAX_LIMIT: u32 = 500;
pub(crate) const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 250;
// It's a composite key on node's identity and delegator address
type PrimaryKey = Vec<u8>;
pub(crate) struct DelegationIndex<'a> {
pub(crate) owner: MultiIndex<'a, (Addr, PrimaryKey), Delegation>,
pub(crate) mixnode: MultiIndex<'a, (IdentityKey, PrimaryKey), Delegation>,
}
impl<'a> IndexList<Delegation> for DelegationIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<Delegation>> + '_> {
let v: Vec<&dyn Index<Delegation>> = vec![&self.owner, &self.mixnode];
Box::new(v.into_iter())
}
}
// I was really going back and forth about the data stored on the disk vs primary key duplication.
// It was basically between convenience and bloat, but in the end I decided the convenience wins.
//
// Basically I had 2 approaches. a) store delegator address and mixnode identity only as primary key of delegation or
// b) store it both as primary key AND inside delegation data.
// For the longest time I was in favour of a), since that removed any data duplication. However...,
// that also required that during index creation I recovered delegator address and mixnode identity
// from the Vec<u8>. That doesn't sound that terrible. However, even though I'm 99.99% certain that
// conversion would be impossible to fail, I'd still have to call an `unwrap` here due to required
// type signature and I didn't feel super comfortable doing that in our smart contract...
// So to get rid of this uncertainty I went with the b) approach. Even though each stored delegation
// takes over ~250B (since the key has to be duplicated), in the grand blockchain scheme of things
// it's not that terrible. Say we had 100_000_000 delegations -> that's still only 25GB of data
// and as a nice by-product it cleans up code a little bit by only having a single Delegation type.
pub(crate) fn delegations<'a>() -> IndexedMap<'a, PrimaryKey, Delegation, DelegationIndex<'a>> {
let indexes = DelegationIndex {
owner: MultiIndex::new(
|d, pk| (d.owner.clone(), pk),
DELEGATION_PK_NAMESPACE,
DELEGATION_OWNER_IDX_NAMESPACE,
),
mixnode: MultiIndex::new(
|d, pk| (d.node_identity.clone(), pk),
DELEGATION_PK_NAMESPACE,
DELEGATION_MIXNODE_IDX_NAMESPACE,
),
};
IndexedMap::new(DELEGATION_PK_NAMESPACE, indexes)
}
#[cfg(test)]
mod tests {
use crate::delegations::storage;
use cosmwasm_std::Addr;
use mixnet_contract::IdentityKey;
#[cfg(test)]
mod reverse_mix_delegations {
use super::*;
use crate::support::tests::test_helpers;
use config::defaults::DENOM;
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::{coin, Order};
use cw_storage_plus::PrimaryKey;
use mixnet_contract::Delegation;
#[test]
fn reverse_mix_delegation_exists() {
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
let delegation_owner = Addr::unchecked("bar");
let delegation = coin(12345, DENOM);
let dummy_data = Delegation::new(
delegation_owner.clone(),
node_identity.clone(),
delegation,
mock_env().block.height,
None,
);
storage::delegations()
.save(
&mut deps.storage,
(node_identity.clone(), delegation_owner.clone()).joined_key(),
&dummy_data,
)
.unwrap();
let read = storage::delegations()
.idx
.owner
.prefix(delegation_owner)
.range(&deps.storage, None, None, Order::Ascending)
.map(|record| record.unwrap().1)
.collect::<Vec<_>>();
assert_eq!(1, read.len());
assert_eq!(dummy_data, read[0]);
}
#[test]
fn reverse_mix_delegation_returns_none_if_delegation_doesnt_exist() {
let mut deps = test_helpers::init_contract();
let node_identity1: IdentityKey = "foo1".into();
let node_identity2: IdentityKey = "foo2".into();
let delegation_owner1 = Addr::unchecked("bar");
let delegation_owner2 = Addr::unchecked("bar2");
let delegation = coin(12345, DENOM);
assert!(test_helpers::read_delegation(
deps.as_ref().storage,
&node_identity1,
&delegation_owner1
)
.is_none());
// add delegation for a different node
let dummy_data = Delegation::new(
delegation_owner1.clone(),
node_identity2.clone(),
delegation.clone(),
mock_env().block.height,
None,
);
storage::delegations()
.save(
&mut deps.storage,
(node_identity1.clone(), delegation_owner1.clone()).joined_key(),
&dummy_data,
)
.unwrap();
storage::delegations()
.idx
.owner
.prefix(delegation_owner1.clone())
.range(&deps.storage, None, None, Order::Ascending)
.map(|record| record.unwrap().1)
.for_each(|delegation| assert_ne!(delegation.node_identity, node_identity1));
// add delegation from a different owner
let dummy_data = Delegation::new(
delegation_owner2.clone(),
node_identity1.clone(),
delegation.clone(),
mock_env().block.height,
None,
);
storage::delegations()
.save(
&mut deps.storage,
(node_identity1.clone(), delegation_owner2.clone()).joined_key(),
&dummy_data,
)
.unwrap();
storage::delegations()
.idx
.owner
.prefix(delegation_owner1.clone())
.range(&deps.storage, None, None, Order::Ascending)
.map(|record| record.unwrap().1)
.for_each(|delegation| assert_ne!(delegation.node_identity, node_identity1));
}
}
}
@@ -1,862 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::error::ContractError;
use crate::mixnodes::storage as mixnodes_storage;
use crate::support::helpers::generate_storage_key;
use config::defaults::DENOM;
use cosmwasm_std::{coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response};
use cw_storage_plus::PrimaryKey;
use mixnet_contract::Delegation;
use mixnet_contract::IdentityKey;
use vesting_contract::messages::ExecuteMsg as VestingContractExecuteMsg;
fn validate_delegation_stake(mut delegation: Vec<Coin>) -> Result<Coin, ContractError> {
// check if anything was put as delegation
if delegation.is_empty() {
return Err(ContractError::EmptyDelegation);
}
if delegation.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
// check that the denomination is correct
if delegation[0].denom != DENOM {
return Err(ContractError::WrongDenom {});
}
// check that we have provided a non-zero amount in the delegation
if delegation[0].amount.is_zero() {
return Err(ContractError::EmptyDelegation);
}
Ok(delegation.pop().unwrap())
}
pub(crate) fn try_delegate_to_mixnode(
deps: DepsMut,
env: Env,
info: MessageInfo,
mix_identity: IdentityKey,
) -> Result<Response, ContractError> {
// check if the delegation contains any funds of the appropriate denomination
let amount = validate_delegation_stake(info.funds)?;
_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
let amount = validate_delegation_stake(info.funds)?;
_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: Coin,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let delegate = deps.api.addr_validate(delegate)?;
// check if the target node actually exists
if mixnodes_storage::mixnodes()
.may_load(deps.storage, &mix_identity)?
.is_none()
{
return Err(ContractError::MixNodeBondNotFound {
identity: mix_identity,
});
}
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
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() + amount.amount)
},
)?;
// 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.amount, Some(env.block.height));
existing_delegation
}
None => Delegation::new(
delegate.to_owned(),
mix_identity,
amount,
env.block.height,
proxy,
),
})
},
)?;
Ok(Response::default())
}
pub(crate) fn try_remove_delegation_from_mixnode(
deps: DepsMut,
info: MessageInfo,
mix_identity: IdentityKey,
) -> Result<Response, ContractError> {
_try_remove_delegation_from_mixnode(deps, mix_identity, info.sender.as_str(), None)
}
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();
match delegation_map.may_load(deps.storage, storage_key.clone())? {
None => Err(ContractError::NoMixnodeDelegationFound {
identity: mix_identity,
address: delegate,
}),
Some(old_delegation) => {
// remove all delegation associated with this delegator
if proxy != old_delegation.proxy {
return Err(ContractError::ProxyMismatch {
existing: old_delegation
.proxy
.map_or_else(|| "None".to_string(), |a| a.to_string()),
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.to_string()),
});
}
// remove old delegation data from the store
// note for reviewers: I'm using `replace` as `remove` is just `may_load` followed by `replace`
// and we've already performed `may_load` and have access to pre-existing data
delegation_map.replace(deps.storage, storage_key, None, Some(&old_delegation))?;
// send delegated funds back to the delegation owner
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
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
// the second unwrap is also fine because we should NEVER underflow here,
// if we do, it means we have some serious error in our logic
Ok(total_delegation
.unwrap()
.checked_sub(old_delegation.amount.amount)
.unwrap())
},
)?;
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)
}
}
}
#[cfg(test)]
mod tests {
use cosmwasm_std::coins;
use crate::support::tests::test_helpers;
use super::storage;
use super::*;
#[cfg(test)]
mod delegation_stake_validation {
use cosmwasm_std::coin;
use super::*;
#[test]
fn stake_cant_be_empty() {
assert_eq!(
Err(ContractError::EmptyDelegation),
validate_delegation_stake(vec![])
)
}
#[test]
fn stake_must_have_single_coin_type() {
assert_eq!(
Err(ContractError::MultipleDenoms),
validate_delegation_stake(vec![
coin(123, DENOM),
coin(123, "BTC"),
coin(123, "DOGE")
])
)
}
#[test]
fn stake_coin_must_be_of_correct_type() {
assert_eq!(
Err(ContractError::WrongDenom {}),
validate_delegation_stake(coins(123, "DOGE"))
)
}
#[test]
fn stake_coin_must_have_value_greater_than_zero() {
assert_eq!(
Err(ContractError::EmptyDelegation),
validate_delegation_stake(coins(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
assert!(validate_delegation_stake(coins(1, DENOM)).is_ok());
assert!(validate_delegation_stake(coins(123, DENOM)).is_ok());
assert!(validate_delegation_stake(coins(10000000000, DENOM)).is_ok());
}
}
#[cfg(test)]
mod mix_stake_delegation {
use super::*;
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;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::Addr;
#[test]
fn fails_if_node_doesnt_exist() {
let mut deps = test_helpers::init_contract();
assert_eq!(
Err(ContractError::MixNodeBondNotFound {
identity: "non-existent-mix-identity".into()
}),
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info("sender", &coins(123, DENOM)),
"non-existent-mix-identity".into(),
)
);
}
#[test]
fn succeeds_for_existing_node() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
let delegation = coin(123, DENOM);
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &[delegation.clone()]),
identity.clone(),
)
.is_ok());
let expected = Delegation::new(
delegation_owner.clone(),
identity.clone(),
delegation.clone(),
mock_env().block.height,
None,
);
assert_eq!(
expected,
test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap()
);
// node's "total_delegation" is increased
assert_eq!(
delegation.amount,
mixnodes_storage::TOTAL_DELEGATION
.load(&deps.storage, &identity)
.unwrap()
)
}
#[test]
fn fails_if_node_unbonded() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
assert_eq!(
Err(ContractError::MixNodeBondNotFound {
identity: identity.clone()
}),
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(123, DENOM)),
identity,
)
);
}
#[test]
fn succeeds_if_node_rebonded() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation = coin(123, DENOM);
let delegation_owner = Addr::unchecked("sender");
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &[delegation.clone()]),
identity.clone(),
)
.is_ok());
let expected = Delegation::new(
delegation_owner.clone(),
identity.clone(),
delegation.clone(),
mock_env().block.height,
None,
);
assert_eq!(
expected,
test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap()
);
// node's "total_delegation" is increased
assert_eq!(
delegation.amount,
mixnodes_storage::TOTAL_DELEGATION
.load(&deps.storage, &identity)
.unwrap()
)
}
#[test]
fn is_possible_for_an_already_delegated_node() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
let delegation1 = coin(100, DENOM);
let delegation2 = coin(50, DENOM);
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &[delegation1.clone()]),
identity.clone(),
)
.unwrap();
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &[delegation2.clone()]),
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!(
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,
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();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
let delegation = coin(100, DENOM);
let env1 = mock_env();
let mut env2 = mock_env();
let initial_height = env1.block.height;
let updated_height = initial_height + 42;
// second env has grown in block height
env2.block.height = updated_height;
try_delegate_to_mixnode(
deps.as_mut(),
env1,
mock_info(delegation_owner.as_str(), &[delegation.clone()]),
identity.clone(),
)
.unwrap();
assert_eq!(
initial_height,
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner)
.unwrap()
.block_height
);
try_delegate_to_mixnode(
deps.as_mut(),
env2,
mock_info(delegation_owner.as_str(), &[delegation.clone()]),
identity.clone(),
)
.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]
fn block_height_is_not_updated_on_different_delegator() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner1 = Addr::unchecked("sender1");
let delegation_owner2 = Addr::unchecked("sender2");
let delegation1 = coin(100, DENOM);
let delegation2 = coin(120, DENOM);
let env1 = mock_env();
let mut env2 = mock_env();
let initial_height = env1.block.height;
let second_height = initial_height + 42;
// second env has grown in block height
env2.block.height = second_height;
try_delegate_to_mixnode(
deps.as_mut(),
env1,
mock_info(delegation_owner1.as_str(), &[delegation1.clone()]),
identity.clone(),
)
.unwrap();
assert_eq!(
initial_height,
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner1)
.unwrap()
.block_height
);
try_delegate_to_mixnode(
deps.as_mut(),
env2,
mock_info(delegation_owner2.as_str(), &[delegation2.clone()]),
identity.clone(),
)
.unwrap();
assert_eq!(
initial_height,
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner1)
.unwrap()
.block_height
);
assert_eq!(
second_height,
test_helpers::read_delegation(&deps.storage, identity, &delegation_owner2)
.unwrap()
.block_height
);
}
#[test]
fn is_disallowed_for_already_delegated_node_if_it_unbonded() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(100, DENOM)),
identity.clone(),
)
.unwrap();
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
assert_eq!(
Err(ContractError::MixNodeBondNotFound {
identity: identity.clone()
}),
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(50, DENOM)),
identity,
)
);
}
#[test]
fn is_allowed_for_multiple_nodes() {
let mut deps = test_helpers::init_contract();
let mixnode_owner1 = "bob";
let mixnode_owner2 = "fred";
let identity1 =
test_helpers::add_mixnode(mixnode_owner1, good_mixnode_bond(), deps.as_mut());
let identity2 =
test_helpers::add_mixnode(mixnode_owner2, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(123, DENOM)),
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(),
)
.is_ok());
let expected1 = Delegation::new(
delegation_owner.clone(),
identity1.clone(),
coin(123, DENOM),
mock_env().block.height,
None,
);
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!(
expected2,
test_helpers::read_delegation(&deps.storage, identity2, &delegation_owner).unwrap()
);
}
#[test]
fn is_allowed_by_multiple_users() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation1 = coin(123, DENOM);
let delegation2 = coin(234, DENOM);
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info("sender1", &[delegation1.clone()]),
identity.clone(),
)
.is_ok());
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info("sender2", &[delegation2.clone()]),
identity.clone(),
)
.is_ok());
// node's "total_delegation" is sum of both
assert_eq!(
delegation1.amount + delegation2.amount,
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();
let mixnode_owner = "bob";
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(), &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!(
expected,
test_helpers::read_delegation(&deps.storage, identity, delegation_owner).unwrap()
)
}
}
#[cfg(test)]
mod removing_mix_stake_delegation {
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();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
identity: identity.clone(),
address: delegation_owner.clone(),
}),
try_remove_delegation_from_mixnode(
deps.as_mut(),
mock_info(delegation_owner.as_str(), &[]),
identity,
)
);
}
#[test]
fn succeeds_if_delegation_existed() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(100, DENOM)),
identity.clone(),
)
.unwrap();
assert_eq!(
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::delegations()
.may_load(
&deps.storage,
(identity.clone(), delegation_owner).joined_key(),
)
.unwrap()
.is_none());
// and total delegation is cleared
assert_eq!(
Uint128::zero(),
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();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner = Addr::unchecked("sender");
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(100, DENOM)),
identity.clone(),
)
.unwrap();
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
assert_eq!(
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!(
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();
let mixnode_owner = "bob";
let identity =
test_helpers::add_mixnode(mixnode_owner, good_mixnode_bond(), deps.as_mut());
let delegation_owner1 = Addr::unchecked("sender1");
let delegation_owner2 = Addr::unchecked("sender2");
let delegation1 = coin(123, DENOM);
let delegation2 = coin(234, DENOM);
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner1.as_str(), &[delegation1.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(),
)
.is_ok());
// sender1 undelegates
try_remove_delegation_from_mixnode(
deps.as_mut(),
mock_info(delegation_owner1.as_str(), &[]),
identity.clone(),
)
.unwrap();
// but total delegation should still equal to what sender2 sent
// node's "total_delegation" is sum of both
assert_eq!(
delegation2.amount,
mixnodes_storage::TOTAL_DELEGATION
.load(&deps.storage, &identity)
.unwrap()
)
}
}
// #[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
// );
// }
// }
}
+9 -15
View File
@@ -24,6 +24,9 @@ pub enum ContractError {
#[error("Not enough funds sent for gateway bond. (received {received}, minimum {minimum})")]
InsufficientGatewayBond { received: u128, minimum: u128 },
#[error("Gateway ({identity}) does not exist")]
GatewayBondNotFound { identity: IdentityKey },
#[error("{owner} does not seem to own any mixnodes")]
NoAssociatedMixNodeBond { owner: Addr },
@@ -51,6 +54,9 @@ pub enum ContractError {
#[error("Provided rewarded set size is zero")]
ZeroRewardedSet,
#[error("The node had uptime larger than 100%")]
UnexpectedUptime,
#[error("This address has already bonded a mixnode")]
AlreadyOwnsMixnode,
@@ -66,6 +72,9 @@ pub enum ContractError {
#[error("No funds were provided for the delegation")]
EmptyDelegation,
#[error("Request did not come from the node owner ({owner})")]
InvalidSender { owner: Addr },
#[error("Could not find any delegation information associated with mixnode {identity} for {address}")]
NoMixnodeDelegationFound {
identity: IdentityKey,
@@ -92,19 +101,4 @@ 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 },
#[error("Failed to recover ed25519 public key from its base58 representation - {0}")]
MalformedEd25519IdentityKey(String),
#[error("Failed to recover ed25519 signature from its base58 representation - {0}")]
MalformedEd25519Signature(String),
#[error("Provided ed25519 signature did not verify correctly")]
InvalidEd25519Signature,
#[error("Profit margin percent needs to be an integer in range [0, 100], recieved {0}")]
InvalidProfitMarginPercent(u8),
}
-6
View File
@@ -1,6 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod queries;
pub mod storage;
pub mod transactions;
-212
View File
@@ -1,212 +0,0 @@
// 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 cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
use mixnet_contract::{GatewayBond, GatewayOwnershipResponse, IdentityKey, PagedGatewayResponse};
pub(crate) fn query_gateways_paged(
deps: Deps,
start_after: Option<IdentityKey>,
limit: Option<u32>,
) -> StdResult<PagedGatewayResponse> {
let limit = limit
.unwrap_or(BOND_PAGE_DEFAULT_LIMIT)
.min(BOND_PAGE_MAX_LIMIT) as usize;
let start = start_after.map(Bound::exclusive);
let nodes = storage::gateways()
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<GatewayBond>>>()?;
let start_next_after = nodes.last().map(|node| node.identity().clone());
Ok(PagedGatewayResponse::new(nodes, limit, start_next_after))
}
pub(crate) fn query_owns_gateway(
deps: Deps,
address: String,
) -> StdResult<GatewayOwnershipResponse> {
let validated_addr = deps.api.addr_validate(&address)?;
let gateway = storage::gateways()
.idx
.owner
.item(deps.storage, validated_addr.clone())?
.map(|record| record.1);
Ok(GatewayOwnershipResponse {
address: validated_addr,
gateway,
})
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::contract::execute;
use crate::support::tests::test_helpers;
use cosmwasm_std::testing::{mock_env, mock_info};
#[test]
fn gateways_empty_on_init() {
let deps = test_helpers::init_contract();
let response = query_gateways_paged(deps.as_ref(), None, Option::from(2)).unwrap();
assert_eq!(0, response.nodes.len());
}
#[test]
fn gateways_paged_retrieval_obeys_limits() {
let mut deps = test_helpers::init_contract();
let limit = 2;
for n in 0..1000 {
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);
}
#[test]
fn gateways_paged_retrieval_has_default_limit() {
let mut deps = test_helpers::init_contract();
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();
assert_eq!(BOND_PAGE_DEFAULT_LIMIT, page1.nodes.len() as u32);
}
#[test]
fn gateways_paged_retrieval_has_max_limit() {
let mut deps = test_helpers::init_contract();
for n in 0..1000 {
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;
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 = BOND_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.nodes.len() as u32);
}
#[test]
fn gateway_pagination_works() {
// prepare 4 messages and identities that are sorted by the generated identities
// (because we query them in an ascended manner)
let mut exec_data = (0..4)
.map(|i| {
let sender = format!("nym-addr{}", i);
let (msg, identity) = test_helpers::valid_bond_gateway_msg(&sender);
(msg, (sender, identity))
})
.collect::<Vec<_>>();
exec_data.sort_by(|(_, (_, id1)), (_, (_, id2))| id1.cmp(id2));
let (messages, sender_identities): (Vec<_>, Vec<_>) = exec_data.into_iter().unzip();
let mut deps = test_helpers::init_contract();
let info = mock_info(
&sender_identities[0].0.clone(),
&test_helpers::good_gateway_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[0].clone()).unwrap();
let per_page = 2;
let page1 = query_gateways_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.nodes.len());
// save another
let info = mock_info(
&sender_identities[1].0.clone(),
&test_helpers::good_gateway_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[1].clone()).unwrap();
// 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());
let info = mock_info(
&sender_identities[2].0.clone(),
&test_helpers::good_gateway_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[2].clone()).unwrap();
// 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 = page1.start_next_after.unwrap();
let page2 = query_gateways_paged(
deps.as_ref(),
Option::from(start_after.clone()),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.nodes.len());
// save another one
let info = mock_info(
&sender_identities[3].0.clone(),
&test_helpers::good_gateway_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[3].clone()).unwrap();
let page2 = query_gateways_paged(
deps.as_ref(),
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.nodes.len());
}
#[test]
fn query_for_gateway_owner_works() {
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(), "fred".to_string()).unwrap();
assert!(res.gateway.is_none());
// mixnode was added to "bob", "fred" still does not own one
test_helpers::add_gateway("bob", test_helpers::good_gateway_bond(), deps.as_mut());
let res = query_owns_gateway(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.gateway.is_none());
// "fred" now owns a gateway!
test_helpers::add_gateway("fred", test_helpers::good_gateway_bond(), deps.as_mut());
let res = query_owns_gateway(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.gateway.is_some());
// 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(), "fred".to_string()).unwrap();
assert!(res.gateway.is_none());
}
}
-108
View File
@@ -1,108 +0,0 @@
// 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 GATEWAYS_PK_NAMESPACE: &str = "gt";
const GATEWAYS_OWNER_IDX_NAMESPACE: &str = "gto";
pub(crate) struct GatewayBondIndex<'a> {
pub(crate) owner: UniqueIndex<'a, Addr, GatewayBond>,
}
// 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())
}
}
// 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 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::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_pledge_amount(
storage: &dyn Storage,
identity: IdentityKeyRef,
) -> StdResult<cosmwasm_std::Uint128> {
let node = storage::gateways().load(storage, identity)?;
Ok(node.pledge_amount.amount)
}
#[test]
fn gateway_single_read_retrieval() {
let mut storage = MockStorage::new();
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()
.save(&mut storage, "bond2", &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);
}
#[test]
fn reading_gateway_bond() {
let mut mock_storage = MockStorage::new();
let node_owner: Addr = Addr::unchecked("node-owner");
let node_identity: IdentityKey = "nodeidentity".into();
// produces an error if target gateway doesn't exist
let res = read_gateway_pledge_amount(&mock_storage, &node_identity);
assert!(res.is_err());
// returns appropriate value otherwise
let pledge_amount = 1000;
let gateway_bond = GatewayBond {
pledge_amount: coin(pledge_amount, DENOM),
owner: node_owner.clone(),
block_height: 12_345,
gateway: Gateway {
identity_key: node_identity.clone(),
..test_helpers::gateway_fixture()
},
proxy: None,
};
storage::gateways()
.save(&mut mock_storage, &node_identity, &gateway_bond)
.unwrap();
assert_eq!(
Uint128::new(pledge_amount),
read_gateway_pledge_amount(&mock_storage, &node_identity).unwrap()
);
}
}
@@ -1,569 +0,0 @@
// 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::support::helpers::{ensure_no_existing_bond, validate_node_identity_signature};
use config::defaults::DENOM;
use cosmwasm_std::{
coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128,
};
use mixnet_contract::{Gateway, GatewayBond, Layer};
use vesting_contract::messages::ExecuteMsg as VestingContractExecuteMsg;
pub fn try_add_gateway(
deps: DepsMut,
env: Env,
info: MessageInfo,
gateway: Gateway,
owner_signature: String,
) -> Result<Response, ContractError> {
// check if the pledge contains any funds of the appropriate denomination
let minimum_pledge = mixnet_params_storage::CONTRACT_STATE
.load(deps.storage)?
.params
.minimum_mixnode_pledge;
let pledge = validate_gateway_pledge(info.funds, minimum_pledge)?;
_try_add_gateway(
deps,
env,
gateway,
pledge,
info.sender.as_str(),
owner_signature,
None,
)
}
pub fn try_add_gateway_on_behalf(
deps: DepsMut,
env: Env,
info: MessageInfo,
gateway: Gateway,
owner: String,
owner_signature: String,
) -> Result<Response, ContractError> {
// check if the pledge contains any funds of the appropriate denomination
let minimum_pledge = mixnet_params_storage::CONTRACT_STATE
.load(deps.storage)?
.params
.minimum_mixnode_pledge;
let pledge = validate_gateway_pledge(info.funds, minimum_pledge)?;
let proxy = info.sender;
_try_add_gateway(
deps,
env,
gateway,
pledge,
&owner,
owner_signature,
Some(proxy),
)
}
pub(crate) fn _try_add_gateway(
deps: DepsMut,
env: Env,
gateway: Gateway,
pledge: Coin,
owner: &str,
owner_signature: String,
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 gateway with this identity
if let Some(existing_bond) =
storage::gateways().may_load(deps.storage, &gateway.identity_key)?
{
if existing_bond.owner != owner {
return Err(ContractError::DuplicateGateway {
owner: existing_bond.owner,
});
}
}
// check if this sender actually owns the gateway by checking the signature
validate_node_identity_signature(
deps.as_ref(),
&owner,
owner_signature,
&gateway.identity_key,
)?;
let bond = GatewayBond::new(pledge, owner, env.block.height, gateway, proxy);
storage::gateways().save(deps.storage, bond.identity(), &bond)?;
mixnet_params_storage::increment_layer_count(deps.storage, Layer::Gateway)?;
Ok(Response::new())
}
pub fn try_remove_gateway_on_behalf(
deps: DepsMut,
info: MessageInfo,
owner: String,
) -> Result<Response, ContractError> {
let proxy = info.sender;
_try_remove_gateway(deps, &owner, Some(proxy))
}
pub fn try_remove_gateway(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
_try_remove_gateway(deps, info.sender.as_ref(), None)
}
pub(crate) fn _try_remove_gateway(
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 gateway_bond = match storage::gateways()
.idx
.owner
.item(deps.storage, owner.clone())?
{
Some(record) => record.1,
None => return Err(ContractError::NoAssociatedGatewayBond { owner }),
};
if proxy != gateway_bond.proxy {
return Err(ContractError::ProxyMismatch {
existing: gateway_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()),
});
}
// 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![gateway_bond.pledge_amount()],
};
// remove the bond
storage::gateways().remove(deps.storage, gateway_bond.identity())?;
// decrement layer count
mixnet_params_storage::decrement_layer_count(deps.storage, Layer::Gateway)?;
let mut response = Response::new()
.add_message(return_tokens)
.add_attribute("action", "unbond")
.add_attribute("address", owner.clone())
.add_attribute("gateway_bond", gateway_bond.to_string());
if let Some(proxy) = &proxy {
let msg = VestingContractExecuteMsg::TrackUnbondGateway {
owner: owner.as_str().to_string(),
amount: gateway_bond.pledge_amount,
};
let track_unbond_message = wasm_execute(proxy, &msg, coins(0, DENOM))?;
response = response.add_message(track_unbond_message);
}
Ok(response)
}
fn validate_gateway_pledge(
mut pledge: Vec<Coin>,
minimum_pledge: Uint128,
) -> Result<Coin, ContractError> {
// check if anything was put as bond
if pledge.is_empty() {
return Err(ContractError::NoBondFound);
}
if pledge.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
// check that the denomination is correct
if pledge[0].denom != DENOM {
return Err(ContractError::WrongDenom {});
}
// check that we have at least 100 coins in our pledge
if pledge[0].amount < minimum_pledge {
return Err(ContractError::InsufficientGatewayBond {
received: pledge[0].amount.into(),
minimum: minimum_pledge.into(),
});
}
Ok(pledge.pop().unwrap())
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::contract::{execute, query, INITIAL_GATEWAY_PLEDGE};
use crate::error::ContractError;
use crate::gateways::transactions::validate_gateway_pledge;
use crate::support::tests::test_helpers;
use config::defaults::DENOM;
use cosmwasm_std::attr;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{coins, BankMsg, Response};
use cosmwasm_std::{from_binary, Addr, Uint128};
use mixnet_contract::Gateway;
use mixnet_contract::{ExecuteMsg, PagedGatewayResponse, QueryMsg};
#[test]
fn gateway_add() {
let mut deps = test_helpers::init_contract();
// if we fail validation (by say not sending enough funds
let insufficient_bond = Into::<u128>::into(INITIAL_GATEWAY_PLEDGE) - 1;
let info = mock_info("anyone", &coins(insufficient_bond, DENOM));
let (msg, _) = test_helpers::valid_bond_gateway_msg("anyone");
// we are informed that we didn't send enough funds
let result = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(
result,
Err(ContractError::InsufficientGatewayBond {
received: insufficient_bond,
minimum: INITIAL_GATEWAY_PLEDGE.into(),
})
);
// make sure no gateway was inserted into the topology
let res = query(
deps.as_ref(),
mock_env(),
QueryMsg::GetGateways {
start_after: None,
limit: Option::from(2),
},
)
.unwrap();
let page: PagedGatewayResponse = from_binary(&res).unwrap();
assert_eq!(0, page.nodes.len());
// if we send enough funds
let info = mock_info("anyone", &test_helpers::good_gateway_bond());
let (msg, identity) = test_helpers::valid_bond_gateway_msg("anyone");
// we get back a message telling us everything was OK
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert!(execute_response.is_ok());
// we can query topology and the new node is there
let query_response = query(
deps.as_ref(),
mock_env(),
QueryMsg::GetGateways {
start_after: None,
limit: Option::from(2),
},
)
.unwrap();
let page: PagedGatewayResponse = from_binary(&query_response).unwrap();
assert_eq!(1, page.nodes.len());
assert_eq!(
&Gateway {
identity_key: identity,
..test_helpers::gateway_fixture()
},
page.nodes[0].gateway()
);
// if there was already a gateway bonded by particular user
let info = mock_info("foomper", &test_helpers::good_gateway_bond());
let (msg, _) = test_helpers::valid_bond_gateway_msg("foomper");
execute(deps.as_mut(), mock_env(), info, msg).unwrap();
let info = mock_info("foomper", &test_helpers::good_gateway_bond());
let (msg, _) = test_helpers::valid_bond_gateway_msg("foomper");
// it fails
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(Err(ContractError::AlreadyOwnsGateway), execute_response);
// bonding fails if the user already owns a mixnode
test_helpers::add_mixnode(
"mixnode-owner",
test_helpers::good_mixnode_bond(),
deps.as_mut(),
);
let info = mock_info("mixnode-owner", &test_helpers::good_gateway_bond());
let (msg, _) = test_helpers::valid_bond_gateway_msg("mixnode-owner");
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(execute_response, Err(ContractError::AlreadyOwnsMixnode));
// but after he unbonds it, it's all fine again
let info = mock_info("mixnode-owner", &[]);
let msg = ExecuteMsg::UnbondMixnode {};
execute(deps.as_mut(), mock_env(), info, msg).unwrap();
let info = mock_info("mixnode-owner", &test_helpers::good_gateway_bond());
let (msg, _) = test_helpers::valid_bond_gateway_msg("mixnode-owner");
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert!(execute_response.is_ok());
// adding another node from another account, but with the same IP, should fail (or we would have a weird state).
// Is that right? Think about this, not sure yet.
}
#[test]
fn adding_gateway_without_existing_owner() {
let mut deps = test_helpers::init_contract();
let info = mock_info("gateway-owner", &test_helpers::good_gateway_bond());
// before the execution the node had no associated owner
assert!(storage::gateways()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("gateway-owner"))
.unwrap()
.is_none());
let (msg, identity) = test_helpers::valid_bond_gateway_msg("gateway-owner");
// it's all fine, owner is saved
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert!(execute_response.is_ok());
assert_eq!(
&identity,
storage::gateways()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("gateway-owner"))
.unwrap()
.unwrap()
.1
.identity()
);
}
#[test]
fn adding_gateway_with_existing_owner() {
let mut deps = test_helpers::init_contract();
let identity = test_helpers::add_gateway(
"gateway-owner",
test_helpers::good_gateway_bond(),
deps.as_mut(),
);
// request fails giving the existing owner address in the message
let info = mock_info(
"gateway-owner-pretender",
&test_helpers::good_gateway_bond(),
);
let msg = ExecuteMsg::BondGateway {
gateway: Gateway {
identity_key: identity,
..test_helpers::gateway_fixture()
},
owner_signature: "foomp".to_string(),
};
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(
Err(ContractError::DuplicateGateway {
owner: Addr::unchecked("gateway-owner")
}),
execute_response
);
}
#[test]
fn adding_gateway_with_existing_unchanged_owner() {
let mut deps = test_helpers::init_contract();
test_helpers::add_gateway(
"gateway-owner",
test_helpers::good_gateway_bond(),
deps.as_mut(),
);
let info = mock_info("gateway-owner", &test_helpers::good_gateway_bond());
let (msg, _) = test_helpers::valid_bond_gateway_msg("gateway-owner");
let res = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(Err(ContractError::AlreadyOwnsGateway), res);
}
#[test]
fn gateway_remove() {
let mut deps = test_helpers::init_contract();
// try unbond when no nodes exist yet
let info = mock_info("anyone", &[]);
let msg = ExecuteMsg::UnbondGateway {};
let result = execute(deps.as_mut(), mock_env(), info, msg);
// we're told that there is no node for our address
assert_eq!(
result,
Err(ContractError::NoAssociatedGatewayBond {
owner: Addr::unchecked("anyone")
})
);
// let's add a node owned by bob
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", &[]);
let msg = ExecuteMsg::UnbondGateway {};
let result = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(
result,
Err(ContractError::NoAssociatedGatewayBond {
owner: Addr::unchecked("fred")
})
);
// bob's node is still there
let nodes = test_helpers::get_gateways(&mut deps);
assert_eq!(1, nodes.len());
let first_node = &nodes[0];
assert_eq!(&Addr::unchecked("bob"), first_node.owner());
// add a node owned by fred
let fred_identity =
test_helpers::add_gateway("fred", test_helpers::good_gateway_bond(), deps.as_mut());
// let's make sure we now have 2 nodes:
assert_eq!(2, test_helpers::get_gateways(&mut deps).len());
// unbond fred's node
let info = mock_info("fred", &[]);
let msg = ExecuteMsg::UnbondGateway {};
let remove_fred = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
// we should see log messages come back showing an unbond message
let expected_attributes = vec![
attr("action", "unbond"),
attr("address", "fred"),
attr(
"gateway_bond",
format!(
"amount: {} {}, owner: fred, identity: {}",
INITIAL_GATEWAY_PLEDGE, DENOM, fred_identity
),
),
];
// we should see a funds transfer from the contract back to fred
let expected_message = BankMsg::Send {
to_address: String::from(info.sender),
amount: test_helpers::good_gateway_bond(),
};
// 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:
let gateway_bonds = test_helpers::get_gateways(&mut deps);
assert_eq!(1, gateway_bonds.len());
assert_eq!(&Addr::unchecked("bob"), gateway_bonds[0].owner());
}
#[test]
fn removing_gateway_clears_ownership() {
let mut deps = test_helpers::init_contract();
let info = mock_info("gateway-owner", &test_helpers::good_gateway_bond());
let (bond_msg, identity) = test_helpers::valid_bond_gateway_msg("gateway-owner");
execute(deps.as_mut(), mock_env(), info, bond_msg.clone()).unwrap();
assert_eq!(
&identity,
storage::gateways()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("gateway-owner"))
.unwrap()
.unwrap()
.1
.identity()
);
let info = mock_info("gateway-owner", &[]);
let msg = ExecuteMsg::UnbondGateway {};
assert!(execute(deps.as_mut(), mock_env(), info, msg).is_ok());
assert!(storage::gateways()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("gateway-owner"))
.unwrap()
.is_none());
// and since it's removed, it can be reclaimed
let info = mock_info("gateway-owner", &test_helpers::good_gateway_bond());
assert!(execute(deps.as_mut(), mock_env(), info, bond_msg).is_ok());
assert_eq!(
&identity,
storage::gateways()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("gateway-owner"))
.unwrap()
.unwrap()
.1
.identity()
);
}
#[test]
fn validating_gateway_bond() {
// you must send SOME funds
let result = validate_gateway_pledge(Vec::new(), INITIAL_GATEWAY_PLEDGE);
assert_eq!(result, Err(ContractError::NoBondFound));
// you must send at least 100 coins...
let mut bond = test_helpers::good_gateway_bond();
bond[0].amount = INITIAL_GATEWAY_PLEDGE.checked_sub(Uint128::new(1)).unwrap();
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
assert_eq!(
result,
Err(ContractError::InsufficientGatewayBond {
received: Into::<u128>::into(INITIAL_GATEWAY_PLEDGE) - 1,
minimum: INITIAL_GATEWAY_PLEDGE.into(),
})
);
// more than that is still fine
let mut bond = test_helpers::good_gateway_bond();
bond[0].amount = INITIAL_GATEWAY_PLEDGE + Uint128::new(1);
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
assert!(result.is_ok());
// it must be sent in the defined denom!
let mut bond = test_helpers::good_gateway_bond();
bond[0].denom = "baddenom".to_string();
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
assert_eq!(result, Err(ContractError::WrongDenom {}));
let mut bond = test_helpers::good_gateway_bond();
bond[0].denom = "foomp".to_string();
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
assert_eq!(result, Err(ContractError::WrongDenom {}));
}
}
+272
View File
@@ -0,0 +1,272 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::transactions::OLD_DELEGATIONS_CHUNK_SIZE;
use cosmwasm_std::{Order, StdError, StdResult};
use cosmwasm_storage::ReadonlyBucket;
use mixnet_contract::{Addr, IdentityKey, PagedAllDelegationsResponse, UnpackedDelegation};
use serde::de::DeserializeOwned;
use serde::Serialize;
// cosmwasm bucket internal value
const NAMESPACE_LENGTH: usize = 2;
// 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)> {
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))
}
// currently not used outside tests
#[cfg(test)]
// Converts the node identity and owner of a delegation into the bytes used as
// key in the delegation buckets.
pub(crate) fn identity_and_owner_to_bytes(identity: &IdentityKey, 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
}
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,
))
}
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,
}
}
}
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::queries::tests::store_n_mix_delegations;
use crate::storage::{all_mix_delegations_read, mix_delegations};
use crate::support::tests::helpers;
use cosmwasm_std::testing::mock_dependencies;
use mixnet_contract::RawDelegationData;
#[test]
fn delegations_iterator() {
let mut deps = 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 = 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 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 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 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;
mix_delegations(&mut deps.storage, &node_identity1)
.save(delegation_owner1.as_bytes(), &raw_delegation)
.unwrap();
let bucket = 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()
)
);
mix_delegations(&mut deps.storage, &node_identity2)
.save(delegation_owner2.as_bytes(), &raw_delegation)
.unwrap();
let bucket = 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()
)
);
mix_delegations(&mut deps.storage, &node_identity1).remove(delegation_owner1.as_bytes());
let bucket = 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()),
);
}
}
+7 -7
View File
@@ -2,10 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
pub mod contract;
mod delegations;
mod error;
mod gateways;
mod mixnet_contract_settings;
mod mixnodes;
mod rewards;
mod support;
pub mod error;
pub(crate) mod helpers;
pub mod queries;
pub mod state;
pub(crate) mod storage;
pub mod support;
pub mod transactions;
@@ -1,7 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod models;
pub mod queries;
pub mod storage;
pub mod transactions;
@@ -1,86 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use cosmwasm_std::{Deps, StdResult};
use mixnet_contract::{ContractStateParams, MixnetContractVersion, RewardingIntervalResponse};
pub(crate) fn query_contract_settings_params(deps: Deps) -> StdResult<ContractStateParams> {
storage::CONTRACT_STATE
.load(deps.storage)
.map(|settings| settings.params)
}
pub(crate) fn query_rewarding_interval(deps: Deps) -> StdResult<RewardingIntervalResponse> {
let state = storage::CONTRACT_STATE.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 {
// as per docs
// env! macro will expand to the value of the named environment variable at
// compile time, yielding an expression of type `&'static str`
MixnetContractVersion {
build_timestamp: env!("VERGEN_BUILD_TIMESTAMP").to_string(),
build_version: env!("VERGEN_BUILD_SEMVER").to_string(),
commit_sha: env!("VERGEN_GIT_SHA").to_string(),
commit_timestamp: env!("VERGEN_GIT_COMMIT_TIMESTAMP").to_string(),
commit_branch: env!("VERGEN_GIT_BRANCH").to_string(),
rustc_version: env!("VERGEN_RUSTC_SEMVER").to_string(),
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::mixnet_contract_settings::models::ContractState;
use crate::support::tests::test_helpers;
use cosmwasm_std::Addr;
#[test]
fn query_for_contract_settings_works() {
let mut deps = test_helpers::init_contract();
let dummy_state = ContractState {
owner: Addr::unchecked("someowner"),
rewarding_validator_address: Addr::unchecked("monitor"),
params: ContractStateParams {
minimum_mixnode_pledge: 123u128.into(),
minimum_gateway_pledge: 456u128.into(),
mixnode_rewarded_set_size: 1000,
mixnode_active_set_size: 500,
active_set_work_factor: 10,
},
rewarding_interval_starting_block: 123,
latest_rewarding_interval_nonce: 0,
rewarding_in_progress: false,
};
storage::CONTRACT_STATE
.save(deps.as_mut().storage, &dummy_state)
.unwrap();
assert_eq!(
dummy_state.params,
query_contract_settings_params(deps.as_ref()).unwrap()
)
}
#[test]
fn query_for_contract_version_works() {
// this basically means _something_ was grabbed from the environment at compilation time
let version = query_contract_version();
assert!(!version.build_timestamp.is_empty());
assert!(!version.build_version.is_empty());
assert!(!version.commit_sha.is_empty());
assert!(!version.commit_timestamp.is_empty());
assert!(!version.commit_branch.is_empty());
assert!(!version.rustc_version.is_empty());
}
}
@@ -1,60 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnet_contract_settings::models::ContractState;
use cosmwasm_std::StdResult;
use cosmwasm_std::Storage;
use cw_storage_plus::Item;
use mixnet_contract::Layer;
use mixnet_contract::LayerDistribution;
pub(crate) const CONTRACT_STATE: Item<ContractState> = Item::new("config");
pub(crate) const LAYERS: Item<LayerDistribution> = Item::new("layers");
pub fn increment_layer_count(storage: &mut dyn Storage, layer: Layer) -> StdResult<()> {
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<()> {
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(|_| ())
}
@@ -1,110 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::error::ContractError;
use cosmwasm_std::DepsMut;
use cosmwasm_std::MessageInfo;
use cosmwasm_std::Response;
use mixnet_contract::ContractStateParams;
pub(crate) fn try_update_contract_settings(
deps: DepsMut,
info: MessageInfo,
params: ContractStateParams,
) -> Result<Response, ContractError> {
let mut state = storage::CONTRACT_STATE.load(deps.storage)?;
// check if this is executed by the owner, if not reject the transaction
if info.sender != state.owner {
return Err(ContractError::Unauthorized);
}
if params.mixnode_rewarded_set_size == 0 {
return Err(ContractError::ZeroRewardedSet);
}
if params.mixnode_active_set_size == 0 {
return Err(ContractError::ZeroActiveSet);
}
// note: rewarded_set = active_set + idle_set
// hence rewarded set must always be bigger than (or equal to) the active set
if params.mixnode_rewarded_set_size < params.mixnode_active_set_size {
return Err(ContractError::InvalidActiveSetSize);
}
state.params = params;
storage::CONTRACT_STATE.save(deps.storage, &state)?;
Ok(Response::default())
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::contract::{INITIAL_GATEWAY_PLEDGE, INITIAL_MIXNODE_PLEDGE};
use crate::error::ContractError;
use crate::mixnet_contract_settings::transactions::try_update_contract_settings;
use crate::support::tests::test_helpers;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::Response;
use mixnet_contract::ContractStateParams;
#[test]
fn updating_contract_settings() {
let mut deps = test_helpers::init_contract();
let new_params = ContractStateParams {
minimum_mixnode_pledge: INITIAL_MIXNODE_PLEDGE,
minimum_gateway_pledge: INITIAL_GATEWAY_PLEDGE,
mixnode_rewarded_set_size: 100,
mixnode_active_set_size: 50,
active_set_work_factor: 10,
};
// sanity check to ensure new_params are different than the default ones
assert_ne!(
new_params,
storage::CONTRACT_STATE
.load(deps.as_ref().storage)
.unwrap()
.params
);
// cannot be updated from non-owner account
let info = mock_info("not-the-creator", &[]);
let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone());
assert_eq!(res, Err(ContractError::Unauthorized));
// but works fine from the creator account
let info = mock_info("creator", &[]);
let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone());
assert_eq!(res, Ok(Response::default()));
// and the state is actually updated
let current_state = storage::CONTRACT_STATE.load(deps.as_ref().storage).unwrap();
assert_eq!(current_state.params, new_params);
// error is thrown if rewarded set is smaller than the active set
let info = mock_info("creator", &[]);
let mut new_params = current_state.params.clone();
new_params.mixnode_rewarded_set_size = new_params.mixnode_active_set_size - 1;
let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone());
assert_eq!(Err(ContractError::InvalidActiveSetSize), res);
// error is thrown for 0 size rewarded set
let info = mock_info("creator", &[]);
let mut new_params = current_state.params.clone();
new_params.mixnode_rewarded_set_size = 0;
let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone());
assert_eq!(Err(ContractError::ZeroRewardedSet), res);
// error is thrown for 0 size active set
let info = mock_info("creator", &[]);
let mut new_params = current_state.params.clone();
new_params.mixnode_active_set_size = 0;
let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone());
assert_eq!(Err(ContractError::ZeroActiveSet), res);
}
}
@@ -1,228 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
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,
start_after: Option<IdentityKey>,
limit: Option<u32>,
) -> StdResult<PagedMixnodeResponse> {
let limit = limit
.unwrap_or(storage::BOND_PAGE_DEFAULT_LIMIT)
.min(storage::BOND_PAGE_MAX_LIMIT) as usize;
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.load(deps.storage, stored_bond.identity());
total_delegation
.map(|total_delegation| stored_bond.attach_delegation(total_delegation))
})
})
.collect::<StdResult<StdResult<Vec<MixNodeBond>>>>()??;
let start_next_after = nodes.last().map(|node| node.identity().clone());
Ok(PagedMixnodeResponse::new(nodes, limit, start_next_after))
}
pub fn query_owns_mixnode(deps: Deps, address: String) -> StdResult<MixOwnershipResponse> {
let validated_addr = deps.api.addr_validate(&address)?;
let stored_bond = storage::mixnodes()
.idx
.owner
.item(deps.storage, validated_addr.clone())?
.map(|record| record.1);
let mixnode = match stored_bond {
None => None,
Some(bond) => {
let total_delegation =
storage::TOTAL_DELEGATION.may_load(deps.storage, bond.identity())?;
Some(bond.attach_delegation(total_delegation.unwrap_or_default()))
}
};
Ok(MixOwnershipResponse {
address: validated_addr,
mixnode,
})
}
#[cfg(test)]
pub(crate) mod tests {
use super::storage;
use super::*;
use crate::contract::execute;
use crate::mixnodes::storage::BOND_PAGE_DEFAULT_LIMIT;
use crate::support::tests::test_helpers;
use cosmwasm_std::testing::{mock_env, mock_info};
#[test]
fn mixnodes_empty_on_init() {
let deps = test_helpers::init_contract();
let response = query_mixnodes_paged(deps.as_ref(), None, Option::from(2)).unwrap();
assert_eq!(0, response.nodes.len());
}
#[test]
fn mixnodes_paged_retrieval_obeys_limits() {
let mut deps = test_helpers::init_contract();
let limit = 2;
for n in 0..1000 {
let key = format!("bond{}", n);
test_helpers::add_mixnode(&key, test_helpers::good_mixnode_bond(), deps.as_mut());
}
let page1 = query_mixnodes_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.nodes.len() as u32);
}
#[test]
fn mixnodes_paged_retrieval_has_default_limit() {
let mut deps = test_helpers::init_contract();
for n in 0..1000 {
let key = format!("bond{}", n);
test_helpers::add_mixnode(&key, test_helpers::good_mixnode_bond(), deps.as_mut());
}
// query without explicitly setting a limit
let page1 = query_mixnodes_paged(deps.as_ref(), None, None).unwrap();
assert_eq!(BOND_PAGE_DEFAULT_LIMIT, page1.nodes.len() as u32);
}
#[test]
fn mixnodes_paged_retrieval_has_max_limit() {
let mut deps = test_helpers::init_contract();
for n in 0..1000 {
let key = format!("bond{}", n);
test_helpers::add_mixnode(&key, test_helpers::good_mixnode_bond(), deps.as_mut());
}
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000;
let page1 = query_mixnodes_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap();
// we default to a decent sized upper bound instead
let expected_limit = storage::BOND_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.nodes.len() as u32);
}
#[test]
fn pagination_works() {
// prepare 4 messages and identities that are sorted by the generated identities
// (because we query them in an ascended manner)
let mut exec_data = (0..4)
.map(|i| {
let sender = format!("nym-addr{}", i);
let (msg, identity) = test_helpers::valid_bond_mixnode_msg(&sender);
(msg, (sender, identity))
})
.collect::<Vec<_>>();
exec_data.sort_by(|(_, (_, id1)), (_, (_, id2))| id1.cmp(id2));
let (messages, sender_identities): (Vec<_>, Vec<_>) = exec_data.into_iter().unzip();
let mut deps = test_helpers::init_contract();
let info = mock_info(
&sender_identities[0].0.clone(),
&test_helpers::good_mixnode_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[0].clone()).unwrap();
let per_page = 2;
let page1 = query_mixnodes_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.nodes.len());
// save another
let info = mock_info(
&sender_identities[1].0.clone(),
&test_helpers::good_mixnode_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[1].clone()).unwrap();
// page1 should have 2 results on it
let page1 = query_mixnodes_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.nodes.len());
let info = mock_info(
&sender_identities[2].0.clone(),
&test_helpers::good_mixnode_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[2].clone()).unwrap();
// page1 still has 2 results
let page1 = query_mixnodes_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 = page1.start_next_after.unwrap();
let page2 = query_mixnodes_paged(
deps.as_ref(),
Option::from(start_after.clone()),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.nodes.len());
// save another one
let info = mock_info(
&sender_identities[3].0.clone(),
&test_helpers::good_mixnode_bond(),
);
execute(deps.as_mut(), mock_env(), info, messages[3].clone()).unwrap();
let page2 = query_mixnodes_paged(
deps.as_ref(),
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.nodes.len());
}
#[test]
fn query_for_mixnode_owner_works() {
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(), "fred".to_string()).unwrap();
assert!(res.mixnode.is_none());
// mixnode was added to "bob", "fred" still does not own one
test_helpers::add_mixnode("bob", test_helpers::good_mixnode_bond(), deps.as_mut());
let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.mixnode.is_none());
// "fred" now owns a mixnode!
test_helpers::add_mixnode("fred", test_helpers::good_mixnode_bond(), deps.as_mut());
let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.mixnode.is_some());
// but after unbonding it, he doesn't own one anymore
crate::mixnodes::transactions::try_remove_mixnode(deps.as_mut(), mock_info("fred", &[]))
.unwrap();
let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.mixnode.is_none());
}
}
@@ -1,10 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use cosmwasm_std::{Deps, StdResult};
use mixnet_contract::LayerDistribution;
pub(crate) fn query_layer_distribution(deps: Deps) -> StdResult<LayerDistribution> {
mixnet_params_storage::LAYERS.load(deps.storage)
}
-7
View File
@@ -1,7 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod bonding_queries;
pub mod layer_queries;
pub mod storage;
pub mod transactions;
-200
View File
@@ -1,200 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use config::defaults::DENOM;
use cosmwasm_std::{StdResult, Storage, Uint128};
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 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
pub(crate) const BOND_PAGE_MAX_LIMIT: u32 = 75;
pub(crate) const BOND_PAGE_DEFAULT_LIMIT: u32 = 50;
pub(crate) const TOTAL_DELEGATION: Map<IdentityKeyRef, Uint128> =
Map::new(TOTAL_DELEGATION_NAMESPACE);
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 pledge_amount: Coin,
pub owner: Addr,
pub layer: Layer,
pub block_height: u64,
pub mix_node: MixNode,
pub profit_margin_percent: Option<u8>,
pub proxy: Option<Addr>,
}
impl StoredMixnodeBond {
pub(crate) fn new(
pledge_amount: Coin,
owner: Addr,
layer: Layer,
block_height: u64,
mix_node: MixNode,
profit_margin_percent: Option<u8>,
proxy: Option<Addr>,
) -> Self {
StoredMixnodeBond {
pledge_amount,
owner,
layer,
block_height,
mix_node,
profit_margin_percent,
proxy,
}
}
pub(crate) fn attach_delegation(self, total_delegation: Uint128) -> MixNodeBond {
MixNodeBond {
total_delegation: Coin {
denom: self.pledge_amount.denom.clone(),
amount: total_delegation,
},
pledge_amount: self.pledge_amount,
owner: self.owner,
layer: self.layer,
block_height: self.block_height,
mix_node: self.mix_node,
proxy: self.proxy,
}
}
pub(crate) fn identity(&self) -> &String {
&self.mix_node.identity_key
}
pub(crate) fn pledge_amount(&self) -> Coin {
self.pledge_amount.clone()
}
}
impl Display for StoredMixnodeBond {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(
f,
"amount: {}, owner: {}, identity: {}",
self.pledge_amount, self.owner, self.mix_node.identity_key
)
}
}
pub(crate) fn read_full_mixnode_bond(
storage: &dyn Storage,
mix_identity: IdentityKeyRef,
) -> StdResult<Option<MixNodeBond>> {
let stored_bond = mixnodes().may_load(storage, mix_identity)?;
match stored_bond {
None => Ok(None),
Some(stored_bond) => {
let total_delegation = TOTAL_DELEGATION.may_load(storage, mix_identity)?;
Ok(Some(MixNodeBond {
pledge_amount: stored_bond.pledge_amount,
total_delegation: Coin {
denom: DENOM.to_owned(),
amount: total_delegation.unwrap_or_default(),
},
owner: stored_bond.owner,
layer: stored_bond.layer,
block_height: stored_bond.block_height,
mix_node: stored_bond.mix_node,
proxy: stored_bond.proxy,
}))
}
}
}
#[cfg(test)]
mod tests {
use super::super::storage;
use super::*;
use crate::support::tests::test_helpers;
use config::defaults::DENOM;
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::{coin, Addr, Uint128};
use mixnet_contract::IdentityKey;
use mixnet_contract::MixNode;
#[test]
fn mixnode_single_read_retrieval() {
let mut storage = MockStorage::new();
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 = mixnodes().load(&storage, "bond1").unwrap();
let res2 = mixnodes().load(&storage, "bond2").unwrap();
assert_eq!(bond1, res1);
assert_eq!(bond2, res2);
}
#[test]
fn reading_mixnode_bond() {
let mut mock_storage = MockStorage::new();
let node_owner: Addr = Addr::unchecked("node-owner");
let node_identity: IdentityKey = "nodeidentity".into();
// produces a None if target mixnode doesn't exist
let res = storage::read_full_mixnode_bond(&mock_storage, &node_identity).unwrap();
assert!(res.is_none());
// returns appropriate value otherwise
let pledge_value = 1000000000;
let mixnode_bond = StoredMixnodeBond {
pledge_amount: coin(pledge_value, DENOM),
owner: node_owner.clone(),
layer: Layer::One,
block_height: 12_345,
mix_node: MixNode {
identity_key: node_identity.clone(),
..test_helpers::mix_node_fixture()
},
profit_margin_percent: None,
proxy: None,
};
storage::mixnodes()
.save(&mut mock_storage, &node_identity, &mixnode_bond)
.unwrap();
assert_eq!(
Uint128::new(pledge_value),
storage::read_full_mixnode_bond(&mock_storage, node_identity.as_str())
.unwrap()
.unwrap()
.pledge_amount
.amount
);
}
}
@@ -1,640 +0,0 @@
// 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::layer_queries::query_layer_distribution;
use crate::mixnodes::storage::StoredMixnodeBond;
use crate::support::helpers::{ensure_no_existing_bond, validate_node_identity_signature};
use config::defaults::DENOM;
use cosmwasm_std::{
coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128,
};
use mixnet_contract::MixNode;
use vesting_contract::messages::ExecuteMsg as VestingContractExecuteMsg;
pub fn try_add_mixnode(
deps: DepsMut,
env: Env,
info: MessageInfo,
mix_node: MixNode,
owner_signature: String,
) -> Result<Response, ContractError> {
// check if the pledge contains any funds of the appropriate denomination
let minimum_pledge = mixnet_params_storage::CONTRACT_STATE
.load(deps.storage)?
.params
.minimum_mixnode_pledge;
let pledge = validate_mixnode_pledge(info.funds, minimum_pledge)?;
_try_add_mixnode(
deps,
env,
mix_node,
pledge,
info.sender.as_str(),
owner_signature,
None,
)
}
pub fn try_add_mixnode_on_behalf(
deps: DepsMut,
env: Env,
info: MessageInfo,
mix_node: MixNode,
owner: String,
owner_signature: String,
) -> Result<Response, ContractError> {
// check if the pledge contains any funds of the appropriate denomination
let minimum_pledge = mixnet_params_storage::CONTRACT_STATE
.load(deps.storage)?
.params
.minimum_mixnode_pledge;
let pledge = validate_mixnode_pledge(info.funds, minimum_pledge)?;
let proxy = info.sender;
_try_add_mixnode(
deps,
env,
mix_node,
pledge,
&owner,
owner_signature,
Some(proxy),
)
}
fn _try_add_mixnode(
deps: DepsMut,
env: Env,
mix_node: MixNode,
pledge_amount: Coin,
owner: &str,
owner_signature: String,
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)?;
// We don't have to check lower bound as its an u8
if mix_node.profit_margin_percent > 100 {
return Err(ContractError::InvalidProfitMarginPercent(
mix_node.profit_margin_percent,
));
}
// check if somebody else has already bonded a mixnode with this identity
if let Some(existing_bond) =
storage::mixnodes().may_load(deps.storage, &mix_node.identity_key)?
{
if existing_bond.owner != owner {
return Err(ContractError::DuplicateMixnode {
owner: existing_bond.owner,
});
}
}
// check if this sender actually owns the mixnode by checking the signature
validate_node_identity_signature(
deps.as_ref(),
&owner,
owner_signature,
&mix_node.identity_key,
)?;
let layer_distribution = query_layer_distribution(deps.as_ref())?;
let layer = layer_distribution.choose_with_fewest();
let stored_bond = StoredMixnodeBond::new(
pledge_amount,
owner,
layer,
env.block.height,
mix_node,
None,
proxy,
);
// 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
let identity = stored_bond.identity();
storage::mixnodes().save(deps.storage, identity, &stored_bond)?;
// if this is a fresh mixnode - write 0 total delegation, otherwise, don't touch it since the node has just rebonded
if storage::TOTAL_DELEGATION
.may_load(deps.storage, identity)?
.is_none()
{
storage::TOTAL_DELEGATION.save(deps.storage, identity, &Uint128::zero())?;
}
mixnet_params_storage::increment_layer_count(deps.storage, stored_bond.layer)?;
Ok(Response::new())
}
pub fn try_remove_mixnode_on_behalf(
deps: DepsMut,
info: MessageInfo,
owner: String,
) -> Result<Response, ContractError> {
let proxy = info.sender;
_try_remove_mixnode(deps, &owner, Some(proxy))
}
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 mixnode_bond = match storage::mixnodes()
.idx
.owner
.item(deps.storage, owner.clone())?
{
Some(record) => record.1,
None => return Err(ContractError::NoAssociatedMixNodeBond { owner }),
};
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()),
});
}
// 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.pledge_amount()],
};
// remove the bond
storage::mixnodes().remove(deps.storage, mixnode_bond.identity())?;
// decrement layer count
mixnet_params_storage::decrement_layer_count(deps.storage, mixnode_bond.layer)?;
let mut response = Response::new()
.add_message(return_tokens)
.add_attribute("action", "unbond")
.add_attribute("mixnode_bond", mixnode_bond.to_string());
if let Some(proxy) = &proxy {
let msg = VestingContractExecuteMsg::TrackUnbondMixnode {
owner: owner.as_str().to_string(),
amount: mixnode_bond.pledge_amount,
};
let track_unbond_message = wasm_execute(proxy, &msg, coins(0, DENOM))?;
response = response.add_message(track_unbond_message);
}
Ok(response)
}
fn validate_mixnode_pledge(
mut pledge: Vec<Coin>,
minimum_pledge: Uint128,
) -> Result<Coin, ContractError> {
// check if anything was put as bond
if pledge.is_empty() {
return Err(ContractError::NoBondFound);
}
if pledge.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
// check that the denomination is correct
if pledge[0].denom != DENOM {
return Err(ContractError::WrongDenom {});
}
// check that we have at least MIXNODE_BOND coins in our pledge
if pledge[0].amount < minimum_pledge {
return Err(ContractError::InsufficientMixNodeBond {
received: pledge[0].amount.into(),
minimum: minimum_pledge.into(),
});
}
Ok(pledge.pop().unwrap())
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::contract::{execute, query, INITIAL_MIXNODE_PLEDGE};
use crate::error::ContractError;
use crate::mixnodes::transactions::validate_mixnode_pledge;
use crate::support::tests::test_helpers;
use config::defaults::DENOM;
use cosmwasm_std::attr;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{coins, BankMsg, Response};
use cosmwasm_std::{from_binary, Addr, Uint128};
use mixnet_contract::Layer;
use mixnet_contract::MixNode;
use mixnet_contract::{ExecuteMsg, LayerDistribution, PagedMixnodeResponse, QueryMsg};
#[test]
fn mixnode_add() {
let mut deps = test_helpers::init_contract();
// if we don't send enough funds
let insufficient_bond = Into::<u128>::into(INITIAL_MIXNODE_PLEDGE) - 1;
let info = mock_info("anyone", &coins(insufficient_bond, DENOM));
let (msg, _) = test_helpers::valid_bond_mixnode_msg("anyone");
// we are informed that we didn't send enough funds
let result = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(
result,
Err(ContractError::InsufficientMixNodeBond {
received: insufficient_bond,
minimum: INITIAL_MIXNODE_PLEDGE.into(),
})
);
// no mixnode was inserted into the topology
let res = query(
deps.as_ref(),
mock_env(),
QueryMsg::GetMixNodes {
start_after: None,
limit: Option::from(2),
},
)
.unwrap();
let page: PagedMixnodeResponse = from_binary(&res).unwrap();
assert_eq!(0, page.nodes.len());
// if we send enough funds
let info = mock_info("anyone", &test_helpers::good_mixnode_bond());
let (msg, identity) = test_helpers::valid_bond_mixnode_msg("anyone");
// we get back a message telling us everything was OK
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert!(execute_response.is_ok());
// we can query topology and the new node is there
let query_response = query(
deps.as_ref(),
mock_env(),
QueryMsg::GetMixNodes {
start_after: None,
limit: Option::from(2),
},
)
.unwrap();
let page: PagedMixnodeResponse = from_binary(&query_response).unwrap();
assert_eq!(1, page.nodes.len());
assert_eq!(
&MixNode {
identity_key: identity,
..test_helpers::mix_node_fixture()
},
page.nodes[0].mix_node()
);
// if there was already a mixnode bonded by particular user
let info = mock_info("foomper", &test_helpers::good_mixnode_bond());
let (msg, _) = test_helpers::valid_bond_mixnode_msg("foomper");
execute(deps.as_mut(), mock_env(), info, msg).unwrap();
let info = mock_info("foomper", &test_helpers::good_mixnode_bond());
let (msg, _) = test_helpers::valid_bond_mixnode_msg("foomper");
// it fails
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(Err(ContractError::AlreadyOwnsMixnode), execute_response);
// bonding fails if the user already owns a gateway
test_helpers::add_gateway(
"gateway-owner",
test_helpers::good_gateway_bond(),
deps.as_mut(),
);
let info = mock_info("gateway-owner", &test_helpers::good_mixnode_bond());
let (msg, _) = test_helpers::valid_bond_mixnode_msg("gateway-owner");
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(execute_response, Err(ContractError::AlreadyOwnsGateway));
// but after he unbonds it, it's all fine again
let info = mock_info("gateway-owner", &[]);
let msg = ExecuteMsg::UnbondGateway {};
execute(deps.as_mut(), mock_env(), info, msg).unwrap();
let info = mock_info("gateway-owner", &test_helpers::good_mixnode_bond());
let (msg, _) = test_helpers::valid_bond_mixnode_msg("gateway-owner");
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert!(execute_response.is_ok());
// adding another node from another account, but with the same IP, should fail (or we would have a weird state). Is that right? Think about this, not sure yet.
// if we attempt to register a second node from the same address, should we get an error? It would probably be polite.
}
#[test]
fn adding_mixnode_without_existing_owner() {
let mut deps = test_helpers::init_contract();
let info = mock_info("mix-owner", &test_helpers::good_mixnode_bond());
// before the execution the node had no associated owner
assert!(storage::mixnodes()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("mix-owner"))
.unwrap()
.is_none());
let (msg, identity) = test_helpers::valid_bond_mixnode_msg("mix-owner");
// it's all fine, owner is saved
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert!(execute_response.is_ok());
assert_eq!(
&identity,
storage::mixnodes()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("mix-owner"))
.unwrap()
.unwrap()
.1
.identity()
);
}
#[test]
fn adding_mixnode_with_existing_owner() {
let mut deps = test_helpers::init_contract();
let identity = test_helpers::add_mixnode(
"mix-owner",
test_helpers::good_mixnode_bond(),
deps.as_mut(),
);
// request fails giving the existing owner address in the message
let info = mock_info("mix-owner-pretender", &test_helpers::good_mixnode_bond());
let msg = ExecuteMsg::BondMixnode {
mix_node: MixNode {
identity_key: identity,
..test_helpers::mix_node_fixture()
},
owner_signature: "foomp".to_string(),
};
let execute_response = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(
Err(ContractError::DuplicateMixnode {
owner: Addr::unchecked("mix-owner")
}),
execute_response
);
}
#[test]
fn adding_mixnode_with_existing_unchanged_owner() {
let mut deps = test_helpers::init_contract();
test_helpers::add_mixnode(
"mix-owner",
test_helpers::good_mixnode_bond(),
deps.as_mut(),
);
let info = mock_info("mix-owner", &test_helpers::good_mixnode_bond());
let (msg, _) = test_helpers::valid_bond_mixnode_msg("mix-owner");
let res = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(Err(ContractError::AlreadyOwnsMixnode), res);
}
#[test]
fn adding_mixnode_updates_layer_distribution() {
let mut deps = test_helpers::init_contract();
assert_eq!(
LayerDistribution::default(),
mixnet_params_storage::LAYERS.load(&deps.storage).unwrap(),
);
test_helpers::add_mixnode("mix1", test_helpers::good_mixnode_bond(), deps.as_mut());
assert_eq!(
LayerDistribution {
layer1: 1,
..Default::default()
},
mixnet_params_storage::LAYERS.load(&deps.storage).unwrap()
);
}
#[test]
fn mixnode_remove() {
let mut deps = test_helpers::init_contract();
// try un-registering when no nodes exist yet
let info = mock_info("anyone", &[]);
let msg = ExecuteMsg::UnbondMixnode {};
let result = execute(deps.as_mut(), mock_env(), info, msg);
// we're told that there is no node for our address
assert_eq!(
result,
Err(ContractError::NoAssociatedMixNodeBond {
owner: Addr::unchecked("anyone")
})
);
// let's add a node owned by bob
test_helpers::add_mixnode("bob", test_helpers::good_mixnode_bond(), deps.as_mut());
// attempt to un-register fred's node, which doesn't exist
let info = mock_info("fred", &[]);
let msg = ExecuteMsg::UnbondMixnode {};
let result = execute(deps.as_mut(), mock_env(), info, msg);
assert_eq!(
result,
Err(ContractError::NoAssociatedMixNodeBond {
owner: Addr::unchecked("fred")
})
);
// bob's node is still there
let nodes = test_helpers::get_mix_nodes(&mut deps);
assert_eq!(1, nodes.len());
assert_eq!("bob", nodes[0].owner().clone());
// add a node owned by fred
let fred_identity =
test_helpers::add_mixnode("fred", test_helpers::good_mixnode_bond(), deps.as_mut());
// let's make sure we now have 2 nodes:
assert_eq!(2, test_helpers::get_mix_nodes(&mut deps).len());
// un-register fred's node
let info = mock_info("fred", &[]);
let msg = ExecuteMsg::UnbondMixnode {};
let remove_fred = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
// we should see log messages come back showing an unbond message
let expected_attributes = vec![
attr("action", "unbond"),
attr(
"mixnode_bond",
format!(
"amount: {}{}, owner: fred, identity: {}",
INITIAL_MIXNODE_PLEDGE, DENOM, fred_identity
),
),
];
// we should see a funds transfer from the contract back to fred
let expected_message = BankMsg::Send {
to_address: String::from(info.sender),
amount: test_helpers::good_mixnode_bond(),
};
// 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:
let mix_node_bonds = test_helpers::get_mix_nodes(&mut deps);
assert_eq!(1, mix_node_bonds.len());
assert_eq!(&Addr::unchecked("bob"), mix_node_bonds[0].owner());
}
#[test]
fn removing_mixnode_clears_ownership() {
let mut deps = test_helpers::init_contract();
let info = mock_info("mix-owner", &test_helpers::good_mixnode_bond());
let (bond_msg, identity) = test_helpers::valid_bond_mixnode_msg("mix-owner");
execute(deps.as_mut(), mock_env(), info, bond_msg.clone()).unwrap();
assert_eq!(
&identity,
storage::mixnodes()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("mix-owner"))
.unwrap()
.unwrap()
.1
.identity()
);
let info = mock_info("mix-owner", &[]);
let msg = ExecuteMsg::UnbondMixnode {};
assert!(execute(deps.as_mut(), mock_env(), info, msg).is_ok());
assert!(storage::mixnodes()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("mix-owner"))
.unwrap()
.is_none());
// and since it's removed, it can be reclaimed
let info = mock_info("mix-owner", &test_helpers::good_mixnode_bond());
assert!(execute(deps.as_mut(), mock_env(), info, bond_msg).is_ok());
assert_eq!(
&identity,
storage::mixnodes()
.idx
.owner
.item(deps.as_ref().storage, Addr::unchecked("mix-owner"))
.unwrap()
.unwrap()
.1
.identity()
);
}
#[test]
fn validating_mixnode_bond() {
// you must send SOME funds
let result = validate_mixnode_pledge(Vec::new(), INITIAL_MIXNODE_PLEDGE);
assert_eq!(result, Err(ContractError::NoBondFound));
// you must send at least 100 coins...
let mut bond = test_helpers::good_mixnode_bond();
bond[0].amount = INITIAL_MIXNODE_PLEDGE.checked_sub(Uint128::new(1)).unwrap();
let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE);
assert_eq!(
result,
Err(ContractError::InsufficientMixNodeBond {
received: Into::<u128>::into(INITIAL_MIXNODE_PLEDGE) - 1,
minimum: INITIAL_MIXNODE_PLEDGE.into(),
})
);
// more than that is still fine
let mut bond = test_helpers::good_mixnode_bond();
bond[0].amount = INITIAL_MIXNODE_PLEDGE + Uint128::new(1);
let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE);
assert!(result.is_ok());
// it must be sent in the defined denom!
let mut bond = test_helpers::good_mixnode_bond();
bond[0].denom = "baddenom".to_string();
let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE);
assert_eq!(result, Err(ContractError::WrongDenom {}));
let mut bond = test_helpers::good_mixnode_bond();
bond[0].denom = "foomp".to_string();
let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE);
assert_eq!(result, Err(ContractError::WrongDenom {}));
}
#[test]
fn choose_layer_mix_node() {
let mut deps = test_helpers::init_contract();
let alice_identity =
test_helpers::add_mixnode("alice", test_helpers::good_mixnode_bond(), deps.as_mut());
let bob_identity =
test_helpers::add_mixnode("bob", test_helpers::good_mixnode_bond(), deps.as_mut());
let bonded_mix_nodes = test_helpers::get_mix_nodes(&mut deps);
let alice_node = bonded_mix_nodes
.iter()
.find(|m| m.owner == "alice")
.cloned()
.unwrap();
let bob_node = bonded_mix_nodes
.iter()
.find(|m| m.owner == "bob")
.cloned()
.unwrap();
assert_eq!(alice_node.mix_node.identity_key, alice_identity);
assert_eq!(alice_node.layer, Layer::One);
assert_eq!(bob_node.mix_node.identity_key, bob_identity);
assert_eq!(bob_node.layer, mixnet_contract::Layer::Two);
}
}
File diff suppressed because it is too large Load Diff
-83
View File
@@ -1,83 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::error::ContractError;
use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::{Addr, Storage, Uint128};
use mixnet_contract::mixnode::DelegatorRewardParams;
use mixnet_contract::{
IdentityKey, IdentityKeyRef, PendingDelegatorRewarding, RewardingResult, RewardingStatus,
};
pub(crate) fn update_post_rewarding_storage(
storage: &mut dyn Storage,
mix_identity: IdentityKeyRef,
operator_reward: Uint128,
delegators_reward: Uint128,
) -> Result<(), ContractError> {
if operator_reward == Uint128::zero() && delegators_reward == Uint128::zero() {
return Ok(());
}
// update pledge
if operator_reward > Uint128::zero() {
mixnodes_storage::mixnodes().update(storage, mix_identity, |current_bond| {
match current_bond {
None => Err(ContractError::MixNodeBondNotFound {
identity: mix_identity.to_string(),
}),
Some(mut mixnode_bond) => {
mixnode_bond.pledge_amount.amount += operator_reward;
Ok(mixnode_bond)
}
}
})?;
}
// update total_delegation
if delegators_reward > Uint128::zero() {
mixnodes_storage::TOTAL_DELEGATION.update(storage, mix_identity, |current_total| {
match current_total {
None => Err(ContractError::MixNodeBondNotFound {
identity: mix_identity.to_string(),
}),
Some(current_total) => Ok(current_total + delegators_reward),
}
})?;
}
// update reward pool
storage::decr_reward_pool(storage, operator_reward + delegators_reward)?;
Ok(())
}
pub(crate) fn update_rewarding_status(
storage: &mut dyn Storage,
rewarding_interval_nonce: u32,
mix_identity: IdentityKey,
rewarding_results: RewardingResult,
next_start: Option<Addr>,
delegators_rewarding_params: DelegatorRewardParams,
) -> Result<(), ContractError> {
if let Some(next_start) = next_start {
storage::REWARDING_STATUS.save(
storage,
(rewarding_interval_nonce.into(), mix_identity),
&RewardingStatus::PendingNextDelegatorPage(PendingDelegatorRewarding {
running_results: rewarding_results,
next_start,
rewarding_params: delegators_rewarding_params,
}),
)?;
} else {
storage::REWARDING_STATUS.save(
storage,
(rewarding_interval_nonce.into(), mix_identity),
&RewardingStatus::Complete(rewarding_results),
)?;
}
Ok(())
}
-7
View File
@@ -1,7 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod helpers;
pub mod queries;
pub mod storage;
pub mod transactions;
-270
View File
@@ -1,270 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use cosmwasm_std::Uint128;
use cosmwasm_std::{Deps, StdResult};
use mixnet_contract::{IdentityKey, MixnodeRewardingStatusResponse};
pub(crate) fn query_reward_pool(deps: Deps) -> StdResult<Uint128> {
storage::REWARD_POOL.load(deps.storage)
}
pub(crate) fn query_circulating_supply(deps: Deps) -> StdResult<Uint128> {
storage::circulating_supply(deps.storage)
}
pub(crate) fn query_rewarding_status(
deps: Deps,
mix_identity: IdentityKey,
rewarding_interval_nonce: u32,
) -> StdResult<MixnodeRewardingStatusResponse> {
let status = storage::REWARDING_STATUS.may_load(
deps.storage,
(rewarding_interval_nonce.into(), mix_identity),
)?;
Ok(MixnodeRewardingStatusResponse { status })
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::support::tests::test_helpers;
use cosmwasm_std::testing::{mock_env, mock_info};
#[cfg(test)]
mod querying_for_rewarding_status {
use super::storage;
use super::*;
use crate::delegations::transactions::try_delegate_to_mixnode;
use crate::rewards::transactions::{
try_begin_mixnode_rewarding, try_finish_mixnode_rewarding, try_reward_mixnode,
try_reward_next_mixnode_delegators,
};
use config::defaults::DENOM;
use cosmwasm_std::{coin, Addr};
use mixnet_contract::{RewardingResult, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT};
#[test]
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_STATE
.load(deps.as_mut().storage)
.unwrap();
let rewarding_validator_address = current_state.rewarding_validator_address;
let node_identity =
test_helpers::add_mixnode("bob", test_helpers::good_mixnode_bond(), deps.as_mut());
assert!(
query_rewarding_status(deps.as_ref(), node_identity.clone(), 1)
.unwrap()
.status
.is_none()
);
// node was rewarded but for different epoch
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 1).unwrap();
try_reward_mixnode(
deps.as_mut(),
env.clone(),
info.clone(),
node_identity.clone(),
test_helpers::node_rewarding_params_fixture(100),
1,
)
.unwrap();
try_finish_mixnode_rewarding(deps.as_mut(), info.clone(), 1).unwrap();
assert!(query_rewarding_status(deps.as_ref(), node_identity, 2)
.unwrap()
.status
.is_none());
}
#[test]
fn returns_complete_status_for_fully_rewarded_node() {
// with single page
let mut deps = test_helpers::init_contract();
let mut env = mock_env();
let current_state = mixnet_params_storage::CONTRACT_STATE
.load(deps.as_mut().storage)
.unwrap();
let rewarding_validator_address = current_state.rewarding_validator_address;
let node_owner: Addr = Addr::unchecked("bob");
let node_identity = test_helpers::add_mixnode(
node_owner.as_str(),
test_helpers::good_mixnode_bond(),
deps.as_mut(),
);
env.block.height += storage::MINIMUM_BLOCK_AGE_FOR_REWARDING;
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 1).unwrap();
try_reward_mixnode(
deps.as_mut(),
env.clone(),
info.clone(),
node_identity.clone(),
test_helpers::node_rewarding_params_fixture(100),
1,
)
.unwrap();
try_finish_mixnode_rewarding(deps.as_mut(), info.clone(), 1).unwrap();
let res = query_rewarding_status(deps.as_ref(), node_identity, 1).unwrap();
assert!(matches!(res.status, Some(RewardingStatus::Complete(..))));
match res.status.unwrap() {
RewardingStatus::Complete(result) => {
assert_ne!(
RewardingResult::default().operator_reward,
result.operator_reward
);
assert_eq!(
RewardingResult::default().total_delegator_reward,
result.total_delegator_reward
);
}
_ => unreachable!(),
}
// with multiple pages
let node_owner: Addr = Addr::unchecked("alice");
let node_identity = test_helpers::add_mixnode(
node_owner.as_str(),
test_helpers::good_mixnode_bond(),
deps.as_mut(),
);
for i in 0..MIXNODE_DELEGATORS_PAGE_LIMIT + 123 {
try_delegate_to_mixnode(
deps.as_mut(),
env.clone(),
mock_info(
&*format!("delegator{:04}", i),
&vec![coin(200_000000, DENOM)],
),
node_identity.clone(),
)
.unwrap();
}
env.block.height += storage::MINIMUM_BLOCK_AGE_FOR_REWARDING;
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 2).unwrap();
try_reward_mixnode(
deps.as_mut(),
env.clone(),
info.clone(),
node_identity.clone(),
test_helpers::node_rewarding_params_fixture(100),
2,
)
.unwrap();
// rewards all pending
try_reward_next_mixnode_delegators(
deps.as_mut(),
info.clone(),
node_identity.to_string(),
2,
)
.unwrap();
let res = query_rewarding_status(deps.as_ref(), node_identity, 2).unwrap();
assert!(matches!(res.status, Some(RewardingStatus::Complete(..))));
match res.status.unwrap() {
RewardingStatus::Complete(result) => {
assert_ne!(
RewardingResult::default().operator_reward,
result.operator_reward
);
assert_ne!(
RewardingResult::default().total_delegator_reward,
result.total_delegator_reward
);
}
_ => unreachable!(),
}
}
#[test]
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_STATE
.load(deps.as_mut().storage)
.unwrap();
let rewarding_validator_address = current_state.rewarding_validator_address;
let node_owner: Addr = Addr::unchecked("bob");
let node_identity = test_helpers::add_mixnode(
node_owner.as_str(),
test_helpers::good_mixnode_bond(),
deps.as_mut(),
);
for i in 0..MIXNODE_DELEGATORS_PAGE_LIMIT + 123 {
try_delegate_to_mixnode(
deps.as_mut(),
env.clone(),
mock_info(
&*format!("delegator{:04}", i),
&vec![coin(200_000000, DENOM)],
),
node_identity.clone(),
)
.unwrap();
}
env.block.height += storage::MINIMUM_BLOCK_AGE_FOR_REWARDING;
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 1).unwrap();
try_reward_mixnode(
deps.as_mut(),
env.clone(),
info,
node_identity.clone(),
test_helpers::node_rewarding_params_fixture(100),
1,
)
.unwrap();
let res = query_rewarding_status(deps.as_ref(), node_identity, 1).unwrap();
assert!(matches!(
res.status,
Some(RewardingStatus::PendingNextDelegatorPage(..))
));
match res.status.unwrap() {
RewardingStatus::PendingNextDelegatorPage(result) => {
assert_ne!(
RewardingResult::default().operator_reward,
result.running_results.operator_reward
);
assert_ne!(
RewardingResult::default().total_delegator_reward,
result.running_results.total_delegator_reward
);
assert_eq!(
&*format!("delegator{:04}", MIXNODE_DELEGATORS_PAGE_LIMIT),
result.next_start
);
}
_ => unreachable!(),
}
}
}
}
-49
View File
@@ -1,49 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::ContractError;
use config::defaults::TOTAL_SUPPLY;
use cosmwasm_std::{StdResult, Storage, Uint128};
use cw_storage_plus::{Item, Map, U32Key};
use mixnet_contract::{IdentityKey, RewardingStatus};
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;
// approximately 30min (assuming 5s per block)
pub(crate) const MAX_REWARDING_DURATION_IN_BLOCKS: u64 = 360;
#[allow(dead_code)]
pub fn incr_reward_pool(
amount: Uint128,
storage: &mut dyn Storage,
) -> Result<Uint128, ContractError> {
REWARD_POOL.update(storage, |mut current_pool| {
current_pool += amount;
Ok(current_pool)
})
}
pub fn decr_reward_pool(
storage: &mut dyn Storage,
amount: Uint128,
) -> Result<Uint128, ContractError> {
REWARD_POOL.update(storage, |current_pool| {
let stake = current_pool
.checked_sub(amount)
.map_err(|_| ContractError::OutOfFunds {
to_remove: amount.u128(),
reward_pool: current_pool.u128(),
})?;
Ok(stake)
})
}
pub fn circulating_supply(storage: &dyn Storage) -> StdResult<Uint128> {
let reward_pool = REWARD_POOL.load(storage)?;
Ok(Uint128::new(TOTAL_SUPPLY) - reward_pool)
}
@@ -2,15 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Addr;
use mixnet_contract::ContractStateParams;
use mixnet_contract::StateParams;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ContractState {
pub struct State {
pub owner: Addr, // only the owner account can update state
pub rewarding_validator_address: Addr,
pub params: ContractStateParams,
pub params: StateParams,
// keep track of the changes to the current rewarding interval,
// i.e. at which block has the latest rewarding occurred
+593
View File
@@ -0,0 +1,593 @@
// 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 crate::state::State;
use config::defaults::{DENOM, TOTAL_SUPPLY};
use cosmwasm_std::{Coin, StdResult, Storage, Uint128};
use cosmwasm_storage::{
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
Singleton,
};
use mixnet_contract::{
Addr, GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNode, MixNodeBond,
RawDelegationData, RewardingStatus, StateParams,
};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
// storage prefixes
// all of them must be unique and presumably not be a prefix of 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
// singletons
const CONFIG_KEY: &[u8] = b"config";
const LAYER_DISTRIBUTION_KEY: &[u8] = b"layers";
const REWARD_POOL_PREFIX: &[u8] = b"pool";
// buckets
const PREFIX_MIXNODES: &[u8] = b"mn";
const PREFIX_MIXNODES_OWNERS: &[u8] = b"mo";
const PREFIX_GATEWAYS: &[u8] = b"gt";
const PREFIX_GATEWAYS_OWNERS: &[u8] = b"go";
const PREFIX_MIX_DELEGATION: &[u8] = b"md";
const PREFIX_REVERSE_MIX_DELEGATION: &[u8] = b"dm";
const PREFIX_REWARDED_MIXNODES: &[u8] = b"rm";
const PREFIX_TOTAL_DELEGATION: &[u8] = b"td";
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub(crate) struct StoredMixnodeBond {
pub bond_amount: Coin,
pub owner: Addr,
pub layer: Layer,
pub block_height: u64,
pub mix_node: MixNode,
pub profit_margin_percent: Option<u8>,
}
impl StoredMixnodeBond {
pub(crate) fn new(
bond_amount: Coin,
owner: Addr,
layer: Layer,
block_height: u64,
mix_node: MixNode,
profit_margin_percent: Option<u8>,
) -> Self {
StoredMixnodeBond {
bond_amount,
owner,
layer,
block_height,
mix_node,
profit_margin_percent,
}
}
pub(crate) fn attach_delegation(self, total_delegation: Uint128) -> MixNodeBond {
MixNodeBond {
total_delegation: Coin {
denom: self.bond_amount.denom.clone(),
amount: total_delegation,
},
bond_amount: self.bond_amount,
owner: self.owner,
layer: self.layer,
block_height: self.block_height,
mix_node: self.mix_node,
profit_margin_percent: self.profit_margin_percent,
}
}
pub(crate) fn identity(&self) -> &String {
&self.mix_node.identity_key
}
pub(crate) fn bond_amount(&self) -> Coin {
self.bond_amount.clone()
}
}
impl Display for StoredMixnodeBond {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(
f,
"amount: {}, owner: {}, identity: {}",
self.bond_amount, self.owner, self.mix_node.identity_key
)
}
}
// Contract-level stuff
// TODO Unify bucket and mixnode storage functions
pub fn config(storage: &mut dyn Storage) -> Singleton<State> {
singleton(storage, CONFIG_KEY)
}
pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton<State> {
singleton_read(storage, CONFIG_KEY)
}
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)
}
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 {
to_remove: amount.u128(),
reward_pool: reward_pool_value(storage).u128(),
})
}
};
mut_reward_pool(storage).save(&stake)?;
Ok(stake)
}
pub fn circulating_supply(storage: &dyn Storage) -> Uint128 {
let reward_pool = reward_pool_value(storage).u128();
Uint128(TOTAL_SUPPLY - reward_pool)
}
pub(crate) fn read_state_params(storage: &dyn Storage) -> StateParams {
// 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.
config_read(storage).load().unwrap().params
}
pub fn layer_distribution(storage: &mut dyn Storage) -> Singleton<LayerDistribution> {
singleton(storage, LAYER_DISTRIBUTION_KEY)
}
pub fn layer_distribution_read(storage: &dyn Storage) -> ReadonlySingleton<LayerDistribution> {
singleton_read(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 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)
}
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)
}
// 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,
],
)
}
// helpers
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())?;
match stored_bond {
None => Ok(None),
Some(stored_bond) => {
let total_delegation =
total_delegation_read(storage).may_load(mix_identity.as_bytes())?;
Ok(Some(MixNodeBond {
bond_amount: stored_bond.bond_amount,
total_delegation: Coin {
denom: DENOM.to_owned(),
amount: total_delegation.unwrap_or_default(),
},
owner: stored_bond.owner,
layer: stored_bond.layer,
block_height: stored_bond.block_height,
mix_node: stored_bond.mix_node,
profit_margin_percent: stored_bond.profit_margin_percent,
}))
}
}
}
// currently not used outside tests
#[cfg(test)]
pub(crate) fn read_mixnode_bond_amount(
storage: &dyn Storage,
identity: &[u8],
) -> StdResult<cosmwasm_std::Uint128> {
let bucket = mixnodes_read(storage);
let node = bucket.load(identity)?;
Ok(node.bond_amount.amount)
}
// Gateway-related stuff
pub fn gateways(storage: &mut dyn Storage) -> Bucket<GatewayBond> {
bucket(storage, PREFIX_GATEWAYS)
}
pub fn gateways_read(storage: &dyn Storage) -> ReadonlyBucket<GatewayBond> {
bucket_read(storage, PREFIX_GATEWAYS)
}
// 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)
}
// 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()])
}
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()])
}
// currently not used outside tests
#[cfg(test)]
pub(crate) fn read_gateway_bond(
storage: &dyn Storage,
identity: &[u8],
) -> StdResult<cosmwasm_std::Uint128> {
let bucket = gateways_read(storage);
let node = bucket.load(identity)?;
Ok(node.bond_amount.amount)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::helpers::identity_and_owner_to_bytes;
use crate::support::tests::helpers::{
gateway_bond_fixture, gateway_fixture, mix_node_fixture, stored_mixnode_bond_fixture,
};
use config::defaults::DENOM;
use cosmwasm_std::testing::{mock_dependencies, MockStorage};
use cosmwasm_std::{coin, Addr, Uint128};
use mixnet_contract::{Gateway, MixNode};
#[test]
fn mixnode_single_read_retrieval() {
let mut storage = MockStorage::new();
let bond1 = stored_mixnode_bond_fixture();
let bond2 = stored_mixnode_bond_fixture();
mixnodes(&mut storage).save(b"bond1", &bond1).unwrap();
mixnodes(&mut storage).save(b"bond2", &bond2).unwrap();
let res1 = mixnodes_read(&storage).load(b"bond1").unwrap();
let res2 = mixnodes_read(&storage).load(b"bond2").unwrap();
assert_eq!(bond1, res1);
assert_eq!(bond2, res2);
}
#[test]
fn gateway_single_read_retrieval() {
let mut storage = MockStorage::new();
let bond1 = gateway_bond_fixture();
let bond2 = gateway_bond_fixture();
gateways(&mut storage).save(b"bond1", &bond1).unwrap();
gateways(&mut storage).save(b"bond2", &bond2).unwrap();
let res1 = gateways_read(&storage).load(b"bond1").unwrap();
let res2 = gateways_read(&storage).load(b"bond2").unwrap();
assert_eq!(bond1, res1);
assert_eq!(bond2, res2);
}
#[test]
fn reading_mixnode_bond() {
let mut storage = MockStorage::new();
let node_owner: Addr = Addr::unchecked("node-owner");
let node_identity: IdentityKey = "nodeidentity".into();
// produces an error if target mixnode doesn't exist
let res = read_mixnode_bond_amount(&storage, node_owner.as_bytes());
assert!(res.is_err());
// returns appropriate value otherwise
let bond_value = 1000;
let mixnode_bond = StoredMixnodeBond {
bond_amount: coin(bond_value, DENOM),
owner: node_owner.clone(),
layer: Layer::One,
block_height: 12_345,
mix_node: MixNode {
identity_key: node_identity.clone(),
..mix_node_fixture()
},
profit_margin_percent: Some(10),
};
mixnodes(&mut storage)
.save(node_identity.as_bytes(), &mixnode_bond)
.unwrap();
assert_eq!(
Uint128(bond_value),
read_mixnode_bond_amount(&storage, node_identity.as_bytes()).unwrap()
);
}
#[test]
#[test]
fn reading_gateway_bond() {
let mut storage = MockStorage::new();
let node_owner: Addr = Addr::unchecked("node-owner");
let node_identity: IdentityKey = "nodeidentity".into();
// produces an error if target gateway doesn't exist
let res = read_gateway_bond(&storage, node_owner.as_bytes());
assert!(res.is_err());
// returns appropriate value otherwise
let bond_value = 1000;
let gateway_bond = GatewayBond {
bond_amount: coin(bond_value, DENOM),
owner: node_owner.clone(),
block_height: 12_345,
gateway: Gateway {
identity_key: node_identity.clone(),
..gateway_fixture()
},
};
gateways(&mut storage)
.save(node_identity.as_bytes(), &gateway_bond)
.unwrap();
assert_eq!(
Uint128(bond_value),
read_gateway_bond(&storage, node_identity.as_bytes()).unwrap()
);
}
#[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);
mix_delegations(&mut deps.storage, &node_identity1)
.save(delegation_owner1.as_bytes(), &raw_delegation1)
.unwrap();
mix_delegations(&mut deps.storage, &node_identity2)
.save(delegation_owner2.as_bytes(), &raw_delegation2)
.unwrap();
let res1 = all_mix_delegations_read::<RawDelegationData>(&deps.storage)
.load(&*identity_and_owner_to_bytes(
&node_identity1,
&delegation_owner1,
))
.unwrap();
let res2 = all_mix_delegations_read::<RawDelegationData>(&deps.storage)
.load(&*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::helpers;
#[test]
fn reverse_mix_delegation_exists() {
let mut deps = helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
let delegation_owner = Addr::unchecked("bar");
reverse_mix_delegations(&mut deps.storage, &delegation_owner)
.save(node_identity.as_bytes(), &())
.unwrap();
assert!(
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 = 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!(
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
reverse_mix_delegations(&mut deps.storage, &delegation_owner1)
.save(node_identity2.as_bytes(), &())
.unwrap();
assert!(
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
reverse_mix_delegations(&mut deps.storage, &delegation_owner2)
.save(node_identity1.as_bytes(), &())
.unwrap();
assert!(
reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1)
.may_load(node_identity1.as_bytes())
.unwrap()
.is_none()
);
}
}
}
-224
View File
@@ -1,224 +0,0 @@
// 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, Deps, Storage};
use mixnet_contract::IdentityKeyRef;
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(())
}
pub(crate) fn validate_node_identity_signature(
deps: Deps,
owner: &Addr,
signature: String,
identity: IdentityKeyRef,
) -> Result<(), ContractError> {
let owner_bytes = owner.as_bytes();
let mut identity_bytes = [0u8; 32];
let mut signature_bytes = [0u8; 64];
let identity_used_bytes = bs58::decode(identity)
.into(&mut identity_bytes)
.map_err(|err| ContractError::MalformedEd25519IdentityKey(err.to_string()))?;
let signature_used_bytes = bs58::decode(signature)
.into(&mut signature_bytes)
.map_err(|err| ContractError::MalformedEd25519Signature(err.to_string()))?;
if identity_used_bytes != 32 {
return Err(ContractError::MalformedEd25519IdentityKey(
"Too few bytes provided".into(),
));
}
if signature_used_bytes != 64 {
return Err(ContractError::MalformedEd25519Signature(
"Too few bytes provided".into(),
));
}
let res = deps
.api
.ed25519_verify(owner_bytes, &signature_bytes, &identity_bytes)
.map_err(cosmwasm_std::StdError::verification_err)?;
if !res {
Err(ContractError::InvalidEd25519Signature)
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::mock_dependencies;
use crypto::asymmetric::identity;
use rand_chacha::rand_core::SeedableRng;
#[test]
fn validating_node_signature() {
let deps = mock_dependencies();
// since those tests are NOT compiled to wasm, we can use rng-related dependency
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let short_bs58 = "2SfEgZ4aQUr3HSwqE";
let long_bs58 = "g34PyULki9fc3FqKobj5wdVNCaWAt1M9oZowyyMFfWSCejxg7wt574piZVjqjFEN2UXsgZ56KTkKf3jnWD4DJ2Gsf7KXQAvptFfcYRrZHTjMVo3NXcBSNm3wDBKZWZURzp4Fixv";
let address1 = Addr::unchecked("some-dummy-address1");
let address2 = Addr::unchecked("some-dummy-address2");
let keypair1 = identity::KeyPair::new(&mut rng);
let keypair2 = identity::KeyPair::new(&mut rng);
let sig_addr1_key1 = keypair1
.private_key()
.sign(address1.as_bytes())
.to_base58_string();
let sig_addr2_key1 = keypair1
.private_key()
.sign(address2.as_bytes())
.to_base58_string();
let sig_addr1_key2 = keypair2
.private_key()
.sign(address1.as_bytes())
.to_base58_string();
assert_eq!(
Err(ContractError::MalformedEd25519IdentityKey(
"buffer provided to decode base58 encoded string into was too small".into()
)),
validate_node_identity_signature(
deps.as_ref(),
&address1,
sig_addr1_key1.clone(),
long_bs58,
)
);
assert_eq!(
Err(ContractError::MalformedEd25519Signature(
"buffer provided to decode base58 encoded string into was too small".into()
)),
validate_node_identity_signature(
deps.as_ref(),
&address1,
long_bs58.into(),
&keypair1.public_key().to_base58_string(),
)
);
assert_eq!(
Err(ContractError::MalformedEd25519IdentityKey(
"Too few bytes provided".into()
)),
validate_node_identity_signature(
deps.as_ref(),
&address1,
sig_addr1_key1.clone(),
short_bs58,
)
);
assert_eq!(
Err(ContractError::MalformedEd25519Signature(
"Too few bytes provided".into()
)),
validate_node_identity_signature(
deps.as_ref(),
&address1,
short_bs58.into(),
&keypair1.public_key().to_base58_string(),
)
);
assert_eq!(
Err(ContractError::InvalidEd25519Signature),
validate_node_identity_signature(
deps.as_ref(),
&address1,
sig_addr1_key1.clone(),
&keypair2.public_key().to_base58_string(),
)
);
assert_eq!(
Err(ContractError::InvalidEd25519Signature),
validate_node_identity_signature(
deps.as_ref(),
&address1,
sig_addr2_key1,
&keypair1.public_key().to_base58_string(),
)
);
assert_eq!(
Err(ContractError::InvalidEd25519Signature),
validate_node_identity_signature(
deps.as_ref(),
&address2,
sig_addr1_key1.clone(),
&keypair1.public_key().to_base58_string(),
)
);
assert_eq!(
Err(ContractError::InvalidEd25519Signature),
validate_node_identity_signature(
deps.as_ref(),
&address1,
sig_addr1_key2,
&keypair1.public_key().to_base58_string(),
)
);
assert!(validate_node_identity_signature(
deps.as_ref(),
&address1,
sig_addr1_key1,
&keypair1.public_key().to_base58_string(),
)
.is_ok());
}
}
+1 -5
View File
@@ -1,5 +1 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod helpers;
pub(crate) mod tests;
pub mod tests;
+60 -142
View File
@@ -1,78 +1,42 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
pub mod test_helpers {
pub mod helpers {
use super::*;
use crate::contract::{instantiate, INITIAL_MIXNODE_PLEDGE};
use crate::contract::{instantiate, INITIAL_MIXNODE_BOND};
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::storage as mixnodes_storage;
use crate::mixnodes::storage::StoredMixnodeBond;
use crate::mixnodes::transactions::try_add_mixnode;
use crate::storage::StoredMixnodeBond;
use crate::transactions::{try_add_gateway, 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;
use cosmwasm_std::testing::MockApi;
use cosmwasm_std::testing::MockQuerier;
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::Addr;
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::{
Delegation, ExecuteMsg, Gateway, GatewayBond, IdentityKey, IdentityKeyRef, InstantiateMsg,
Layer, MixNode, MixNodeBond, PagedGatewayResponse, PagedMixnodeResponse, QueryMsg,
Gateway, GatewayBond, InstantiateMsg, Layer, MixNode, MixNodeBond, PagedGatewayResponse,
PagedMixnodeResponse, QueryMsg, RawDelegationData,
};
use rand::thread_rng;
pub(crate) fn valid_bond_mixnode_msg(sender: &str) -> (ExecuteMsg, IdentityKey) {
let keypair = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng());
let owner_signature = keypair
.private_key()
.sign(sender.as_bytes())
.to_base58_string();
let identity_key = keypair.public_key().to_base58_string();
(
ExecuteMsg::BondMixnode {
mix_node: MixNode {
identity_key: identity_key.clone(),
..test_helpers::mix_node_fixture()
},
owner_signature,
},
identity_key,
)
}
pub fn add_mixnode(sender: &str, stake: Vec<Coin>, deps: DepsMut) -> String {
let keypair = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng());
let owner_signature = keypair
.private_key()
.sign(sender.as_bytes())
.to_base58_string();
let info = mock_info(sender, &stake);
let key = keypair.public_key().to_base58_string();
let key = format!("{}mixnode", sender);
try_add_mixnode(
deps,
mock_env(),
info,
MixNode {
identity_key: key.clone(),
..test_helpers::mix_node_fixture()
..helpers::mix_node_fixture()
},
owner_signature,
)
.unwrap();
key
@@ -95,44 +59,21 @@ pub mod test_helpers {
page.nodes
}
pub(crate) fn valid_bond_gateway_msg(sender: &str) -> (ExecuteMsg, IdentityKey) {
let keypair = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng());
let owner_signature = keypair
.private_key()
.sign(sender.as_bytes())
.to_base58_string();
let identity_key = keypair.public_key().to_base58_string();
(
ExecuteMsg::BondGateway {
gateway: Gateway {
identity_key: identity_key.clone(),
..test_helpers::gateway_fixture()
},
owner_signature,
},
identity_key,
)
}
pub fn add_gateway(sender: &str, stake: Vec<Coin>, deps: DepsMut) -> String {
let keypair = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng());
let owner_signature = keypair
.private_key()
.sign(sender.as_bytes())
.to_base58_string();
pub fn add_gateway(
sender: &str,
stake: Vec<Coin>,
deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier>,
) -> String {
let info = mock_info(sender, &stake);
let key = keypair.public_key().to_base58_string();
let key = format!("{}gateway", sender);
try_add_gateway(
deps,
deps.as_mut(),
mock_env(),
info,
Gateway {
identity_key: key.clone(),
..test_helpers::gateway_fixture()
..helpers::gateway_fixture()
},
owner_signature,
)
.unwrap();
key
@@ -156,14 +97,12 @@ pub mod test_helpers {
}
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
rewarding_validator_address: config::defaults::REWARDING_VALIDATOR_ADDRESS.to_string(),
};
let mut deps = mock_dependencies(&[]);
let msg = InstantiateMsg {};
let env = mock_env();
let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
deps
return deps;
}
pub fn mix_node_fixture() -> MixNode {
@@ -175,21 +114,36 @@ pub mod test_helpers {
sphinx_key: "sphinx".to_string(),
identity_key: "identity".to_string(),
version: "0.10.0".to_string(),
profit_margin_percent: 10,
}
}
pub(crate) fn stored_mixnode_bond_fixture(owner: &str) -> mixnodes_storage::StoredMixnodeBond {
StoredMixnodeBond::new(
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(owner),
Addr::unchecked("foo"),
Layer::One,
12_345,
MixNode {
identity_key: format!("id-{}", owner),
..mix_node_fixture()
},
mix_node,
None,
)
}
pub(crate) fn stored_mixnode_bond_fixture() -> StoredMixnodeBond {
StoredMixnodeBond::new(
coin(50, DENOM),
Addr::unchecked("foo"),
Layer::One,
12_345,
mix_node_fixture(),
None,
)
}
@@ -200,24 +154,28 @@ 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(owner: &str) -> GatewayBond {
pub fn gateway_bond_fixture() -> GatewayBond {
let gateway = Gateway {
identity_key: format!("id-{}", owner),
..gateway_fixture()
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(),
};
GatewayBond::new(
coin(50, DENOM),
Addr::unchecked(owner),
12_345,
gateway,
None,
)
GatewayBond::new(coin(50, DENOM), Addr::unchecked("foo"), 12_345, gateway)
}
pub fn raw_delegation_fixture(amount: u128) -> RawDelegationData {
RawDelegationData::new(Uint128(amount), 42)
}
pub fn query_contract_balance(
@@ -231,14 +189,14 @@ pub mod test_helpers {
pub fn good_mixnode_bond() -> Vec<Coin> {
vec![Coin {
denom: DENOM.to_string(),
amount: INITIAL_MIXNODE_PLEDGE,
amount: INITIAL_MIXNODE_BOND,
}]
}
pub fn good_gateway_bond() -> Vec<Coin> {
vec![Coin {
denom: DENOM.to_string(),
amount: INITIAL_MIXNODE_PLEDGE,
amount: INITIAL_MIXNODE_BOND,
}]
}
@@ -247,50 +205,10 @@ pub mod test_helpers {
NodeRewardParams::new(
(INITIAL_REWARD_POOL / 100) * EPOCH_REWARD_PERCENT as u128,
50 as u128,
25 as u128,
0,
TOTAL_SUPPLY - INITIAL_REWARD_POOL,
uptime,
DEFAULT_SYBIL_RESISTANCE_PERCENT,
true,
10,
)
}
// currently not used outside tests
pub(crate) fn read_mixnode_pledge_amount(
storage: &dyn Storage,
identity: IdentityKeyRef,
) -> StdResult<cosmwasm_std::Uint128> {
let node = mixnodes_storage::mixnodes().load(storage, identity)?;
Ok(node.pledge_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()
}
}
File diff suppressed because it is too large Load Diff
-908
View File
@@ -1,908 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "az"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "block-buffer"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
"block-padding",
"byte-tools",
"byteorder",
"generic-array 0.12.4",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "block-padding"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
dependencies = [
"byte-tools",
]
[[package]]
name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "bytemuck"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "config"
version = "0.1.0"
dependencies = [
"handlebars",
"humantime-serde",
"network-defaults",
"serde",
"toml",
"url",
]
[[package]]
name = "const-oid"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
[[package]]
name = "cosmwasm-crypto"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
"k256",
"rand_core 0.5.1",
"thiserror",
]
[[package]]
name = "cosmwasm-derive"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"syn",
]
[[package]]
name = "cosmwasm-std"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"base64",
"cosmwasm-crypto",
"cosmwasm-derive",
"schemars",
"serde",
"serde-json-wasm",
"thiserror",
"uint",
]
[[package]]
name = "cosmwasm-storage"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"cosmwasm-std",
"serde",
]
[[package]]
name = "cpufeatures"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
dependencies = [
"libc",
]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-bigint"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03"
dependencies = [
"generic-array 0.14.4",
"rand_core 0.6.3",
"subtle",
"zeroize",
]
[[package]]
name = "crypto-mac"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
dependencies = [
"generic-array 0.14.4",
"subtle",
]
[[package]]
name = "curve25519-dalek"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
dependencies = [
"byteorder",
"digest 0.9.0",
"rand_core 0.5.1",
"subtle",
"zeroize",
]
[[package]]
name = "der"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2"
dependencies = [
"const-oid",
]
[[package]]
name = "digest"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array 0.12.4",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "dyn-clone"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
[[package]]
name = "ecdsa"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372"
dependencies = [
"der",
"elliptic-curve",
"hmac",
"signature",
]
[[package]]
name = "ed25519-zebra"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409"
dependencies = [
"curve25519-dalek",
"hex",
"rand_core 0.5.1",
"serde",
"sha2",
"thiserror",
]
[[package]]
name = "elliptic-curve"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b"
dependencies = [
"crypto-bigint",
"ff",
"generic-array 0.14.4",
"group",
"pkcs8",
"rand_core 0.6.3",
"subtle",
"zeroize",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "ff"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f"
dependencies = [
"rand_core 0.6.3",
"subtle",
]
[[package]]
name = "fixed"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d333a26ec13a023c6dff4b7584de4d323cfee2e508f5dd2bbee6669e4f7efdf"
dependencies = [
"az",
"bytemuck",
"half",
"typenum",
]
[[package]]
name = "form_urlencoded"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
dependencies = [
"matches",
"percent-encoding",
]
[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "group"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
dependencies = [
"ff",
"rand_core 0.6.3",
"subtle",
]
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "handlebars"
version = "3.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3"
dependencies = [
"log",
"pest",
"pest_derive",
"quick-error",
"serde",
"serde_json",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hex-literal"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
[[package]]
name = "hmac"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
dependencies = [
"crypto-mac",
"digest 0.9.0",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "humantime-serde"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058"
dependencies = [
"humantime",
"serde",
]
[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "k256"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea"
dependencies = [
"cfg-if",
"ecdsa",
"elliptic-curve",
"sha2",
]
[[package]]
name = "libc"
version = "0.2.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "mixnet-contract"
version = "0.1.0"
dependencies = [
"az",
"cosmwasm-std",
"fixed",
"log",
"network-defaults",
"schemars",
"serde",
"serde_repr",
"thiserror",
]
[[package]]
name = "network-defaults"
version = "0.1.0"
dependencies = [
"hex-literal",
"serde",
"time",
"url",
]
[[package]]
name = "opaque-debug"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
dependencies = [
"maplit",
"pest",
"sha-1",
]
[[package]]
name = "pkcs8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447"
dependencies = [
"der",
"spki",
]
[[package]]
name = "proc-macro2"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.16",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom 0.2.3",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "schemars"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271ac0c667b8229adf70f0f957697c96fafd7486ab7481e15dc5e45e3e6a4368"
dependencies = [
"dyn-clone",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ebda811090b257411540779860bc09bf321bc587f58d2c5864309d1566214e7"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn",
]
[[package]]
name = "serde"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-json-wasm"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50eef3672ec8fa45f3457fd423ba131117786784a895548021976117c1ded449"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_derive_internals"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sha-1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"opaque-debug 0.2.3",
]
[[package]]
name = "sha2"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
dependencies = [
"block-buffer 0.9.0",
"cfg-if",
"cpufeatures",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
name = "signature"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4"
dependencies = [
"digest 0.9.0",
"rand_core 0.6.3",
]
[[package]]
name = "spki"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32"
dependencies = [
"der",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad"
dependencies = [
"libc",
"time-macros",
]
[[package]]
name = "time-macros"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
[[package]]
name = "tinyvec"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]]
name = "typenum"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "uint"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f"
dependencies = [
"byteorder",
"crunchy",
"hex",
"static_assertions",
]
[[package]]
name = "unicode-bidi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
[[package]]
name = "unicode-normalization"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "url"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
dependencies = [
"form_urlencoded",
"idna",
"matches",
"percent-encoding",
]
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "vesting-contracts"
version = "0.1.0"
dependencies = [
"config",
"cosmwasm-std",
"cosmwasm-storage",
"mixnet-contract",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "zeroize"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
-30
View File
@@ -1,30 +0,0 @@
[package]
name = "vesting-contract"
version = "0.1.0"
authors = ["Drazen Urch <durch@users.noreply.github.com>"]
edition = "2018"
exclude = [
# Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication.
"contract.wasm",
"hash.txt",
]
[lib]
crate-type = ["cdylib", "rlib"]
[features]
# for more explicit tests, cargo test --features=backtraces
backtraces = ["cosmwasm-std/backtraces"]
[dependencies]
mixnet-contract = { path = "../../common/mixnet-contract" }
config = { path = "../../common/config" }
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
cosmwasm-std = { version = "1.0.0-beta2", features = ["iterator"]}
cw-storage-plus = { version = "0.10.3", features = ["iterator"] }
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
-2
View File
@@ -1,2 +0,0 @@
wasm:
RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
-11
View File
@@ -1,11 +0,0 @@
## Nym vesting contract
1. Initial vesting tokens are deposited and assigned to existing addresses via `CreatePeriodicVestingAccount`
2. Admin account can then delegate vested and unvested tokens to mixnodes on behalf of vesting accounts
3. Vesting accounts can withdraw vested and undelegated (spendable) coins to their addresses
### Vesting coin delegation flow
![vesting-coin-delegation](images/vesting-coin-delegation.png)
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 KiB

-403
View File
@@ -1,403 +0,0 @@
use crate::errors::ContractError;
use crate::messages::{ExecuteMsg, InitMsg, QueryMsg};
use crate::storage::account_from_address;
use crate::traits::{
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, 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::{Gateway, 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,
owner_signature,
} => try_bond_mixnode(mix_node, owner_signature, info, env, deps),
ExecuteMsg::UnbondMixnode {} => try_unbond_mixnode(info, deps),
ExecuteMsg::TrackUnbondMixnode { owner, amount } => {
try_track_unbond_mixnode(&owner, amount, info, deps)
}
ExecuteMsg::BondGateway {
gateway,
owner_signature,
} => try_bond_gateway(gateway, owner_signature, info, env, deps),
ExecuteMsg::UnbondGateway {} => try_unbond_gateway(info, deps),
ExecuteMsg::TrackUnbondGateway { owner, amount } => {
try_track_unbond_gateway(&owner, amount, info, deps)
}
}
}
pub fn try_bond_gateway(
gateway: Gateway,
owner_signature: String,
info: MessageInfo,
env: Env,
deps: DepsMut,
) -> Result<Response, ContractError> {
let pledge = validate_funds(&info.funds)?;
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_bond_gateway(gateway, owner_signature, pledge, &env, deps.storage)
}
pub fn try_unbond_gateway(info: MessageInfo, deps: DepsMut) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_unbond_gateway(deps.storage)
}
pub fn try_track_unbond_gateway(
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.try_track_unbond_gateway(amount, deps.storage)?;
Ok(Response::default())
}
pub fn try_bond_mixnode(
mix_node: MixNode,
owner_signature: String,
info: MessageInfo,
env: Env,
deps: DepsMut,
) -> Result<Response, ContractError> {
let pledge = validate_funds(&info.funds)?;
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_bond_mixnode(mix_node, owner_signature, pledge, &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_mixnode(
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.try_track_unbond_mixnode(amount, deps.storage)?;
Ok(Response::default())
}
pub fn try_withdraw_vested_coins(
amount: Coin,
env: Env,
info: MessageInfo,
deps: DepsMut,
) -> Result<Response, ContractError> {
let address = info.sender.clone();
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
let spendable_coins = account.spendable_coins(None, &env, deps.storage)?;
if amount.amount <= spendable_coins.amount {
let new_balance = account
.load_balance(deps.storage)?
.u128()
.saturating_sub(amount.amount.u128());
account.save_balance(Uint128::new(new_balance), deps.storage)?;
let send_tokens = BankMsg::Send {
to_address: address.as_str().to_string(),
amount: vec![amount],
};
Ok(Response::new()
.add_attribute("action", "whitdraw")
.add_message(send_tokens))
} else {
Err(ContractError::InsufficientSpendable(
address.as_str().to_string(),
spendable_coins.amount.u128(),
))
}
}
fn try_track_undelegation(
address: &str,
mix_identity: IdentityKey,
amount: Coin,
info: MessageInfo,
deps: DepsMut,
) -> Result<Response, ContractError> {
if info.sender != DEFAULT_MIXNET_CONTRACT_ADDRESS {
return Err(ContractError::NotMixnetContract(info.sender));
}
let account = account_from_address(address, deps.storage, deps.api)?;
account.track_undelegation(mix_identity, amount, deps.storage)?;
Ok(Response::default())
}
fn try_delegate_to_mixnode(
mix_identity: IdentityKey,
info: MessageInfo,
env: Env,
deps: DepsMut,
) -> Result<Response, ContractError> {
let amount = validate_funds(&info.funds)?;
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_delegate_to_mixnode(mix_identity, amount, &env, deps.storage)
}
fn try_undelegate_from_mixnode(
mix_identity: IdentityKey,
info: MessageInfo,
deps: DepsMut,
) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_undelegate_from_mixnode(mix_identity, deps.storage)
}
fn try_create_periodic_vesting_account(
address: &str,
start_time: Option<u64>,
info: MessageInfo,
env: Env,
deps: DepsMut,
) -> Result<Response, ContractError> {
if info.sender != ADMIN_ADDRESS {
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
}
let coin = validate_funds(&info.funds)?;
let address = deps.api.addr_validate(address)?;
let start_time = start_time.unwrap_or_else(|| env.block.time.seconds());
let periods = populate_vesting_periods(start_time, NUM_VESTING_PERIODS);
Account::new(
address,
coin,
Timestamp::from_seconds(start_time),
periods,
deps.storage,
)?;
Ok(Response::default())
}
#[entry_point]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg {
QueryMsg::LockedCoins {
vesting_account_address,
block_time,
} => to_binary(&try_get_locked_coins(
&vesting_account_address,
block_time,
env,
deps,
)?),
QueryMsg::SpendableCoins {
vesting_account_address,
block_time,
} => to_binary(&try_get_spendable_coins(
&vesting_account_address,
block_time,
env,
deps,
)?),
QueryMsg::GetVestedCoins {
vesting_account_address,
block_time,
} => to_binary(&try_get_vested_coins(
&vesting_account_address,
block_time,
env,
deps,
)?),
QueryMsg::GetVestingCoins {
vesting_account_address,
block_time,
} => to_binary(&try_get_vesting_coins(
&vesting_account_address,
block_time,
env,
deps,
)?),
QueryMsg::GetStartTime {
vesting_account_address,
} => to_binary(&try_get_start_time(&vesting_account_address, deps)?),
QueryMsg::GetEndTime {
vesting_account_address,
} => to_binary(&try_get_end_time(&vesting_account_address, deps)?),
QueryMsg::GetOriginalVesting {
vesting_account_address,
} => to_binary(&try_get_original_vesting(&vesting_account_address, deps)?),
QueryMsg::GetDelegatedFree {
block_time,
vesting_account_address,
} => to_binary(&try_get_delegated_free(
block_time,
&vesting_account_address,
env,
deps,
)?),
QueryMsg::GetDelegatedVesting {
block_time,
vesting_account_address,
} => to_binary(&try_get_delegated_vesting(
block_time,
&vesting_account_address,
env,
deps,
)?),
};
Ok(query_res?)
}
pub fn try_get_locked_coins(
vesting_account_address: &str,
block_time: Option<Timestamp>,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
account.locked_coins(block_time, &env, deps.storage)
}
pub fn try_get_spendable_coins(
vesting_account_address: &str,
block_time: Option<Timestamp>,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
account.spendable_coins(block_time, &env, deps.storage)
}
pub fn try_get_vested_coins(
vesting_account_address: &str,
block_time: Option<Timestamp>,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
account.get_vested_coins(block_time, &env)
}
pub fn try_get_vesting_coins(
vesting_account_address: &str,
block_time: Option<Timestamp>,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
account.get_vesting_coins(block_time, &env)
}
pub fn try_get_start_time(
vesting_account_address: &str,
deps: Deps,
) -> Result<Timestamp, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
Ok(account.get_start_time())
}
pub fn try_get_end_time(
vesting_account_address: &str,
deps: Deps,
) -> Result<Timestamp, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
Ok(account.get_end_time())
}
pub fn try_get_original_vesting(
vesting_account_address: &str,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
Ok(account.get_original_vesting())
}
pub fn try_get_delegated_free(
block_time: Option<Timestamp>,
vesting_account_address: &str,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
account.get_delegated_free(block_time, &env, deps.storage)
}
pub fn try_get_delegated_vesting(
block_time: Option<Timestamp>,
vesting_account_address: &str,
env: Env,
deps: Deps,
) -> Result<Coin, ContractError> {
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
account.get_delegated_vesting(block_time, &env, deps.storage)
}
fn validate_funds(funds: &[Coin]) -> Result<Coin, ContractError> {
if funds.is_empty() || funds[0].amount.is_zero() {
return Err(ContractError::EmptyFunds);
}
if funds.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
if funds[0].denom != DENOM {
return Err(ContractError::WrongDenom(
funds[0].denom.clone(),
DENOM.to_string(),
));
}
Ok(funds[0].clone())
}
-41
View File
@@ -1,41 +0,0 @@
use cosmwasm_std::{Addr, StdError};
use mixnet_contract::IdentityKey;
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
#[error("Account does not exist - {0}")]
NoAccountForAddress(String),
#[error("Only admin can perform this action, {0} is not admin")]
NotAdmin(String),
#[error("Balance not found for existing account ({0}), this is a bug")]
NoBalanceForAddress(String),
#[error("Insufficient balance for address {0} -> {1}")]
InsufficientBalance(String, u128),
#[error("Insufficient spendable balance for address {0} -> {1}")]
InsufficientSpendable(String, u128),
#[error(
"Only delegation owner can perform delegation actions, {0} is not the delegation owner"
)]
NotDelegate(String),
#[error("Total vesting amount is inprobably low -> {0}, this is likely an error")]
ImprobableVestingAmount(u128),
#[error("Address {0} has already bonded a node")]
AlreadyBonded(String),
#[error("Recieved empty funds vector")]
EmptyFunds,
#[error("Recieved wrong denom: {0}, expected {1}")]
WrongDenom(String, String),
#[error("Recieved multiple denoms, expected 1")]
MultipleDenoms,
#[error("No delegations found for account {0}, mix_identity {1}")]
NoSuchDelegation(Addr, IdentityKey),
#[error("Only mixnet contract can perform this operation, got {0}")]
NotMixnetContract(Addr),
#[error("Calculation underflowed")]
Underflow,
#[error("No bond found for account {0}")]
NoBondFound(String),
}
-7
View File
@@ -1,7 +0,0 @@
pub mod contract;
mod errors;
pub mod messages;
mod storage;
mod support;
mod traits;
mod vesting;
-88
View File
@@ -1,88 +0,0 @@
use cosmwasm_std::{Coin, Timestamp};
use mixnet_contract::IdentityKey;
use mixnet_contract::{Gateway, 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,
owner_signature: String,
},
UnbondMixnode {},
TrackUnbondMixnode {
owner: String,
amount: Coin,
},
BondGateway {
gateway: Gateway,
owner_signature: String,
},
UnbondGateway {},
TrackUnbondGateway {
owner: String,
amount: Coin,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
LockedCoins {
vesting_account_address: String,
block_time: Option<Timestamp>,
},
SpendableCoins {
vesting_account_address: String,
block_time: Option<Timestamp>,
},
GetVestedCoins {
vesting_account_address: String,
block_time: Option<Timestamp>,
},
GetVestingCoins {
vesting_account_address: String,
block_time: Option<Timestamp>,
},
GetStartTime {
vesting_account_address: String,
},
GetEndTime {
vesting_account_address: String,
},
GetOriginalVesting {
vesting_account_address: String,
},
GetDelegatedFree {
block_time: Option<Timestamp>,
vesting_account_address: String,
},
GetDelegatedVesting {
block_time: Option<Timestamp>,
vesting_account_address: String,
},
}
-30
View File
@@ -1,30 +0,0 @@
use crate::errors::ContractError;
use crate::vesting::Account;
use cosmwasm_std::{Addr, Api, Storage};
use cw_storage_plus::Map;
const ACCOUNTS: Map<Addr, Account> = Map::new("acc");
pub fn save_account(account: &Account, storage: &mut dyn Storage) -> Result<(), ContractError> {
Ok(ACCOUNTS.save(storage, account.address(), account)?)
}
pub fn load_account(
address: &Addr,
storage: &dyn Storage,
) -> Result<Option<Account>, ContractError> {
Ok(ACCOUNTS.may_load(storage, address.to_owned())?)
}
fn validate_account(address: &Addr, storage: &dyn Storage) -> Result<Account, ContractError> {
load_account(address, storage)?
.ok_or_else(|| ContractError::NoAccountForAddress(address.as_str().to_string()))
}
pub fn account_from_address(
address: &str,
storage: &dyn Storage,
api: &dyn Api,
) -> Result<Account, ContractError> {
validate_account(&api.addr_validate(address)?, storage)
}
-1
View File
@@ -1 +0,0 @@
pub mod tests;

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