Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60dc125735 | |||
| e6259c184c | |||
| c93f3cfc4f | |||
| 87d18bbc16 | |||
| 45ed46afa0 | |||
| 9619e794b2 | |||
| 305864aa0f | |||
| 8f152d42f0 | |||
| 791d051537 | |||
| f8099cb8c8 | |||
| f1e995b076 | |||
| ed1fe22db2 | |||
| b1c45dc0f8 | |||
| 0e0151f781 | |||
| 70e148b3ed | |||
| 53138d6292 | |||
| 39650de2bd | |||
| 7b04093cc5 | |||
| a4c9a81399 | |||
| f42f76901a | |||
| 875bcb4e63 | |||
| fa62de5bc6 | |||
| 5c3b08e1cd | |||
| 745af89019 | |||
| eb0fb90127 | |||
| 527d5a5d9b | |||
| 2dfa0a9d2a | |||
| d9bfa4562e | |||
| aec0239d87 | |||
| a2324f98f8 | |||
| 001d166477 | |||
| bb14e95e61 | |||
| 5aa1c29409 | |||
| db111490b6 | |||
| fe0bb007c9 | |||
| ddad6d73db | |||
| 35c04014e5 | |||
| 138daddfed | |||
| ab3cfe79bc | |||
| 9fcf3105e0 | |||
| 51bf117007 | |||
| a04d4503b5 | |||
| 9fb44f9672 | |||
| f542c28e89 | |||
| b809eb6c5f | |||
| 51e82fe930 | |||
| 67f847e674 | |||
| d4736bac27 | |||
| 6417feaaed | |||
| 718170c651 | |||
| c10038e688 | |||
| 2f68439916 | |||
| b175480ba5 | |||
| 34c9726ac9 | |||
| 11b2a7ad3d | |||
| 18978c7599 | |||
| 8e52a70685 | |||
| 1f42ce57e3 | |||
| 231fba34bc | |||
| 0e05fc46c9 | |||
| 52777efc53 | |||
| 54bc198885 | |||
| 577c5dc1ee | |||
| 7927846390 | |||
| e41f02ad0d | |||
| e57a5b73a2 | |||
| 608de11377 | |||
| b88153a2bd | |||
| 0fd2f946dc | |||
| c5cdbd5bfe | |||
| 3c05cf29e6 | |||
| 6cb58d1f1c | |||
| 5727ac5161 | |||
| 804f254e16 | |||
| f03ce6e07b | |||
| 2631ed4d0c | |||
| 0f93dde8fc | |||
| eb93b428cf | |||
| 04b6f83d99 | |||
| f5612cc64b | |||
| d525563a46 | |||
| 560b306d32 | |||
| a4a4da1121 | |||
| d69cb47c6c | |||
| 3e93b4ffd5 | |||
| 6da660623b | |||
| a85a67199a | |||
| 09b6d37ca6 | |||
| 4029b1b830 | |||
| e075759461 | |||
| 1069032cd7 | |||
| b97b7410da | |||
| f259012faf | |||
| c00cdafc8c | |||
| 82c56351de | |||
| 5eeb55aa78 | |||
| 94357c6132 | |||
| 77c5296c39 | |||
| 6ee5b68b46 | |||
| 56fa48215b | |||
| 26b0d9555f | |||
| 61a67ac334 | |||
| c2e268a1c9 | |||
| cfd0b7868a | |||
| 69b52ae629 | |||
| c361de62a5 | |||
| 7d3d0d8874 | |||
| 39c7b131b6 | |||
| 4071a20bb5 | |||
| b4519cd77e | |||
| 35748e07c4 | |||
| 54e217ccea | |||
| c8f81d118b | |||
| a193e948e6 | |||
| cc77f6e392 | |||
| 58f02109be | |||
| 724e0f78c6 | |||
| d764b11122 | |||
| 4953b40a42 | |||
| 66e5afe485 | |||
| 9b17a1ca77 | |||
| 5ce6147839 | |||
| dd3643a1bb | |||
| 52fedd9866 |
@@ -1,4 +1,4 @@
|
||||
name: Mixnet Contract
|
||||
name: Contracts
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
with:
|
||||
inputFile: '.github/workflows/contract_matrix_includes.json'
|
||||
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
|
||||
mixnet-contract:
|
||||
contracts:
|
||||
# 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/mixnet/Cargo.toml --target wasm32-unknown-unknown
|
||||
args: --manifest-path contracts/Cargo.toml --all --target wasm32-unknown-unknown
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path contracts/mixnet/Cargo.toml
|
||||
args: --manifest-path contracts/Cargo.toml
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path contracts/mixnet/Cargo.toml -- --check
|
||||
args: --manifest-path contracts/Cargo.toml --all -- --check
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path contracts/mixnet/Cargo.toml -- -D warnings
|
||||
args: --manifest-path contracts/Cargo.toml --all -- -D warnings
|
||||
@@ -1,58 +0,0 @@
|
||||
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
|
||||
@@ -22,6 +22,8 @@ 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
-1
@@ -1,4 +1,4 @@
|
||||
name: Publish Tauri Wallet
|
||||
name: Publish Nym Wallet
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
+2
-1
@@ -34,4 +34,5 @@ contracts/mixnet/code_id
|
||||
contracts/mixnet/Justfile
|
||||
contracts/mixnet/Makefile
|
||||
validator-config
|
||||
*.patch
|
||||
*.patch
|
||||
validator-api-config.toml
|
||||
Generated
+40
-10
@@ -868,8 +868,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cosmos-sdk-proto"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edb5204c6ddc4352c74297638b5561f2929d6334866c156e5f3c75e1e1a1436a"
|
||||
source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58"
|
||||
dependencies = [
|
||||
"prost",
|
||||
"prost-types",
|
||||
@@ -879,8 +878,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cosmrs"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d31147fe89e547e74e2692e4bec35387e1ea406fe8cfb14c0ea177b58fd2a8a9"
|
||||
source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58"
|
||||
dependencies = [
|
||||
"bip32",
|
||||
"cosmos-sdk-proto",
|
||||
@@ -901,8 +899,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
@@ -913,16 +912,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
@@ -1207,6 +1208,17 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw-storage-plus"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3b8b840947313c1a1cccf056836cd79a60b4526bdcd6582995be37dc97be4ae"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.10.2"
|
||||
@@ -3562,8 +3574,10 @@ name = "nym-gateway"
|
||||
version = "0.11.0"
|
||||
dependencies = [
|
||||
"bip39",
|
||||
"bs58",
|
||||
"clap",
|
||||
"coconut-interface",
|
||||
"colored",
|
||||
"config",
|
||||
"credentials",
|
||||
"crypto",
|
||||
@@ -3585,6 +3599,7 @@ dependencies = [
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"sqlx",
|
||||
"subtle-encoding",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -3620,6 +3635,7 @@ dependencies = [
|
||||
"rocket",
|
||||
"serde",
|
||||
"serial_test",
|
||||
"subtle-encoding",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
@@ -7129,6 +7145,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"ts-rs",
|
||||
"url",
|
||||
"vesting-contract",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7168,6 +7185,19 @@ 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"
|
||||
|
||||
@@ -62,6 +62,7 @@ default-members = [
|
||||
"service-providers/network-requester",
|
||||
"mixnode",
|
||||
"validator-api",
|
||||
"explorer-api",
|
||||
]
|
||||
|
||||
exclude = ["explorer", "contracts", "tokenomics-py"]
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
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
|
||||
@@ -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 (currently in development)- a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
|
||||
* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
|
||||
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
|
||||
|
||||
@@ -3,6 +3,5 @@ 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
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import NetClient, {INetClient} from "./net-client";
|
||||
import NetClient, { INetClient } from "./net-client";
|
||||
import {
|
||||
StateParams,
|
||||
ContractSettingsParams,
|
||||
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<StateParams> {
|
||||
async getStateParams(): Promise<ContractSettingsParams> {
|
||||
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: StateParams): Promise<ExecuteResult> {
|
||||
async updateStateParams(newParams: ContractSettingsParams): Promise<ExecuteResult> {
|
||||
if (this.client instanceof NetClient) {
|
||||
return await this.client.executeContract(this.client.clientAddress, this.contractAddress, {update_state_params: newParams}, "updating contract state").catch((err) => this.handleRequestFailure(err));
|
||||
return await this.client.executeContract(this.client.clientAddress, this.contractAddress, { update_contract_settings: newParams }, "updating contract settings").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)) {
|
||||
|
||||
@@ -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, {state_params: {}});
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { contract_settings_params: {} });
|
||||
}
|
||||
|
||||
public executeContract(senderAddress: string, contractAddress: string, handleMsg: Record<string, unknown>, memo?: string, transferAmount?: readonly Coin[]): Promise<ExecuteResult> {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
MixOwnershipResponse, PagedGatewayDelegationsResponse,
|
||||
PagedGatewayResponse, PagedMixDelegationsResponse,
|
||||
PagedMixnodeResponse,
|
||||
StateParams
|
||||
ContractSettingsParams
|
||||
} from "./types";
|
||||
|
||||
export interface IQueryClient {
|
||||
@@ -28,7 +28,7 @@ export interface IQueryClient {
|
||||
|
||||
ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
|
||||
|
||||
getStateParams(contractAddress: string): Promise<StateParams>;
|
||||
getStateParams(contractAddress: string): Promise<ContractSettingsParams>;
|
||||
|
||||
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<StateParams> {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {state_params: {}});
|
||||
public getStateParams(contractAddress: string): Promise<ContractSettingsParams> {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { contract_settings_params: {} });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export type GatewayOwnershipResponse = {
|
||||
has_gateway: boolean,
|
||||
}
|
||||
|
||||
export type StateParams = {
|
||||
export type ContractSettingsParams = {
|
||||
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,6 +10,7 @@ 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"] }
|
||||
@@ -26,12 +27,13 @@ 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 = { 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 }
|
||||
prost = { version = "0.9", default-features = false, optional = true }
|
||||
flate2 = { version = "1.0.20", optional = true }
|
||||
sha2 = { version = "0.9.5", optional = true }
|
||||
itertools = { version = "0.10", optional = true }
|
||||
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true }
|
||||
cosmwasm-std = { version = "1.0.0-beta2", optional = true }
|
||||
ts-rs = {version = "5.1", optional = true}
|
||||
|
||||
[features]
|
||||
|
||||
@@ -6,13 +6,18 @@ use crate::nymd::{
|
||||
error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
|
||||
};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use mixnet_contract::StateParams;
|
||||
use mixnet_contract::ContractStateParams;
|
||||
|
||||
use crate::{validator_api, ValidatorClientError};
|
||||
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
|
||||
use mixnet_contract::{GatewayBond, MixNodeBond, MixnodeRewardingStatusResponse};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use mixnet_contract::{RawDelegationData, RewardingIntervalResponse};
|
||||
use mixnet_contract::{
|
||||
Delegation, MixnetContractVersion, MixnodeRewardingStatusResponse, RewardingIntervalResponse,
|
||||
};
|
||||
use mixnet_contract::{GatewayBond, MixNodeBond};
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
@@ -20,6 +25,7 @@ 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>,
|
||||
@@ -32,10 +38,12 @@ 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,
|
||||
@@ -62,6 +70,7 @@ 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>,
|
||||
@@ -83,11 +92,14 @@ 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,
|
||||
@@ -101,7 +113,9 @@ 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(())
|
||||
}
|
||||
@@ -113,16 +127,19 @@ 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()
|
||||
.ok_or(ValidatorClientError::NymdError(
|
||||
NymdError::NoContractAddressAvailable,
|
||||
))?,
|
||||
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()
|
||||
}),
|
||||
)?;
|
||||
|
||||
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,
|
||||
@@ -136,6 +153,7 @@ impl Client<QueryNymdClient> {
|
||||
self.nymd = NymdClient::connect(
|
||||
new_endpoint.as_ref(),
|
||||
self.mixnet_contract_address.clone().unwrap(),
|
||||
self.vesting_contract_address.clone().unwrap(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -165,11 +183,18 @@ impl<C> Client<C> {
|
||||
Ok(self.validator_api.get_gateways().await?)
|
||||
}
|
||||
|
||||
pub async fn get_state_params(&self) -> Result<StateParams, ValidatorClientError>
|
||||
pub async fn get_contract_settings(&self) -> Result<ContractStateParams, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_state_params().await?)
|
||||
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?)
|
||||
}
|
||||
|
||||
pub async fn get_current_rewarding_interval(
|
||||
@@ -300,9 +325,7 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_mixnode_delegations(
|
||||
&self,
|
||||
) -> Result<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>, ValidatorClientError>
|
||||
pub async fn get_all_network_delegations(&self) -> Result<Vec<Delegation>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
@@ -311,7 +334,7 @@ impl<C> Client<C> {
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.nymd
|
||||
.get_all_mix_delegations_paged(
|
||||
.get_all_network_delegations_paged(
|
||||
start_after.take(),
|
||||
self.mixnode_delegations_page_limit,
|
||||
)
|
||||
@@ -328,10 +351,10 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_reverse_mixnode_delegations(
|
||||
pub async fn get_all_delegator_delegations(
|
||||
&self,
|
||||
delegation_owner: &cosmrs::AccountId,
|
||||
) -> Result<Vec<mixnet_contract::IdentityKey>, ValidatorClientError>
|
||||
) -> Result<Vec<Delegation>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
@@ -340,13 +363,13 @@ impl<C> Client<C> {
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.nymd
|
||||
.get_reverse_mix_delegations_paged(
|
||||
mixnet_contract::Addr::unchecked(delegation_owner.as_ref()),
|
||||
.get_delegator_delegations_paged(
|
||||
delegation_owner.to_string(),
|
||||
start_after.take(),
|
||||
self.mixnode_delegations_page_limit,
|
||||
)
|
||||
.await?;
|
||||
delegations.append(&mut paged_response.delegated_nodes);
|
||||
delegations.append(&mut paged_response.delegations);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
@@ -358,28 +381,6 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_mixnode_delegations_of_owner(
|
||||
&self,
|
||||
delegation_owner: &cosmrs::AccountId,
|
||||
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let mut delegations = Vec::new();
|
||||
for node_identity in self
|
||||
.get_all_nymd_reverse_mixnode_delegations(delegation_owner)
|
||||
.await?
|
||||
{
|
||||
let delegation = self
|
||||
.nymd
|
||||
.get_mix_delegation(node_identity, delegation_owner)
|
||||
.await?;
|
||||
delegations.push(delegation);
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
|
||||
@@ -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,
|
||||
SequenceResponse, SimulateResponse,
|
||||
};
|
||||
use crate::nymd::error::NymdError;
|
||||
use async_trait::async_trait;
|
||||
@@ -14,15 +14,19 @@ use cosmrs::proto::cosmos::auth::v1beta1::{
|
||||
use cosmrs::proto::cosmos::bank::v1beta1::{
|
||||
QueryAllBalancesRequest, QueryAllBalancesResponse, QueryBalanceRequest, QueryBalanceResponse,
|
||||
};
|
||||
use cosmrs::proto::cosmwasm::wasm::v1beta1::*;
|
||||
use cosmrs::proto::cosmos::tx::v1beta1::{
|
||||
SimulateRequest, SimulateResponse as ProtoSimulateResponse,
|
||||
};
|
||||
use cosmrs::proto::cosmwasm::wasm::v1::*;
|
||||
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};
|
||||
use cosmrs::{tx, AccountId, Coin, Denom, Tx};
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
@@ -49,6 +53,11 @@ 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())?)
|
||||
}
|
||||
|
||||
@@ -213,7 +222,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
}
|
||||
|
||||
async fn get_codes(&self) -> Result<Vec<Code>, NymdError> {
|
||||
let path = Some("/cosmwasm.wasm.v1beta1.Query/Codes".parse().unwrap());
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/Codes".parse().unwrap());
|
||||
|
||||
let mut raw_codes = Vec::new();
|
||||
let mut pagination = None;
|
||||
@@ -240,7 +249,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
}
|
||||
|
||||
async fn get_code_details(&self, code_id: ContractCodeId) -> Result<CodeDetails, NymdError> {
|
||||
let path = Some("/cosmwasm.wasm.v1beta1.Query/Code".parse().unwrap());
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/Code".parse().unwrap());
|
||||
|
||||
let req = QueryCodeRequest { code_id };
|
||||
|
||||
@@ -255,11 +264,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
}
|
||||
}
|
||||
async fn get_contracts(&self, code_id: ContractCodeId) -> Result<Vec<AccountId>, NymdError> {
|
||||
let path = Some(
|
||||
"/cosmwasm.wasm.v1beta1.Query/ContractsByCode"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/ContractsByCode".parse().unwrap());
|
||||
|
||||
let mut raw_contracts = Vec::new();
|
||||
let mut pagination = None;
|
||||
@@ -290,7 +295,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
}
|
||||
|
||||
async fn get_contract(&self, address: &AccountId) -> Result<Contract, NymdError> {
|
||||
let path = Some("/cosmwasm.wasm.v1beta1.Query/ContractInfo".parse().unwrap());
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/ContractInfo".parse().unwrap());
|
||||
|
||||
let req = QueryContractInfoRequest {
|
||||
address: address.to_string(),
|
||||
@@ -315,11 +320,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
&self,
|
||||
address: &AccountId,
|
||||
) -> Result<Vec<ContractCodeHistoryEntry>, NymdError> {
|
||||
let path = Some(
|
||||
"/cosmwasm.wasm.v1beta1.Query/ContractHistory"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/ContractHistory".parse().unwrap());
|
||||
|
||||
let mut raw_entries = Vec::new();
|
||||
let mut pagination = None;
|
||||
@@ -353,11 +354,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
address: &AccountId,
|
||||
query_data: Vec<u8>,
|
||||
) -> Result<Vec<u8>, NymdError> {
|
||||
let path = Some(
|
||||
"/cosmwasm.wasm.v1beta1.Query/RawContractState"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/RawContractState".parse().unwrap());
|
||||
|
||||
let req = QueryRawContractStateRequest {
|
||||
address: address.to_string(),
|
||||
@@ -381,7 +378,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
let path = Some(
|
||||
"/cosmwasm.wasm.v1beta1.Query/SmartContractState"
|
||||
"/cosmwasm.wasm.v1.Query/SmartContractState"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
@@ -400,4 +397,27 @@ 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.kind == event_type)?
|
||||
.find(|event| event.ty == event_type)?
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|attr| attr.key == attribute_key)
|
||||
@@ -61,7 +61,7 @@ mod tests {
|
||||
assert_eq!(parsed.len(), 1);
|
||||
assert_eq!(parsed[0].msg_index, 0);
|
||||
assert_eq!(parsed[0].events.len(), 1);
|
||||
assert_eq!(parsed[0].events[0].kind, "message");
|
||||
assert_eq!(parsed[0].events[0].ty, "message");
|
||||
assert_eq!(parsed[0].events[0].attributes[3].key, "code_id");
|
||||
assert_eq!(parsed[0].events[0].attributes[3].value, "1");
|
||||
}
|
||||
@@ -76,12 +76,12 @@ mod tests {
|
||||
assert_eq!(parsed[2].msg_index, 2);
|
||||
|
||||
assert_eq!(parsed[0].events.len(), 1);
|
||||
assert_eq!(parsed[0].events[0].kind, "message");
|
||||
assert_eq!(parsed[0].events[0].ty, "message");
|
||||
assert_eq!(parsed[0].events[0].attributes[3].key, "code_id");
|
||||
assert_eq!(parsed[0].events[0].attributes[3].value, "9");
|
||||
|
||||
assert_eq!(parsed[2].events.len(), 1);
|
||||
assert_eq!(parsed[2].events[0].kind, "message");
|
||||
assert_eq!(parsed[2].events[0].ty, "message");
|
||||
assert_eq!(parsed[2].events[0].attributes[2].key, "signer");
|
||||
assert_eq!(
|
||||
parsed[2].events[0].attributes[2].value,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
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;
|
||||
|
||||
@@ -23,9 +24,10 @@ 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)
|
||||
signing_client::Client::connect_with_signer(endpoint, signer, gas_price)
|
||||
}
|
||||
|
||||
@@ -1,37 +1,100 @@
|
||||
// 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 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;
|
||||
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,
|
||||
];
|
||||
|
||||
#[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();
|
||||
@@ -42,14 +105,6 @@ 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()
|
||||
@@ -61,12 +116,13 @@ 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, "message", "code_id")
|
||||
let code_id = logs::find_attribute(&logs, "store_code", "code_id")
|
||||
.unwrap()
|
||||
.value
|
||||
.parse()
|
||||
@@ -80,6 +136,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
code_id,
|
||||
logs,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -111,7 +168,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),
|
||||
init_msg: serde_json::to_vec(msg)?,
|
||||
msg: serde_json::to_vec(msg)?,
|
||||
funds: options.map(|options| options.funds).unwrap_or_default(),
|
||||
}
|
||||
.to_any()
|
||||
@@ -123,12 +180,13 @@ 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, "message", "contract_address")
|
||||
let contract_address = logs::find_attribute(&logs, "instantiate", "_contract_address")
|
||||
.unwrap()
|
||||
.value
|
||||
.parse()
|
||||
@@ -138,6 +196,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
contract_address,
|
||||
logs,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -162,9 +221,12 @@ 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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -187,9 +249,12 @@ 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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -209,7 +274,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
sender: sender_address.clone(),
|
||||
contract: contract_address.clone(),
|
||||
code_id,
|
||||
migrate_msg: serde_json::to_vec(msg)?,
|
||||
msg: serde_json::to_vec(msg)?,
|
||||
}
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?;
|
||||
@@ -219,9 +284,12 @@ 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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -251,9 +319,12 @@ 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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -288,14 +359,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
debug!(
|
||||
"gas wanted: {:?}, gas used: {:?}",
|
||||
tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used
|
||||
);
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -316,7 +385,36 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo)
|
||||
.await
|
||||
.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()
|
||||
}
|
||||
|
||||
async fn delegate_tokens(
|
||||
@@ -336,7 +434,8 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo)
|
||||
.await
|
||||
.await?
|
||||
.check_response()
|
||||
}
|
||||
|
||||
async fn undelegate_tokens(
|
||||
@@ -356,7 +455,8 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo)
|
||||
.await
|
||||
.await?
|
||||
.check_response()
|
||||
}
|
||||
|
||||
async fn withdraw_rewards(
|
||||
@@ -374,7 +474,45 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo)
|
||||
.await
|
||||
.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)
|
||||
}
|
||||
|
||||
/// Broadcast a transaction, returning immediately.
|
||||
@@ -385,6 +523,10 @@ 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()
|
||||
@@ -401,6 +543,10 @@ 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()
|
||||
@@ -417,6 +563,11 @@ 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()
|
||||
@@ -429,7 +580,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Any>,
|
||||
fee: Fee,
|
||||
fee: tx::Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
signer_data: SignerData,
|
||||
) -> Result<tx::Raw, NymdError> {
|
||||
@@ -467,7 +618,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Any>,
|
||||
fee: Fee,
|
||||
fee: tx::Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<tx::Raw, NymdError> {
|
||||
// TODO: Future optimisation: rather than grabbing current account_number and sequence
|
||||
@@ -485,22 +636,28 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
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 })
|
||||
Ok(Client {
|
||||
rpc_client,
|
||||
signer,
|
||||
gas_price: gas_price.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,4 +679,8 @@ impl SigningCosmWasmClient for Client {
|
||||
fn signer(&self) -> &DirectSecp256k1HdWallet {
|
||||
&self.signer
|
||||
}
|
||||
|
||||
fn gas_price(&self) -> &GasPrice {
|
||||
&self.gas_price
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,19 @@ 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::cosmwasm::wasm::v1beta1::{
|
||||
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::{
|
||||
CodeInfoResponse, ContractCodeHistoryEntry as ProtoContractCodeHistoryEntry,
|
||||
ContractCodeHistoryOperationType, ContractInfo as ProtoContractInfo,
|
||||
};
|
||||
use cosmrs::tendermint::chain;
|
||||
use cosmrs::tx::{AccountNumber, SequenceNumber};
|
||||
use cosmrs::tendermint::{abci, chain};
|
||||
use cosmrs::tx::{AccountNumber, Gas, SequenceNumber};
|
||||
use cosmrs::{tx, AccountId, Coin};
|
||||
use serde::Serialize;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
pub type ContractCodeId = u64;
|
||||
|
||||
@@ -70,18 +74,6 @@ 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 {
|
||||
@@ -92,31 +84,16 @@ 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -242,6 +219,101 @@ 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)
|
||||
// ##############################################################################
|
||||
@@ -254,21 +326,6 @@ 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
|
||||
@@ -290,6 +347,8 @@ pub struct UploadResult {
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
|
||||
pub gas_info: GasInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -316,6 +375,8 @@ pub struct InstantiateResult {
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
|
||||
pub gas_info: GasInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -324,6 +385,8 @@ pub struct ChangeAdminResult {
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
|
||||
pub gas_info: GasInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -332,6 +395,8 @@ pub struct MigrateResult {
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
|
||||
pub gas_info: GasInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -340,4 +405,6 @@ 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::block;
|
||||
use cosmrs::tendermint::{abci, block};
|
||||
use cosmrs::{bip32, tx, AccountId};
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
@@ -102,6 +102,12 @@ 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
-1
@@ -38,7 +38,7 @@ impl<'a> Mul<Gas> for &'a GasPrice {
|
||||
// however, realistically that is impossible to happen as the resultant value
|
||||
// would have to be way higher than our token limit of 10^15 (1 billion of tokens * 1 million for denomination)
|
||||
// and max value of u128 is approximately 10^38
|
||||
if limit_uint128.u128() * gas_price_numerator > amount.u128() * gas_price_denominator {
|
||||
if limit_uint128 * gas_price_numerator > amount * gas_price_denominator {
|
||||
amount += Uint128::new(1);
|
||||
}
|
||||
|
||||
+42
-7
@@ -17,17 +17,29 @@ pub enum Operation {
|
||||
Send,
|
||||
|
||||
BondMixnode,
|
||||
BondMixnodeOnBehalf,
|
||||
UnbondMixnode,
|
||||
UnbondMixnodeOnBehalf,
|
||||
DelegateToMixnode,
|
||||
DelegateToMixnodeOnBehalf,
|
||||
UndelegateFromMixnode,
|
||||
UndelegateFromMixnodeOnBehalf,
|
||||
|
||||
BondGateway,
|
||||
BondGatewayOnBehalf,
|
||||
UnbondGateway,
|
||||
UnbondGatewayOnBehalf,
|
||||
|
||||
UpdateStateParams,
|
||||
UpdateContractSettings,
|
||||
|
||||
BeginMixnodeRewarding,
|
||||
FinishMixnodeRewarding,
|
||||
|
||||
TrackUnbondGateway,
|
||||
TrackUnbondMixnode,
|
||||
WithdrawVestedCoins,
|
||||
TrackUndelegation,
|
||||
CreatePeriodicVestingAccount,
|
||||
}
|
||||
|
||||
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
|
||||
@@ -43,14 +55,27 @@ 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::UpdateStateParams => f.write_str("UpdateStateParams"),
|
||||
Operation::UndelegateFromMixnodeOnBehalf => {
|
||||
f.write_str("UndelegateFromMixnodeOnBehalf")
|
||||
}
|
||||
Operation::UpdateContractSettings => f.write_str("UpdateContractSettings"),
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,23 +84,34 @@ impl Operation {
|
||||
// TODO: some value tweaking
|
||||
pub fn default_gas_limit(&self) -> Gas {
|
||||
match self {
|
||||
Operation::Upload => 2_500_000u64.into(),
|
||||
Operation::Upload => 3_000_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::UpdateStateParams => 175_000u64.into(),
|
||||
Operation::UpdateContractSettings => 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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,9 +125,8 @@ impl Operation {
|
||||
Fee::from_amount_and_gas(fee, 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)
|
||||
pub fn default_fee(&self, gas_price: &GasPrice) -> Fee {
|
||||
Self::determine_custom_fee(gas_price, self.default_gas_limit())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
// 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;
|
||||
@@ -0,0 +1,176 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
// 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)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct Secp256k1Derivation {
|
||||
hd_path: DerivationPath,
|
||||
prefix: String,
|
||||
@@ -40,7 +40,7 @@ impl AccountData {
|
||||
|
||||
type Secp256k1Keypair = (SigningKey, PublicKey);
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DirectSecp256k1HdWallet {
|
||||
/// Base secret
|
||||
secret: bip39::Mnemonic,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use ed25519_dalek::ed25519::signature::Signature as SignatureTrait;
|
||||
pub 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 KeyRecoveryError {
|
||||
pub enum Ed25519RecoveryError {
|
||||
MalformedBytes(SignatureError),
|
||||
MalformedString(bs58::decode::Error),
|
||||
}
|
||||
|
||||
impl From<SignatureError> for KeyRecoveryError {
|
||||
impl From<SignatureError> for Ed25519RecoveryError {
|
||||
fn from(err: SignatureError) -> Self {
|
||||
KeyRecoveryError::MalformedBytes(err)
|
||||
Ed25519RecoveryError::MalformedBytes(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bs58::decode::Error> for KeyRecoveryError {
|
||||
impl From<bs58::decode::Error> for Ed25519RecoveryError {
|
||||
fn from(err: bs58::decode::Error) -> Self {
|
||||
KeyRecoveryError::MalformedString(err)
|
||||
Ed25519RecoveryError::MalformedString(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for KeyRecoveryError {
|
||||
impl fmt::Display for Ed25519RecoveryError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
KeyRecoveryError::MalformedBytes(err) => write!(f, "malformed bytes - {}", err),
|
||||
KeyRecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err),
|
||||
Ed25519RecoveryError::MalformedBytes(err) => write!(f, "malformed bytes - {}", err),
|
||||
Ed25519RecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for KeyRecoveryError {}
|
||||
impl std::error::Error for Ed25519RecoveryError {}
|
||||
|
||||
/// 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, KeyRecoveryError> {
|
||||
pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result<Self, Ed25519RecoveryError> {
|
||||
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, KeyRecoveryError> {
|
||||
pub fn from_bytes(b: &[u8]) -> Result<Self, Ed25519RecoveryError> {
|
||||
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, KeyRecoveryError> {
|
||||
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
|
||||
let bytes = bs58::decode(val).into_vec()?;
|
||||
Self::from_bytes(&bytes)
|
||||
}
|
||||
@@ -134,7 +134,7 @@ impl PublicKey {
|
||||
}
|
||||
|
||||
impl PemStorableKey for PublicKey {
|
||||
type Error = KeyRecoveryError;
|
||||
type Error = Ed25519RecoveryError;
|
||||
|
||||
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, KeyRecoveryError> {
|
||||
pub fn from_bytes(b: &[u8]) -> Result<Self, Ed25519RecoveryError> {
|
||||
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, KeyRecoveryError> {
|
||||
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
|
||||
let bytes = bs58::decode(val).into_vec()?;
|
||||
Self::from_bytes(&bytes)
|
||||
}
|
||||
@@ -192,7 +192,7 @@ impl PrivateKey {
|
||||
}
|
||||
|
||||
impl PemStorableKey for PrivateKey {
|
||||
type Error = KeyRecoveryError;
|
||||
type Error = Ed25519RecoveryError;
|
||||
|
||||
fn pem_type() -> &'static str {
|
||||
"ED25519 PRIVATE KEY"
|
||||
@@ -211,11 +211,20 @@ 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, SignatureError> {
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Ed25519RecoveryError> {
|
||||
Ok(Signature(ed25519_dalek::Signature::from_bytes(bytes)?))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,7 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
|
||||
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch = "0.14.1-updatedk256" }
|
||||
#cosmwasm-std = { version = "0.14.1" }
|
||||
cosmwasm-std = "1.0.0-beta2"
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_repr = "0.1"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// 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)]
|
||||
|
||||
@@ -7,51 +10,41 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct UnpackedDelegation<T> {
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
|
||||
pub struct Delegation {
|
||||
pub owner: Addr,
|
||||
pub node_identity: IdentityKey,
|
||||
pub delegation_data: T,
|
||||
}
|
||||
|
||||
impl<T> UnpackedDelegation<T> {
|
||||
pub fn new(owner: Addr, node_identity: IdentityKey, delegation_data: T) -> Self {
|
||||
UnpackedDelegation {
|
||||
owner,
|
||||
node_identity,
|
||||
delegation_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct RawDelegationData {
|
||||
pub amount: Uint128,
|
||||
pub amount: Coin,
|
||||
pub block_height: u64,
|
||||
}
|
||||
|
||||
impl RawDelegationData {
|
||||
pub fn new(amount: Uint128, block_height: u64) -> Self {
|
||||
RawDelegationData {
|
||||
amount,
|
||||
block_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct Delegation {
|
||||
owner: Addr,
|
||||
amount: Coin,
|
||||
block_height: u64,
|
||||
pub proxy: Option<Addr>, // proxy address used to delegate the funds on behalf of anouther address
|
||||
}
|
||||
|
||||
impl Delegation {
|
||||
pub fn new(owner: Addr, amount: Coin, block_height: u64) -> Self {
|
||||
pub fn new(
|
||||
owner: Addr,
|
||||
node_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
block_height: u64,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
Delegation {
|
||||
owner,
|
||||
node_identity,
|
||||
amount,
|
||||
block_height,
|
||||
proxy,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: change that to use .joined_key() and return Vec<u8>
|
||||
pub fn storage_key(&self) -> (IdentityKey, Addr) {
|
||||
(self.node_identity(), self.owner())
|
||||
}
|
||||
|
||||
pub fn increment_amount(&mut self, amount: Uint128, at_height: Option<u64>) {
|
||||
self.amount.amount += amount;
|
||||
if let Some(at_height) = at_height {
|
||||
self.block_height = at_height;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +52,10 @@ impl Delegation {
|
||||
&self.amount
|
||||
}
|
||||
|
||||
pub fn node_identity(&self) -> IdentityKey {
|
||||
self.node_identity.clone()
|
||||
}
|
||||
|
||||
pub fn owner(&self) -> Addr {
|
||||
self.owner.clone()
|
||||
}
|
||||
@@ -72,65 +69,56 @@ impl Display for Delegation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} {} delegated by {} at block {}",
|
||||
self.amount.amount, self.amount.denom, self.owner, self.block_height
|
||||
"{} delegated towards {} by {} at block {}",
|
||||
self.amount, self.node_identity, self.owner, self.block_height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedMixDelegationsResponse {
|
||||
pub node_identity: IdentityKey,
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<Addr>,
|
||||
pub start_next_after: Option<String>,
|
||||
}
|
||||
|
||||
impl PagedMixDelegationsResponse {
|
||||
pub fn new(
|
||||
node_identity: IdentityKey,
|
||||
delegations: Vec<Delegation>,
|
||||
start_next_after: Option<Addr>,
|
||||
) -> Self {
|
||||
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<Addr>) -> Self {
|
||||
PagedMixDelegationsResponse {
|
||||
node_identity,
|
||||
delegations,
|
||||
start_next_after,
|
||||
start_next_after: start_next_after.map(|s| s.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedReverseMixDelegationsResponse {
|
||||
pub delegation_owner: Addr,
|
||||
pub delegated_nodes: Vec<IdentityKey>,
|
||||
pub struct PagedDelegatorDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<IdentityKey>,
|
||||
}
|
||||
|
||||
impl PagedReverseMixDelegationsResponse {
|
||||
pub fn new(
|
||||
delegation_owner: Addr,
|
||||
delegated_nodes: Vec<IdentityKey>,
|
||||
start_next_after: Option<IdentityKey>,
|
||||
) -> Self {
|
||||
PagedReverseMixDelegationsResponse {
|
||||
delegation_owner,
|
||||
delegated_nodes,
|
||||
impl PagedDelegatorDelegationsResponse {
|
||||
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<IdentityKey>) -> Self {
|
||||
PagedDelegatorDelegationsResponse {
|
||||
delegations,
|
||||
start_next_after,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedAllDelegationsResponse<T> {
|
||||
pub delegations: Vec<UnpackedDelegation<T>>,
|
||||
pub start_next_after: Option<Vec<u8>>,
|
||||
pub struct PagedAllDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<(IdentityKey, String)>,
|
||||
}
|
||||
|
||||
impl<T> PagedAllDelegationsResponse<T> {
|
||||
pub fn new(delegations: Vec<UnpackedDelegation<T>>, start_next_after: Option<Vec<u8>>) -> Self {
|
||||
impl PagedAllDelegationsResponse {
|
||||
pub fn new(
|
||||
delegations: Vec<Delegation>,
|
||||
start_next_after: Option<(IdentityKey, Addr)>,
|
||||
) -> Self {
|
||||
PagedAllDelegationsResponse {
|
||||
delegations,
|
||||
start_next_after,
|
||||
start_next_after: start_next_after.map(|(id, addr)| (id, addr.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,19 +23,27 @@ pub struct Gateway {
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct GatewayBond {
|
||||
pub bond_amount: Coin,
|
||||
pub pledge_amount: Coin,
|
||||
pub owner: Addr,
|
||||
pub block_height: u64,
|
||||
pub gateway: Gateway,
|
||||
pub proxy: Option<Addr>,
|
||||
}
|
||||
|
||||
impl GatewayBond {
|
||||
pub fn new(bond_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
|
||||
pub fn new(
|
||||
pledge_amount: Coin,
|
||||
owner: Addr,
|
||||
block_height: u64,
|
||||
gateway: Gateway,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
GatewayBond {
|
||||
bond_amount,
|
||||
pledge_amount,
|
||||
owner,
|
||||
block_height,
|
||||
gateway,
|
||||
proxy,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +51,8 @@ impl GatewayBond {
|
||||
&self.gateway.identity_key
|
||||
}
|
||||
|
||||
pub fn bond_amount(&self) -> Coin {
|
||||
self.bond_amount.clone()
|
||||
pub fn pledge_amount(&self) -> Coin {
|
||||
self.pledge_amount.clone()
|
||||
}
|
||||
|
||||
pub fn owner(&self) -> &Addr {
|
||||
@@ -59,17 +67,17 @@ impl GatewayBond {
|
||||
impl PartialOrd for GatewayBond {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
// first remove invalid cases
|
||||
if self.bond_amount.denom != other.bond_amount.denom {
|
||||
if self.pledge_amount.denom != other.pledge_amount.denom {
|
||||
return None;
|
||||
}
|
||||
|
||||
// try to order by total bond
|
||||
let bond_cmp = self
|
||||
.bond_amount
|
||||
// try to order by total pledge
|
||||
let pledge_cmp = self
|
||||
.pledge_amount
|
||||
.amount
|
||||
.partial_cmp(&other.bond_amount.amount)?;
|
||||
if bond_cmp != Ordering::Equal {
|
||||
return Some(bond_cmp);
|
||||
.partial_cmp(&other.pledge_amount.amount)?;
|
||||
if pledge_cmp != Ordering::Equal {
|
||||
return Some(pledge_cmp);
|
||||
}
|
||||
|
||||
// then check block height
|
||||
@@ -94,7 +102,10 @@ impl Display for GatewayBond {
|
||||
write!(
|
||||
f,
|
||||
"amount: {} {}, owner: {}, identity: {}",
|
||||
self.bond_amount.amount, self.bond_amount.denom, self.owner, self.gateway.identity_key
|
||||
self.pledge_amount.amount,
|
||||
self.pledge_amount.denom,
|
||||
self.owner,
|
||||
self.gateway.identity_key
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -123,7 +134,7 @@ impl PagedGatewayResponse {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct GatewayOwnershipResponse {
|
||||
pub address: Addr,
|
||||
pub has_gateway: bool,
|
||||
pub gateway: Option<GatewayBond>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -150,38 +161,43 @@ mod tests {
|
||||
let _0foos = Coin::new(0, "foo");
|
||||
|
||||
let gate1 = GatewayBond {
|
||||
bond_amount: _150foos.clone(),
|
||||
pledge_amount: _150foos.clone(),
|
||||
owner: Addr::unchecked("foo1"),
|
||||
block_height: 100,
|
||||
gateway: gateway_fixture(),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
let gate2 = GatewayBond {
|
||||
bond_amount: _150foos,
|
||||
pledge_amount: _150foos,
|
||||
owner: Addr::unchecked("foo2"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
let gate3 = GatewayBond {
|
||||
bond_amount: _50foos,
|
||||
pledge_amount: _50foos,
|
||||
owner: Addr::unchecked("foo3"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
let gate4 = GatewayBond {
|
||||
bond_amount: _140foos,
|
||||
pledge_amount: _140foos,
|
||||
owner: Addr::unchecked("foo4"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
let gate5 = GatewayBond {
|
||||
bond_amount: _0foos,
|
||||
pledge_amount: _0foos,
|
||||
owner: Addr::unchecked("foo5"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
// summary:
|
||||
|
||||
@@ -12,10 +12,10 @@ pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
|
||||
|
||||
pub use cosmwasm_std::{Addr, Coin};
|
||||
pub use delegation::{
|
||||
Delegation, PagedAllDelegationsResponse, PagedMixDelegationsResponse,
|
||||
PagedReverseMixDelegationsResponse, RawDelegationData, UnpackedDelegation,
|
||||
Delegation, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
|
||||
PagedMixDelegationsResponse,
|
||||
};
|
||||
pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse};
|
||||
pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
|
||||
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
pub use msg::*;
|
||||
pub use types::*;
|
||||
|
||||
@@ -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, DEFAULT_PROFIT_MARGIN};
|
||||
use network_defaults::DEFAULT_OPERATOR_EPOCH_COST;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
@@ -29,6 +29,7 @@ pub struct MixNode {
|
||||
/// Base58 encoded ed25519 EdDSA public key.
|
||||
pub identity_key: IdentityKey,
|
||||
pub version: String,
|
||||
pub profit_margin_percent: u8,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
@@ -55,32 +56,68 @@ pub enum Layer {
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct NodeRewardParams {
|
||||
period_reward_pool: Uint128,
|
||||
k: Uint128,
|
||||
rewarded_set_size: Uint128,
|
||||
active_set_size: 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,
|
||||
k: u128,
|
||||
rewarded_set_size: u128,
|
||||
active_set_size: 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(period_reward_pool),
|
||||
k: Uint128(k),
|
||||
period_reward_pool: Uint128::new(period_reward_pool),
|
||||
rewarded_set_size: Uint128::new(rewarded_set_size),
|
||||
active_set_size: Uint128::new(active_set_size),
|
||||
reward_blockstamp,
|
||||
circulating_supply: Uint128(circulating_supply),
|
||||
uptime: Uint128(uptime),
|
||||
circulating_supply: Uint128::new(circulating_supply),
|
||||
uptime: Uint128::new(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)
|
||||
}
|
||||
@@ -97,8 +134,8 @@ impl NodeRewardParams {
|
||||
self.period_reward_pool.u128()
|
||||
}
|
||||
|
||||
pub fn k(&self) -> u128 {
|
||||
self.k.u128()
|
||||
pub fn rewarded_set_size(&self) -> u128 {
|
||||
self.rewarded_set_size.u128()
|
||||
}
|
||||
|
||||
pub fn circulating_supply(&self) -> u128 {
|
||||
@@ -114,7 +151,7 @@ impl NodeRewardParams {
|
||||
}
|
||||
|
||||
pub fn one_over_k(&self) -> U128 {
|
||||
ONE / U128::from_num(self.k.u128())
|
||||
ONE / U128::from_num(self.rewarded_set_size.u128())
|
||||
}
|
||||
|
||||
pub fn alpha(&self) -> U128 {
|
||||
@@ -229,46 +266,45 @@ impl NodeRewardResult {
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeBond {
|
||||
pub bond_amount: Coin,
|
||||
pub pledge_amount: Coin,
|
||||
pub total_delegation: 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 MixNodeBond {
|
||||
pub fn new(
|
||||
bond_amount: Coin,
|
||||
pledge_amount: Coin,
|
||||
owner: Addr,
|
||||
layer: Layer,
|
||||
block_height: u64,
|
||||
mix_node: MixNode,
|
||||
profit_margin_percent: Option<u8>,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
MixNodeBond {
|
||||
total_delegation: coin(0, &bond_amount.denom),
|
||||
bond_amount,
|
||||
total_delegation: coin(0, &pledge_amount.denom),
|
||||
pledge_amount,
|
||||
owner,
|
||||
layer,
|
||||
block_height,
|
||||
mix_node,
|
||||
profit_margin_percent,
|
||||
proxy,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn profit_margin(&self) -> U128 {
|
||||
U128::from_num(self.profit_margin_percent.unwrap_or(DEFAULT_PROFIT_MARGIN))
|
||||
/ U128::from_num(100)
|
||||
U128::from_num(self.mix_node.profit_margin_percent) / U128::from_num(100)
|
||||
}
|
||||
|
||||
pub fn identity(&self) -> &String {
|
||||
&self.mix_node.identity_key
|
||||
}
|
||||
|
||||
pub fn bond_amount(&self) -> Coin {
|
||||
self.bond_amount.clone()
|
||||
pub fn pledge_amount(&self) -> Coin {
|
||||
self.pledge_amount.clone()
|
||||
}
|
||||
|
||||
pub fn owner(&self) -> &Addr {
|
||||
@@ -280,10 +316,10 @@ impl MixNodeBond {
|
||||
}
|
||||
|
||||
pub fn total_stake(&self) -> Option<u128> {
|
||||
if self.bond_amount.denom != self.total_delegation.denom {
|
||||
if self.pledge_amount.denom != self.total_delegation.denom {
|
||||
None
|
||||
} else {
|
||||
Some(self.bond_amount.amount.u128() + self.total_delegation.amount.u128())
|
||||
Some(self.pledge_amount.amount.u128() + self.total_delegation.amount.u128())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,38 +327,37 @@ impl MixNodeBond {
|
||||
self.total_delegation.clone()
|
||||
}
|
||||
|
||||
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 pledge_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
|
||||
U128::from_num(self.pledge_amount().amount.u128()) / U128::from_num(circulating_supply)
|
||||
}
|
||||
|
||||
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())
|
||||
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())
|
||||
/ U128::from_num(circulating_supply)
|
||||
}
|
||||
|
||||
pub fn lambda(&self, params: &NodeRewardParams) -> U128 {
|
||||
// Ratio of a bond to the token circulating supply
|
||||
let bond_to_circulating_supply_ratio =
|
||||
self.bond_to_circulating_supply(params.circulating_supply());
|
||||
bond_to_circulating_supply_ratio.min(params.one_over_k())
|
||||
let pledge_to_circulating_supply_ratio =
|
||||
self.pledge_to_circulating_supply(params.circulating_supply());
|
||||
pledge_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_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())
|
||||
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())
|
||||
}
|
||||
|
||||
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 * omega_k + params.alpha() * lambda * sigma * params.k())
|
||||
* (sigma * params.omega()
|
||||
+ params.alpha() * lambda * sigma * params.rewarded_set_size())
|
||||
/ (ONE + params.alpha());
|
||||
|
||||
NodeRewardResult {
|
||||
@@ -367,9 +402,9 @@ impl MixNodeBond {
|
||||
}
|
||||
|
||||
pub fn sigma_ratio(&self, params: &NodeRewardParams) -> U128 {
|
||||
if self.total_stake_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
|
||||
if self.total_bond_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
|
||||
{
|
||||
self.total_stake_to_circulating_supply(params.circulating_supply())
|
||||
self.total_bond_to_circulating_supply(params.circulating_supply())
|
||||
} else {
|
||||
params.one_over_k()
|
||||
}
|
||||
@@ -384,33 +419,33 @@ impl MixNodeBond {
|
||||
impl PartialOrd for MixNodeBond {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
// first remove invalid cases
|
||||
if self.bond_amount.denom != self.total_delegation.denom {
|
||||
if self.pledge_amount.denom != self.total_delegation.denom {
|
||||
return None;
|
||||
}
|
||||
|
||||
if other.bond_amount.denom != other.total_delegation.denom {
|
||||
if other.pledge_amount.denom != other.total_delegation.denom {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.bond_amount.denom != other.bond_amount.denom {
|
||||
if self.pledge_amount.denom != other.pledge_amount.denom {
|
||||
return None;
|
||||
}
|
||||
|
||||
// try to order by total bond + delegation
|
||||
let total_cmp = (self.bond_amount.amount + self.total_delegation.amount)
|
||||
.partial_cmp(&(self.bond_amount.amount + self.total_delegation.amount))?;
|
||||
let total_cmp = (self.pledge_amount.amount + self.total_delegation.amount)
|
||||
.partial_cmp(&(self.pledge_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 bond_cmp = self
|
||||
.bond_amount
|
||||
let pledge_cmp = self
|
||||
.pledge_amount
|
||||
.amount
|
||||
.partial_cmp(&other.bond_amount.amount)?;
|
||||
if bond_cmp != Ordering::Equal {
|
||||
return Some(bond_cmp);
|
||||
.partial_cmp(&other.pledge_amount.amount)?;
|
||||
if pledge_cmp != Ordering::Equal {
|
||||
return Some(pledge_cmp);
|
||||
}
|
||||
|
||||
// then look at delegation (I'm not sure we can get here, but better safe than sorry)
|
||||
@@ -449,7 +484,10 @@ impl Display for MixNodeBond {
|
||||
write!(
|
||||
f,
|
||||
"amount: {} {}, owner: {}, identity: {}",
|
||||
self.bond_amount.amount, self.bond_amount.denom, self.owner, self.mix_node.identity_key
|
||||
self.pledge_amount.amount,
|
||||
self.pledge_amount.denom,
|
||||
self.owner,
|
||||
self.mix_node.identity_key
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -478,7 +516,7 @@ impl PagedMixnodeResponse {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct MixOwnershipResponse {
|
||||
pub address: Addr,
|
||||
pub has_node: bool,
|
||||
pub mixnode: Option<MixNodeBond>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -494,6 +532,7 @@ mod tests {
|
||||
sphinx_key: "sphinxkey".to_string(),
|
||||
identity_key: "identitykey".to_string(),
|
||||
version: "0.11.0".to_string(),
|
||||
profit_margin_percent: 10,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,53 +543,53 @@ mod tests {
|
||||
let _0foos = Coin::new(0, "foo");
|
||||
|
||||
let mix1 = MixNodeBond {
|
||||
bond_amount: _150foos.clone(),
|
||||
pledge_amount: _150foos.clone(),
|
||||
total_delegation: _50foos.clone(),
|
||||
owner: Addr::unchecked("foo1"),
|
||||
layer: Layer::One,
|
||||
block_height: 100,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
let mix2 = MixNodeBond {
|
||||
bond_amount: _150foos.clone(),
|
||||
pledge_amount: _150foos.clone(),
|
||||
total_delegation: _50foos.clone(),
|
||||
owner: Addr::unchecked("foo2"),
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
let mix3 = MixNodeBond {
|
||||
bond_amount: _50foos,
|
||||
pledge_amount: _50foos,
|
||||
total_delegation: _150foos.clone(),
|
||||
owner: Addr::unchecked("foo3"),
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
let mix4 = MixNodeBond {
|
||||
bond_amount: _150foos.clone(),
|
||||
pledge_amount: _150foos.clone(),
|
||||
total_delegation: _0foos.clone(),
|
||||
owner: Addr::unchecked("foo4"),
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
let mix5 = MixNodeBond {
|
||||
bond_amount: _0foos,
|
||||
pledge_amount: _0foos,
|
||||
total_delegation: _150foos,
|
||||
owner: Addr::unchecked("foo5"),
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
// summary:
|
||||
|
||||
@@ -2,27 +2,30 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::mixnode::NodeRewardParams;
|
||||
use crate::StateParams;
|
||||
use crate::ContractStateParams;
|
||||
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 struct InstantiateMsg {
|
||||
pub rewarding_validator_address: String,
|
||||
}
|
||||
|
||||
#[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 {},
|
||||
UpdateStateParams(StateParams),
|
||||
UpdateContractStateParams(ContractStateParams),
|
||||
|
||||
DelegateToMixnode {
|
||||
mix_identity: IdentityKey,
|
||||
@@ -42,7 +45,7 @@ pub enum ExecuteMsg {
|
||||
rewarding_interval_nonce: u32,
|
||||
},
|
||||
|
||||
RewardMixnodeV2 {
|
||||
RewardMixnode {
|
||||
identity: IdentityKey,
|
||||
// percentage value in range 0-100
|
||||
params: NodeRewardParams,
|
||||
@@ -50,17 +53,41 @@ 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>,
|
||||
@@ -70,30 +97,39 @@ pub enum QueryMsg {
|
||||
limit: Option<u32>,
|
||||
},
|
||||
OwnsMixnode {
|
||||
address: Addr,
|
||||
address: String,
|
||||
},
|
||||
OwnsGateway {
|
||||
address: Addr,
|
||||
address: String,
|
||||
},
|
||||
StateParams {},
|
||||
CurrentRewardingInterval {},
|
||||
GetMixDelegations {
|
||||
// gets all [paged] delegations in the entire network
|
||||
// TODO: do we even want that?
|
||||
GetAllNetworkDelegations {
|
||||
start_after: Option<(IdentityKey, String)>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
// gets all [paged] delegations associated with particular mixnode
|
||||
GetMixnodeDelegations {
|
||||
mix_identity: IdentityKey,
|
||||
start_after: Option<Addr>,
|
||||
// since `start_after` is user-provided input, we can't use `Addr` as we
|
||||
// can't guarantee it's validated.
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetAllMixDelegations {
|
||||
start_after: Option<Vec<u8>>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetReverseMixDelegations {
|
||||
delegation_owner: Addr,
|
||||
// gets all [paged] delegations associated with particular delegator
|
||||
GetDelegatorDelegations {
|
||||
// since `delegator` is user-provided input, we can't use `Addr` as we
|
||||
// can't guarantee it's validated.
|
||||
delegator: String,
|
||||
start_after: Option<IdentityKey>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetMixDelegation {
|
||||
// gets delegation associated with particular mixnode, delegator pair
|
||||
GetDelegationDetails {
|
||||
mix_identity: IdentityKey,
|
||||
address: Addr,
|
||||
delegator: String,
|
||||
},
|
||||
LayerDistribution {},
|
||||
GetRewardPool {},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::mixnode::DelegatorRewardParams;
|
||||
use crate::Layer;
|
||||
use cosmwasm_std::Uint128;
|
||||
use cosmwasm_std::{Addr, Uint128};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
@@ -35,13 +35,13 @@ pub struct RewardingIntervalResponse {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct StateParams {
|
||||
pub struct ContractStateParams {
|
||||
// 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_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
|
||||
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
|
||||
|
||||
// number of mixnode that are going to get rewarded during current rewarding interval (k_m)
|
||||
// based on overall demand for private bandwidth-
|
||||
@@ -50,13 +50,22 @@ pub struct StateParams {
|
||||
// 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 StateParams {
|
||||
impl Display for ContractStateParams {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Contract state parameters: [ ")?;
|
||||
write!(f, "minimum mixnode bond: {}; ", self.minimum_mixnode_bond)?;
|
||||
write!(f, "minimum gateway bond: {}; ", self.minimum_gateway_bond)?;
|
||||
write!(
|
||||
f,
|
||||
"minimum mixnode pledge: {}; ",
|
||||
self.minimum_mixnode_pledge
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"minimum gateway pledge: {}; ",
|
||||
self.minimum_gateway_pledge
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"mixnode rewarded set size: {}",
|
||||
@@ -66,6 +75,11 @@ impl Display for StateParams {
|
||||
f,
|
||||
"mixnode active set size: {}",
|
||||
self.mixnode_active_set_size
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"mixnode active set work factor: {}",
|
||||
self.active_set_work_factor
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -81,7 +95,7 @@ pub struct PendingDelegatorRewarding {
|
||||
// keep track of the running rewarding results so we'd known how much was the operator and its delegators rewarded
|
||||
pub running_results: RewardingResult,
|
||||
|
||||
pub next_start: String,
|
||||
pub next_start: Addr,
|
||||
|
||||
pub rewarding_params: DelegatorRewardParams,
|
||||
}
|
||||
@@ -97,6 +111,27 @@ 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;
|
||||
|
||||
@@ -63,6 +63,7 @@ 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
|
||||
|
||||
@@ -22,9 +22,9 @@ const CLIENT_IDENTITY_SIZE: usize = identity::PUBLIC_KEY_LENGTH;
|
||||
#[derive(Debug)]
|
||||
pub enum RecipientFormattingError {
|
||||
MalformedRecipientError,
|
||||
MalformedIdentityError(identity::KeyRecoveryError),
|
||||
MalformedIdentityError(identity::Ed25519RecoveryError),
|
||||
MalformedEncryptionKeyError(encryption::KeyRecoveryError),
|
||||
MalformedGatewayError(identity::KeyRecoveryError),
|
||||
MalformedGatewayError(identity::Ed25519RecoveryError),
|
||||
}
|
||||
|
||||
impl fmt::Display for RecipientFormattingError {
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::net::SocketAddr;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GatewayConversionError {
|
||||
InvalidIdentityKey(identity::KeyRecoveryError),
|
||||
InvalidIdentityKey(identity::Ed25519RecoveryError),
|
||||
InvalidSphinxKey(encryption::KeyRecoveryError),
|
||||
InvalidAddress(String, io::Error),
|
||||
InvalidStake,
|
||||
@@ -26,8 +26,8 @@ impl From<encryption::KeyRecoveryError> for GatewayConversionError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<identity::KeyRecoveryError> for GatewayConversionError {
|
||||
fn from(err: identity::KeyRecoveryError) -> Self {
|
||||
impl From<identity::Ed25519RecoveryError> for GatewayConversionError {
|
||||
fn from(err: identity::Ed25519RecoveryError) -> 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.bond_amount.amount.into(),
|
||||
stake: bond.pledge_amount.amount.into(),
|
||||
location: bond.gateway.location.clone(),
|
||||
host,
|
||||
mix_host,
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::net::SocketAddr;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MixnodeConversionError {
|
||||
InvalidIdentityKey(identity::KeyRecoveryError),
|
||||
InvalidIdentityKey(identity::Ed25519RecoveryError),
|
||||
InvalidSphinxKey(encryption::KeyRecoveryError),
|
||||
InvalidAddress(String, io::Error),
|
||||
InvalidStake,
|
||||
@@ -26,8 +26,8 @@ impl From<encryption::KeyRecoveryError> for MixnodeConversionError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<identity::KeyRecoveryError> for MixnodeConversionError {
|
||||
fn from(err: identity::KeyRecoveryError) -> Self {
|
||||
impl From<identity::Ed25519RecoveryError> for MixnodeConversionError {
|
||||
fn from(err: identity::Ed25519RecoveryError) -> 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.bond_amount.amount.into(),
|
||||
stake: bond.pledge_amount.amount.into(),
|
||||
delegation: bond.total_delegation.amount.into(),
|
||||
host,
|
||||
mix_host,
|
||||
|
||||
Generated
+1641
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
[workspace]
|
||||
members = ["erc20-bridge", "mixnet", "vesting"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = false
|
||||
rpath = false
|
||||
lto = true
|
||||
debug-assertions = false
|
||||
codegen-units = 1
|
||||
panic = 'abort'
|
||||
incremental = false
|
||||
overflow-checks = true
|
||||
Generated
+12
-8
@@ -89,8 +89,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
@@ -101,16 +102,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
@@ -124,8 +127,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-storage"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf3b4efe3b4f86df668520a02e9a29c23eea99b64dfcacb0e59b98346418af7f"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"serde",
|
||||
|
||||
@@ -5,22 +5,9 @@ edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[workspace] # adding a blank workspace to keep it out of the global workspace.
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = false
|
||||
rpath = false
|
||||
lto = true
|
||||
debug-assertions = false
|
||||
codegen-units = 1
|
||||
panic = 'abort'
|
||||
incremental = false
|
||||
overflow-checks = true
|
||||
|
||||
[features]
|
||||
# for more explicit tests, cargo test --features=backtraces
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
@@ -31,9 +18,8 @@ config = { path = "../../common/config"}
|
||||
[dependencies]
|
||||
erc20-bridge-contract = { path = "../../common/erc20-bridge-contract" }
|
||||
|
||||
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
|
||||
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] }
|
||||
cosmwasm-storage = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] }
|
||||
cosmwasm-std = "1.0.0-beta2"
|
||||
cosmwasm-storage = "1.0.0-beta2"
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -68,7 +68,7 @@ pub mod tests {
|
||||
|
||||
#[test]
|
||||
fn initialize_contract() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let mut deps = mock_dependencies();
|
||||
let env = mock_env();
|
||||
let msg = InstantiateMsg {};
|
||||
let info = mock_info("creator", &[]);
|
||||
|
||||
@@ -11,7 +11,7 @@ pub mod helpers {
|
||||
use erc20_bridge_contract::payment::Payment;
|
||||
|
||||
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let mut deps = mock_dependencies();
|
||||
let msg = InstantiateMsg {};
|
||||
let env = mock_env();
|
||||
let info = mock_info("creator", &[]);
|
||||
|
||||
Generated
+703
-21
@@ -2,6 +2,43 @@
|
||||
# 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"
|
||||
@@ -14,6 +51,39 @@ 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"
|
||||
@@ -44,6 +114,18 @@ 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"
|
||||
@@ -62,12 +144,53 @@ 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"
|
||||
@@ -86,10 +209,17 @@ 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 = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
@@ -100,17 +230,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-schema"
|
||||
version = "0.14.1"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04159eec9b583671db7923ff2b979736dfb8f0152347cab9fd02373c22e1a870"
|
||||
checksum = "fe52b19d45fe3f8359db6cc24df44dbe05e5ae32539afc0f5b7f790a21aa6fd0"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde_json",
|
||||
@@ -118,8 +249,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
@@ -133,8 +265,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-storage"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
version = "1.0.0-beta2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf3b4efe3b4f86df668520a02e9a29c23eea99b64dfcacb0e59b98346418af7f"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"serde",
|
||||
@@ -155,6 +288,26 @@ 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"
|
||||
@@ -163,10 +316,20 @@ checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"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"
|
||||
@@ -174,7 +337,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"subtle",
|
||||
"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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -186,10 +358,21 @@ dependencies = [
|
||||
"byteorder",
|
||||
"digest 0.9.0",
|
||||
"rand_core 0.5.1",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"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"
|
||||
@@ -235,6 +418,29 @@ 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"
|
||||
@@ -261,10 +467,30 @@ dependencies = [
|
||||
"group",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"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"
|
||||
@@ -278,7 +504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f"
|
||||
dependencies = [
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -330,8 +556,10 @@ 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]]
|
||||
@@ -345,6 +573,31 @@ 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"
|
||||
@@ -353,7 +606,7 @@ checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -388,13 +641,23 @@ 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",
|
||||
"crypto-mac 0.11.1",
|
||||
"digest 0.9.0",
|
||||
]
|
||||
|
||||
@@ -431,6 +694,24 @@ 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"
|
||||
@@ -443,12 +724,66 @@ 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"
|
||||
@@ -489,15 +824,21 @@ 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]]
|
||||
@@ -506,10 +847,43 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal",
|
||||
"serde",
|
||||
"time",
|
||||
"time 0.3.4",
|
||||
"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"
|
||||
@@ -522,6 +896,24 @@ 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"
|
||||
@@ -581,6 +973,42 @@ 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"
|
||||
@@ -605,6 +1033,29 @@ 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"
|
||||
@@ -623,6 +1074,55 @@ 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"
|
||||
@@ -653,6 +1153,12 @@ 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"
|
||||
@@ -750,6 +1256,29 @@ 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"
|
||||
@@ -765,6 +1294,12 @@ 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"
|
||||
@@ -782,6 +1317,18 @@ 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"
|
||||
@@ -802,6 +1349,16 @@ 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"
|
||||
@@ -899,6 +1456,29 @@ 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"
|
||||
@@ -918,7 +1498,109 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.4.2"
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970"
|
||||
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"
|
||||
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",
|
||||
]
|
||||
|
||||
+12
-20
@@ -12,22 +12,9 @@ exclude = [
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[workspace] # adding a blank workspace to keep it out of the global workspace.
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = false
|
||||
rpath = false
|
||||
lto = true
|
||||
debug-assertions = false
|
||||
codegen-units = 1
|
||||
panic = 'abort'
|
||||
incremental = false
|
||||
overflow-checks = true
|
||||
|
||||
[features]
|
||||
# for more explicit tests, cargo test --features=backtraces
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
@@ -35,18 +22,23 @@ backtraces = ["cosmwasm-std/backtraces"]
|
||||
[dependencies]
|
||||
mixnet-contract = { path = "../../common/mixnet-contract" }
|
||||
config = { path = "../../common/config"}
|
||||
vesting-contract = { path = "../vesting" }
|
||||
|
||||
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
|
||||
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] }
|
||||
cosmwasm-storage = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] }
|
||||
|
||||
#cosmwasm-std = { version = "0.14.1", features = ["iterator"] }
|
||||
#cosmwasm-storage = { version = "0.14.1", features = ["iterator"] }
|
||||
cosmwasm-std = "1.0.0-beta2"
|
||||
cosmwasm-storage = "1.0.0-beta2"
|
||||
cw-storage-plus = "0.10.3"
|
||||
|
||||
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 = { version = "0.14.0" }
|
||||
cosmwasm-schema = "1.0.0-beta2"
|
||||
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"] }
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// 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")
|
||||
}
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
|
||||
@@ -18,6 +17,5 @@ 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);
|
||||
}
|
||||
|
||||
@@ -69,10 +69,10 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"update_state_params"
|
||||
"update_contract_settings"
|
||||
],
|
||||
"properties": {
|
||||
"update_state_params": {
|
||||
"update_contract_settings": {
|
||||
"$ref": "#/definitions/StateParams"
|
||||
}
|
||||
},
|
||||
@@ -346,4 +346,4 @@
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,10 +101,10 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"state_params"
|
||||
"contract_settings_params"
|
||||
],
|
||||
"properties": {
|
||||
"state_params": {
|
||||
"contract_settings_params": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
@@ -321,4 +321,4 @@
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,35 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
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 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 cosmwasm_std::{
|
||||
entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128,
|
||||
};
|
||||
use mixnet_contract::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StateParams};
|
||||
use mixnet_contract::{ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
|
||||
/// Constant specifying minimum of coin required to bond a gateway
|
||||
pub const INITIAL_GATEWAY_BOND: Uint128 = Uint128(100_000000);
|
||||
pub const INITIAL_GATEWAY_PLEDGE: Uint128 = Uint128::new(100_000_000);
|
||||
|
||||
/// Constant specifying minimum of coin required to bond a mixnode
|
||||
pub const INITIAL_MIXNODE_BOND: Uint128 = Uint128(100_000000);
|
||||
pub const INITIAL_MIXNODE_PLEDGE: Uint128 = Uint128::new(100_000_000);
|
||||
|
||||
pub const INITIAL_MIXNODE_REWARDED_SET_SIZE: u32 = 200;
|
||||
pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100;
|
||||
@@ -24,19 +37,22 @@ 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;
|
||||
|
||||
// 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 {
|
||||
fn default_initial_state(
|
||||
owner: Addr,
|
||||
rewarding_validator_address: Addr,
|
||||
env: Env,
|
||||
) -> ContractState {
|
||||
ContractState {
|
||||
owner,
|
||||
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,
|
||||
rewarding_validator_address,
|
||||
params: ContractStateParams {
|
||||
minimum_mixnode_pledge: INITIAL_MIXNODE_PLEDGE,
|
||||
minimum_gateway_pledge: INITIAL_GATEWAY_PLEDGE,
|
||||
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,
|
||||
@@ -54,12 +70,15 @@ pub fn instantiate(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
_msg: InstantiateMsg,
|
||||
msg: InstantiateMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
let state = default_initial_state(info.sender, env);
|
||||
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))?;
|
||||
|
||||
config(deps.storage).save(&state)?;
|
||||
layer_distribution(deps.storage).save(&Default::default())?;
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
@@ -72,22 +91,42 @@ pub fn execute(
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::BondMixnode { mix_node } => {
|
||||
transactions::try_add_mixnode(deps, env, info, mix_node)
|
||||
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::UnbondMixnode {} => transactions::try_remove_mixnode(deps, info),
|
||||
ExecuteMsg::BondGateway { gateway } => {
|
||||
transactions::try_add_gateway(deps, env, info, gateway)
|
||||
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::UnbondGateway {} => transactions::try_remove_gateway(deps, info),
|
||||
ExecuteMsg::UpdateStateParams(params) => {
|
||||
transactions::try_update_state_params(deps, info, params)
|
||||
ExecuteMsg::UpdateContractStateParams(params) => {
|
||||
crate::mixnet_contract_settings::transactions::try_update_contract_settings(
|
||||
deps, info, params,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::RewardMixnodeV2 {
|
||||
ExecuteMsg::RewardMixnode {
|
||||
identity,
|
||||
params,
|
||||
rewarding_interval_nonce,
|
||||
} => transactions::try_reward_mixnode_v2(
|
||||
} => crate::rewards::transactions::try_reward_mixnode(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
@@ -96,88 +135,143 @@ pub fn execute(
|
||||
rewarding_interval_nonce,
|
||||
),
|
||||
ExecuteMsg::DelegateToMixnode { mix_identity } => {
|
||||
transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
|
||||
crate::delegations::transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
|
||||
}
|
||||
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
|
||||
transactions::try_remove_delegation_from_mixnode(deps, info, mix_identity)
|
||||
crate::delegations::transactions::try_remove_delegation_from_mixnode(
|
||||
deps,
|
||||
info,
|
||||
mix_identity,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::BeginMixnodeRewarding {
|
||||
rewarding_interval_nonce,
|
||||
} => transactions::try_begin_mixnode_rewarding(deps, env, info, rewarding_interval_nonce),
|
||||
} => crate::rewards::transactions::try_begin_mixnode_rewarding(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
rewarding_interval_nonce,
|
||||
),
|
||||
ExecuteMsg::FinishMixnodeRewarding {
|
||||
rewarding_interval_nonce,
|
||||
} => transactions::try_finish_mixnode_rewarding(deps, info, rewarding_interval_nonce),
|
||||
} => crate::rewards::transactions::try_finish_mixnode_rewarding(
|
||||
deps,
|
||||
info,
|
||||
rewarding_interval_nonce,
|
||||
),
|
||||
ExecuteMsg::RewardNextMixDelegators {
|
||||
mix_identity,
|
||||
rewarding_interval_nonce,
|
||||
} => transactions::try_reward_next_mixnode_delegators_v2(
|
||||
} => crate::rewards::transactions::try_reward_next_mixnode_delegators(
|
||||
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(&queries::query_mixnodes_paged(deps, start_after, limit)?)
|
||||
to_binary(&query_mixnodes_paged(deps, start_after, limit)?)
|
||||
}
|
||||
QueryMsg::GetGateways { limit, start_after } => {
|
||||
to_binary(&queries::query_gateways_paged(deps, start_after, limit)?)
|
||||
to_binary(&query_gateways_paged(deps, start_after, limit)?)
|
||||
}
|
||||
QueryMsg::OwnsMixnode { address } => {
|
||||
to_binary(&queries::query_owns_mixnode(deps, address)?)
|
||||
to_binary(&mixnode_queries::query_owns_mixnode(deps, address)?)
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
mix_identity,
|
||||
start_after,
|
||||
limit,
|
||||
} => to_binary(&queries::query_mixnode_delegations_paged(
|
||||
} => to_binary(&query_mixnode_delegations_paged(
|
||||
deps,
|
||||
mix_identity,
|
||||
start_after,
|
||||
limit,
|
||||
)?),
|
||||
QueryMsg::GetAllMixDelegations { start_after, limit } => to_binary(
|
||||
&queries::query_all_mixnode_delegations_paged(deps, start_after, limit)?,
|
||||
QueryMsg::GetAllNetworkDelegations { start_after, limit } => to_binary(
|
||||
&query_all_network_delegations_paged(deps, start_after, limit)?,
|
||||
),
|
||||
QueryMsg::GetReverseMixDelegations {
|
||||
delegation_owner,
|
||||
QueryMsg::GetDelegatorDelegations {
|
||||
delegator: delegation_owner,
|
||||
start_after,
|
||||
limit,
|
||||
} => to_binary(&queries::query_reverse_mixnode_delegations_paged(
|
||||
} => to_binary(&query_delegator_delegations_paged(
|
||||
deps,
|
||||
delegation_owner,
|
||||
start_after,
|
||||
limit,
|
||||
)?),
|
||||
QueryMsg::GetMixDelegation {
|
||||
QueryMsg::GetDelegationDetails {
|
||||
mix_identity,
|
||||
address,
|
||||
} => to_binary(&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)),
|
||||
delegator,
|
||||
} => to_binary(&query_mixnode_delegation(deps, mix_identity, delegator)?),
|
||||
QueryMsg::GetRewardPool {} => to_binary(&query_reward_pool(deps)?),
|
||||
QueryMsg::GetCirculatingSupply {} => to_binary(&query_circulating_supply(deps)?),
|
||||
QueryMsg::GetEpochRewardPercent {} => to_binary(&EPOCH_REWARD_PERCENT),
|
||||
QueryMsg::GetSybilResistancePercent {} => to_binary(&DEFAULT_SYBIL_RESISTANCE_PERCENT),
|
||||
QueryMsg::GetRewardingStatus {
|
||||
mix_identity,
|
||||
rewarding_interval_nonce,
|
||||
} => to_binary(&queries::query_rewarding_status(
|
||||
} => to_binary(&query_rewarding_status(
|
||||
deps,
|
||||
mix_identity,
|
||||
rewarding_interval_nonce,
|
||||
@@ -188,13 +282,14 @@ 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> {
|
||||
Ok(Default::default())
|
||||
todo!("ACTIVE_STATE_WORK_FACTOR to State");
|
||||
// Ok(Default::default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests::helpers::*;
|
||||
use crate::support::tests::test_helpers;
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
|
||||
use cosmwasm_std::{coins, from_binary};
|
||||
@@ -202,9 +297,11 @@ 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 msg = InstantiateMsg {
|
||||
rewarding_validator_address: config::defaults::REWARDING_VALIDATOR_ADDRESS.to_string(),
|
||||
};
|
||||
let info = mock_info("creator", &[]);
|
||||
|
||||
let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
|
||||
@@ -226,7 +323,7 @@ pub mod tests {
|
||||
// Contract balance should match what we initialized it as
|
||||
assert_eq!(
|
||||
coins(0, DENOM),
|
||||
query_contract_balance(env.contract.address, deps)
|
||||
test_helpers::query_contract_balance(env.contract.address, deps)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) mod queries;
|
||||
pub(crate) mod storage;
|
||||
pub(crate) mod transactions;
|
||||
@@ -0,0 +1,649 @@
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex};
|
||||
use mixnet_contract::{Addr, Delegation, IdentityKey};
|
||||
|
||||
// storage prefixes
|
||||
const DELEGATION_PK_NAMESPACE: &str = "dl";
|
||||
const DELEGATION_OWNER_IDX_NAMESPACE: &str = "dlo";
|
||||
const DELEGATION_MIXNODE_IDX_NAMESPACE: &str = "dlm";
|
||||
|
||||
// paged retrieval limits for all queries and transactions
|
||||
pub(crate) const DELEGATION_PAGE_MAX_LIMIT: u32 = 500;
|
||||
pub(crate) const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 250;
|
||||
|
||||
// It's a composite key on node's identity and delegator address
|
||||
type PrimaryKey = Vec<u8>;
|
||||
|
||||
pub(crate) struct DelegationIndex<'a> {
|
||||
pub(crate) owner: MultiIndex<'a, (Addr, PrimaryKey), Delegation>,
|
||||
|
||||
pub(crate) mixnode: MultiIndex<'a, (IdentityKey, PrimaryKey), Delegation>,
|
||||
}
|
||||
|
||||
impl<'a> IndexList<Delegation> for DelegationIndex<'a> {
|
||||
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<Delegation>> + '_> {
|
||||
let v: Vec<&dyn Index<Delegation>> = vec![&self.owner, &self.mixnode];
|
||||
Box::new(v.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
// I was really going back and forth about the data stored on the disk vs primary key duplication.
|
||||
// It was basically between convenience and bloat, but in the end I decided the convenience wins.
|
||||
//
|
||||
// Basically I had 2 approaches. a) store delegator address and mixnode identity only as primary key of delegation or
|
||||
// b) store it both as primary key AND inside delegation data.
|
||||
// For the longest time I was in favour of a), since that removed any data duplication. However...,
|
||||
// that also required that during index creation I recovered delegator address and mixnode identity
|
||||
// from the Vec<u8>. That doesn't sound that terrible. However, even though I'm 99.99% certain that
|
||||
// conversion would be impossible to fail, I'd still have to call an `unwrap` here due to required
|
||||
// type signature and I didn't feel super comfortable doing that in our smart contract...
|
||||
// So to get rid of this uncertainty I went with the b) approach. Even though each stored delegation
|
||||
// takes over ~250B (since the key has to be duplicated), in the grand blockchain scheme of things
|
||||
// it's not that terrible. Say we had 100_000_000 delegations -> that's still only 25GB of data
|
||||
// and as a nice by-product it cleans up code a little bit by only having a single Delegation type.
|
||||
pub(crate) fn delegations<'a>() -> IndexedMap<'a, PrimaryKey, Delegation, DelegationIndex<'a>> {
|
||||
let indexes = DelegationIndex {
|
||||
owner: MultiIndex::new(
|
||||
|d, pk| (d.owner.clone(), pk),
|
||||
DELEGATION_PK_NAMESPACE,
|
||||
DELEGATION_OWNER_IDX_NAMESPACE,
|
||||
),
|
||||
mixnode: MultiIndex::new(
|
||||
|d, pk| (d.node_identity.clone(), pk),
|
||||
DELEGATION_PK_NAMESPACE,
|
||||
DELEGATION_MIXNODE_IDX_NAMESPACE,
|
||||
),
|
||||
};
|
||||
|
||||
IndexedMap::new(DELEGATION_PK_NAMESPACE, indexes)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::delegations::storage;
|
||||
use cosmwasm_std::Addr;
|
||||
use mixnet_contract::IdentityKey;
|
||||
|
||||
#[cfg(test)]
|
||||
mod reverse_mix_delegations {
|
||||
use super::*;
|
||||
use crate::support::tests::test_helpers;
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::testing::mock_env;
|
||||
use cosmwasm_std::{coin, Order};
|
||||
use cw_storage_plus::PrimaryKey;
|
||||
use mixnet_contract::Delegation;
|
||||
|
||||
#[test]
|
||||
fn reverse_mix_delegation_exists() {
|
||||
let mut deps = test_helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
let delegation_owner = Addr::unchecked("bar");
|
||||
let delegation = coin(12345, DENOM);
|
||||
|
||||
let dummy_data = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
node_identity.clone(),
|
||||
delegation,
|
||||
mock_env().block.height,
|
||||
None,
|
||||
);
|
||||
|
||||
storage::delegations()
|
||||
.save(
|
||||
&mut deps.storage,
|
||||
(node_identity.clone(), delegation_owner.clone()).joined_key(),
|
||||
&dummy_data,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let read = storage::delegations()
|
||||
.idx
|
||||
.owner
|
||||
.prefix(delegation_owner)
|
||||
.range(&deps.storage, None, None, Order::Ascending)
|
||||
.map(|record| record.unwrap().1)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(1, read.len());
|
||||
assert_eq!(dummy_data, read[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reverse_mix_delegation_returns_none_if_delegation_doesnt_exist() {
|
||||
let mut deps = test_helpers::init_contract();
|
||||
|
||||
let node_identity1: IdentityKey = "foo1".into();
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner1 = Addr::unchecked("bar");
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
let delegation = coin(12345, DENOM);
|
||||
|
||||
assert!(test_helpers::read_delegation(
|
||||
deps.as_ref().storage,
|
||||
&node_identity1,
|
||||
&delegation_owner1
|
||||
)
|
||||
.is_none());
|
||||
|
||||
// add delegation for a different node
|
||||
let dummy_data = Delegation::new(
|
||||
delegation_owner1.clone(),
|
||||
node_identity2.clone(),
|
||||
delegation.clone(),
|
||||
mock_env().block.height,
|
||||
None,
|
||||
);
|
||||
storage::delegations()
|
||||
.save(
|
||||
&mut deps.storage,
|
||||
(node_identity1.clone(), delegation_owner1.clone()).joined_key(),
|
||||
&dummy_data,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
storage::delegations()
|
||||
.idx
|
||||
.owner
|
||||
.prefix(delegation_owner1.clone())
|
||||
.range(&deps.storage, None, None, Order::Ascending)
|
||||
.map(|record| record.unwrap().1)
|
||||
.for_each(|delegation| assert_ne!(delegation.node_identity, node_identity1));
|
||||
|
||||
// add delegation from a different owner
|
||||
let dummy_data = Delegation::new(
|
||||
delegation_owner2.clone(),
|
||||
node_identity1.clone(),
|
||||
delegation.clone(),
|
||||
mock_env().block.height,
|
||||
None,
|
||||
);
|
||||
storage::delegations()
|
||||
.save(
|
||||
&mut deps.storage,
|
||||
(node_identity1.clone(), delegation_owner2.clone()).joined_key(),
|
||||
&dummy_data,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
storage::delegations()
|
||||
.idx
|
||||
.owner
|
||||
.prefix(delegation_owner1.clone())
|
||||
.range(&deps.storage, None, None, Order::Ascending)
|
||||
.map(|record| record.unwrap().1)
|
||||
.for_each(|delegation| assert_ne!(delegation.node_identity, node_identity1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,862 @@
|
||||
// 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
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -24,9 +24,6 @@ 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 },
|
||||
|
||||
@@ -54,9 +51,6 @@ 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,
|
||||
|
||||
@@ -72,9 +66,6 @@ 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,
|
||||
@@ -101,4 +92,19 @@ 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),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod queries;
|
||||
pub mod storage;
|
||||
pub mod transactions;
|
||||
@@ -0,0 +1,212 @@
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// 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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,569 @@
|
||||
// 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 {}));
|
||||
}
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
// 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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod contract;
|
||||
pub mod error;
|
||||
pub(crate) mod helpers;
|
||||
pub mod queries;
|
||||
pub mod state;
|
||||
pub(crate) mod storage;
|
||||
pub mod support;
|
||||
pub mod transactions;
|
||||
mod delegations;
|
||||
mod error;
|
||||
mod gateways;
|
||||
mod mixnet_contract_settings;
|
||||
mod mixnodes;
|
||||
mod rewards;
|
||||
mod support;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// 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;
|
||||
+3
-3
@@ -2,15 +2,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::Addr;
|
||||
use mixnet_contract::StateParams;
|
||||
use mixnet_contract::ContractStateParams;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct State {
|
||||
pub struct ContractState {
|
||||
pub owner: Addr, // only the owner account can update state
|
||||
pub rewarding_validator_address: Addr,
|
||||
pub params: StateParams,
|
||||
pub params: ContractStateParams,
|
||||
|
||||
// keep track of the changes to the current rewarding interval,
|
||||
// i.e. at which block has the latest rewarding occurred
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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(|_| ())
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// 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;
|
||||
@@ -0,0 +1,200 @@
|
||||
// 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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,640 @@
|
||||
// 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
@@ -0,0 +1,83 @@
|
||||
// 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(())
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// 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;
|
||||
@@ -0,0 +1,270 @@
|
||||
// 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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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)
|
||||
}
|
||||
+680
-707
File diff suppressed because it is too large
Load Diff
@@ -1,593 +0,0 @@
|
||||
// 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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// 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 +1,5 @@
|
||||
pub mod tests;
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) mod helpers;
|
||||
pub(crate) mod tests;
|
||||
|
||||
@@ -1,42 +1,78 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod helpers {
|
||||
pub mod test_helpers {
|
||||
use super::*;
|
||||
use crate::contract::{instantiate, INITIAL_MIXNODE_BOND};
|
||||
use crate::contract::{instantiate, INITIAL_MIXNODE_PLEDGE};
|
||||
use crate::contract::{
|
||||
query, DEFAULT_SYBIL_RESISTANCE_PERCENT, EPOCH_REWARD_PERCENT, INITIAL_REWARD_POOL,
|
||||
};
|
||||
use crate::storage::StoredMixnodeBond;
|
||||
use crate::transactions::{try_add_gateway, try_add_mixnode};
|
||||
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 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::{
|
||||
Gateway, GatewayBond, InstantiateMsg, Layer, MixNode, MixNodeBond, PagedGatewayResponse,
|
||||
PagedMixnodeResponse, QueryMsg, RawDelegationData,
|
||||
Delegation, ExecuteMsg, Gateway, GatewayBond, IdentityKey, IdentityKeyRef, InstantiateMsg,
|
||||
Layer, MixNode, MixNodeBond, PagedGatewayResponse, PagedMixnodeResponse, QueryMsg,
|
||||
};
|
||||
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 = format!("{}mixnode", sender);
|
||||
let key = keypair.public_key().to_base58_string();
|
||||
|
||||
try_add_mixnode(
|
||||
deps,
|
||||
mock_env(),
|
||||
info,
|
||||
MixNode {
|
||||
identity_key: key.clone(),
|
||||
..helpers::mix_node_fixture()
|
||||
..test_helpers::mix_node_fixture()
|
||||
},
|
||||
owner_signature,
|
||||
)
|
||||
.unwrap();
|
||||
key
|
||||
@@ -59,21 +95,44 @@ pub mod helpers {
|
||||
page.nodes
|
||||
}
|
||||
|
||||
pub fn add_gateway(
|
||||
sender: &str,
|
||||
stake: Vec<Coin>,
|
||||
deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier>,
|
||||
) -> String {
|
||||
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();
|
||||
|
||||
let info = mock_info(sender, &stake);
|
||||
let key = format!("{}gateway", sender);
|
||||
let key = keypair.public_key().to_base58_string();
|
||||
try_add_gateway(
|
||||
deps.as_mut(),
|
||||
deps,
|
||||
mock_env(),
|
||||
info,
|
||||
Gateway {
|
||||
identity_key: key.clone(),
|
||||
..helpers::gateway_fixture()
|
||||
..test_helpers::gateway_fixture()
|
||||
},
|
||||
owner_signature,
|
||||
)
|
||||
.unwrap();
|
||||
key
|
||||
@@ -97,12 +156,14 @@ pub mod helpers {
|
||||
}
|
||||
|
||||
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let msg = InstantiateMsg {};
|
||||
let mut deps = mock_dependencies();
|
||||
let msg = InstantiateMsg {
|
||||
rewarding_validator_address: config::defaults::REWARDING_VALIDATOR_ADDRESS.to_string(),
|
||||
};
|
||||
let env = mock_env();
|
||||
let info = mock_info("creator", &[]);
|
||||
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
|
||||
return deps;
|
||||
deps
|
||||
}
|
||||
|
||||
pub fn mix_node_fixture() -> MixNode {
|
||||
@@ -114,36 +175,21 @@ pub mod helpers {
|
||||
sphinx_key: "sphinx".to_string(),
|
||||
identity_key: "identity".to_string(),
|
||||
version: "0.10.0".to_string(),
|
||||
profit_margin_percent: 10,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mixnode_bond_fixture() -> MixNodeBond {
|
||||
let mix_node = MixNode {
|
||||
host: "1.1.1.1".to_string(),
|
||||
mix_port: 1789,
|
||||
verloc_port: 1790,
|
||||
http_api_port: 8000,
|
||||
sphinx_key: "1234".to_string(),
|
||||
identity_key: "aaaa".to_string(),
|
||||
version: "0.10.0".to_string(),
|
||||
};
|
||||
MixNodeBond::new(
|
||||
coin(50, DENOM),
|
||||
Addr::unchecked("foo"),
|
||||
Layer::One,
|
||||
12_345,
|
||||
mix_node,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn stored_mixnode_bond_fixture() -> StoredMixnodeBond {
|
||||
pub(crate) fn stored_mixnode_bond_fixture(owner: &str) -> mixnodes_storage::StoredMixnodeBond {
|
||||
StoredMixnodeBond::new(
|
||||
coin(50, DENOM),
|
||||
Addr::unchecked("foo"),
|
||||
Addr::unchecked(owner),
|
||||
Layer::One,
|
||||
12_345,
|
||||
mix_node_fixture(),
|
||||
MixNode {
|
||||
identity_key: format!("id-{}", owner),
|
||||
..mix_node_fixture()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
@@ -154,28 +200,24 @@ pub mod helpers {
|
||||
mix_port: 1789,
|
||||
clients_port: 9000,
|
||||
location: "Sweden".to_string(),
|
||||
|
||||
sphinx_key: "sphinx".to_string(),
|
||||
identity_key: "identity".to_string(),
|
||||
version: "0.10.0".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gateway_bond_fixture() -> GatewayBond {
|
||||
pub fn gateway_bond_fixture(owner: &str) -> GatewayBond {
|
||||
let gateway = Gateway {
|
||||
host: "1.1.1.1".to_string(),
|
||||
mix_port: 1789,
|
||||
clients_port: 9000,
|
||||
location: "London".to_string(),
|
||||
sphinx_key: "sphinx".to_string(),
|
||||
identity_key: "identity".to_string(),
|
||||
version: "0.10.0".to_string(),
|
||||
identity_key: format!("id-{}", owner),
|
||||
..gateway_fixture()
|
||||
};
|
||||
GatewayBond::new(coin(50, DENOM), Addr::unchecked("foo"), 12_345, gateway)
|
||||
}
|
||||
|
||||
pub fn raw_delegation_fixture(amount: u128) -> RawDelegationData {
|
||||
RawDelegationData::new(Uint128(amount), 42)
|
||||
GatewayBond::new(
|
||||
coin(50, DENOM),
|
||||
Addr::unchecked(owner),
|
||||
12_345,
|
||||
gateway,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn query_contract_balance(
|
||||
@@ -189,14 +231,14 @@ pub mod helpers {
|
||||
pub fn good_mixnode_bond() -> Vec<Coin> {
|
||||
vec![Coin {
|
||||
denom: DENOM.to_string(),
|
||||
amount: INITIAL_MIXNODE_BOND,
|
||||
amount: INITIAL_MIXNODE_PLEDGE,
|
||||
}]
|
||||
}
|
||||
|
||||
pub fn good_gateway_bond() -> Vec<Coin> {
|
||||
vec![Coin {
|
||||
denom: DENOM.to_string(),
|
||||
amount: INITIAL_MIXNODE_BOND,
|
||||
amount: INITIAL_MIXNODE_PLEDGE,
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -205,10 +247,50 @@ pub mod 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
Generated
+908
@@ -0,0 +1,908 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "az"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"byte-tools",
|
||||
"byteorder",
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
|
||||
dependencies = [
|
||||
"byte-tools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"handlebars",
|
||||
"humantime-serde",
|
||||
"network-defaults",
|
||||
"serde",
|
||||
"toml",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
"k256",
|
||||
"rand_core 0.5.1",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
"cosmwasm-derive",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
"thiserror",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-storage"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"digest 0.9.0",
|
||||
"rand_core 0.5.1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||
dependencies = [
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372"
|
||||
dependencies = [
|
||||
"der",
|
||||
"elliptic-curve",
|
||||
"hmac",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-zebra"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"hex",
|
||||
"rand_core 0.5.1",
|
||||
"serde",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b"
|
||||
dependencies = [
|
||||
"crypto-bigint",
|
||||
"ff",
|
||||
"generic-array 0.14.4",
|
||||
"group",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f"
|
||||
dependencies = [
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d333a26ec13a023c6dff4b7584de4d323cfee2e508f5dd2bbee6669e4f7efdf"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bytemuck",
|
||||
"half",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
|
||||
[[package]]
|
||||
name = "handlebars"
|
||||
version = "3.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"quick-error",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex-literal"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
|
||||
dependencies = [
|
||||
"crypto-mac",
|
||||
"digest 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "humantime-serde"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "mixnet-contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"az",
|
||||
"cosmwasm-std",
|
||||
"fixed",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "network-defaults"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal",
|
||||
"serde",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||
dependencies = [
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
|
||||
dependencies = [
|
||||
"maplit",
|
||||
"pest",
|
||||
"sha-1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271ac0c667b8229adf70f0f957697c96fafd7486ab7481e15dc5e45e3e6a4368"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ebda811090b257411540779860bc09bf321bc587f58d2c5864309d1566214e7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-json-wasm"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50eef3672ec8fa45f3457fd423ba131117786784a895548021976117c1ded449"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
||||
dependencies = [
|
||||
"block-buffer 0.7.3",
|
||||
"digest 0.8.1",
|
||||
"fake-simd",
|
||||
"opaque-debug 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32"
|
||||
dependencies = [
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||
|
||||
[[package]]
|
||||
name = "uint"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crunchy",
|
||||
"hex",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "vesting-contracts"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"mixnet-contract",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
|
||||
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "vesting-contract"
|
||||
version = "0.1.0"
|
||||
authors = ["Drazen Urch <durch@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
|
||||
exclude = [
|
||||
# Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication.
|
||||
"contract.wasm",
|
||||
"hash.txt",
|
||||
]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
# for more explicit tests, cargo test --features=backtraces
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
|
||||
[dependencies]
|
||||
mixnet-contract = { path = "../../common/mixnet-contract" }
|
||||
config = { path = "../../common/config" }
|
||||
|
||||
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
|
||||
cosmwasm-std = { version = "1.0.0-beta2", features = ["iterator"]}
|
||||
cw-storage-plus = { version = "0.10.3", features = ["iterator"] }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0.23" }
|
||||
@@ -0,0 +1,2 @@
|
||||
wasm:
|
||||
RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
|
||||
@@ -0,0 +1,11 @@
|
||||
## Nym vesting contract
|
||||
|
||||
1. Initial vesting tokens are deposited and assigned to existing addresses via `CreatePeriodicVestingAccount`
|
||||
2. Admin account can then delegate vested and unvested tokens to mixnodes on behalf of vesting accounts
|
||||
3. Vesting accounts can withdraw vested and undelegated (spendable) coins to their addresses
|
||||
|
||||
### Vesting coin delegation flow
|
||||
|
||||

|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 392 KiB |
@@ -0,0 +1,403 @@
|
||||
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())
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use cosmwasm_std::{Addr, StdError};
|
||||
use mixnet_contract::IdentityKey;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum ContractError {
|
||||
#[error("{0}")]
|
||||
Std(#[from] StdError),
|
||||
#[error("Account does not exist - {0}")]
|
||||
NoAccountForAddress(String),
|
||||
#[error("Only admin can perform this action, {0} is not admin")]
|
||||
NotAdmin(String),
|
||||
#[error("Balance not found for existing account ({0}), this is a bug")]
|
||||
NoBalanceForAddress(String),
|
||||
#[error("Insufficient balance for address {0} -> {1}")]
|
||||
InsufficientBalance(String, u128),
|
||||
#[error("Insufficient spendable balance for address {0} -> {1}")]
|
||||
InsufficientSpendable(String, u128),
|
||||
#[error(
|
||||
"Only delegation owner can perform delegation actions, {0} is not the delegation owner"
|
||||
)]
|
||||
NotDelegate(String),
|
||||
#[error("Total vesting amount is inprobably low -> {0}, this is likely an error")]
|
||||
ImprobableVestingAmount(u128),
|
||||
#[error("Address {0} has already bonded a node")]
|
||||
AlreadyBonded(String),
|
||||
#[error("Recieved empty funds vector")]
|
||||
EmptyFunds,
|
||||
#[error("Recieved wrong denom: {0}, expected {1}")]
|
||||
WrongDenom(String, String),
|
||||
#[error("Recieved multiple denoms, expected 1")]
|
||||
MultipleDenoms,
|
||||
#[error("No delegations found for account {0}, mix_identity {1}")]
|
||||
NoSuchDelegation(Addr, IdentityKey),
|
||||
#[error("Only mixnet contract can perform this operation, got {0}")]
|
||||
NotMixnetContract(Addr),
|
||||
#[error("Calculation underflowed")]
|
||||
Underflow,
|
||||
#[error("No bond found for account {0}")]
|
||||
NoBondFound(String),
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod contract;
|
||||
mod errors;
|
||||
pub mod messages;
|
||||
mod storage;
|
||||
mod support;
|
||||
mod traits;
|
||||
mod vesting;
|
||||
@@ -0,0 +1,88 @@
|
||||
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,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use crate::errors::ContractError;
|
||||
use crate::vesting::Account;
|
||||
use cosmwasm_std::{Addr, Api, Storage};
|
||||
use cw_storage_plus::Map;
|
||||
|
||||
const ACCOUNTS: Map<Addr, Account> = Map::new("acc");
|
||||
|
||||
pub fn save_account(account: &Account, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
Ok(ACCOUNTS.save(storage, account.address(), account)?)
|
||||
}
|
||||
|
||||
pub fn load_account(
|
||||
address: &Addr,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Option<Account>, ContractError> {
|
||||
Ok(ACCOUNTS.may_load(storage, address.to_owned())?)
|
||||
}
|
||||
|
||||
fn validate_account(address: &Addr, storage: &dyn Storage) -> Result<Account, ContractError> {
|
||||
load_account(address, storage)?
|
||||
.ok_or_else(|| ContractError::NoAccountForAddress(address.as_str().to_string()))
|
||||
}
|
||||
|
||||
pub fn account_from_address(
|
||||
address: &str,
|
||||
storage: &dyn Storage,
|
||||
api: &dyn Api,
|
||||
) -> Result<Account, ContractError> {
|
||||
validate_account(&api.addr_validate(address)?, storage)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod tests;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user