Compare commits

..

1 Commits

Author SHA1 Message Date
Jon Häggblad 21f1fa94de wip 2025-03-21 17:23:56 +01:00
370 changed files with 12835 additions and 7496 deletions
@@ -0,0 +1,6 @@
[
{
"rust":"stable",
"runOnEvent":"always"
}
]
@@ -31,6 +31,7 @@ jobs:
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: 1.77
target: wasm32-unknown-unknown
override: true
@@ -39,9 +40,6 @@ jobs:
with:
version: '114'
- name: Install cosmwasm-check
run: cargo install cosmwasm-check
- name: Build release contracts
run: make contracts
@@ -52,6 +50,7 @@ jobs:
run: |
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_bandwidth.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_dkg.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/cw3_flex_multisig.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR
+17 -13
View File
@@ -14,12 +14,28 @@ on:
- '.github/workflows/ci-contracts.yml'
jobs:
matrix_prep:
runs-on: ubuntu-20.04
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from ci-contracts-matrix-includes.json
- uses: actions/checkout@v4
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/ci-contracts-matrix-includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
build:
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-20.04
env:
CARGO_TERM_COLOR: always
needs: matrix_prep
strategy:
fail-fast: false
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
steps:
- uses: actions/checkout@v4
@@ -27,19 +43,11 @@ jobs:
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
toolchain: ${{ matrix.rust }}
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- name: Install cosmwasm-check
run: cargo install cosmwasm-check
- name: Install wasm-opt
uses: ./.github/actions/install-wasm-opt
with:
version: '114'
- name: Build contracts
uses: actions-rs/cargo@v1
env:
@@ -65,7 +73,3 @@ jobs:
with:
command: clippy
args: --lib --manifest-path contracts/Cargo.toml --workspace --all-targets -- -D warnings
- name: Check chain compatibility against release build
# this will build contracts in release mode, run wasm-opt and finally cosmwasm-check
run: make contracts
+52 -52
View File
@@ -10,66 +10,66 @@ jobs:
build:
runs-on: custom-linux
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
- name: Install rsync
run: sudo apt-get install rsync
continue-on-error: true
- name: Install rsync
run: sudo apt-get install rsync
continue-on-error: true
- uses: rlespinasse/github-slug-action@v3.x
- uses: rlespinasse/github-slug-action@v3.x
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Setup yarn
run: npm install -g yarn
- name: Setup yarn
run: npm install -g yarn
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Build dependencies
run: yarn && yarn build
- name: Build dependencies
run: yarn && yarn build
- name: Build storybook
run: yarn storybook:build
working-directory: ./nym-wallet
- name: Build storybook
run: yarn storybook:build
working-directory: ./nym-wallet
- name: Deploy branch to CI www (storybook)
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "nym-wallet/storybook-static/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/wallet-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Deploy branch to CI www (storybook)
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "nym-wallet/storybook-static/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/wallet-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Matrix - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
env:
NYM_NOTIFICATION_KIND: nym-wallet
NYM_PROJECT_NAME: "nym-wallet"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "wallet-${{ env.GITHUB_REF_SLUG }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
IS_SUCCESS: "${{ job.status == 'success' }}"
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
- name: Matrix - Send Notification
env:
NYM_NOTIFICATION_KIND: nym-wallet
NYM_PROJECT_NAME: "nym-wallet"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "wallet-${{ env.GITHUB_REF_SLUG }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
IS_SUCCESS: "${{ job.status == 'success' }}"
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+3 -2
View File
@@ -2,18 +2,19 @@ name: publish-nym-contracts
on:
workflow_dispatch:
release:
types: [ created ]
types: [created]
jobs:
build:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-contracts-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
runs-on: [self-hosted, custom-ubuntu-20.04]
steps:
- uses: actions/checkout@v4
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: 1.77
target: wasm32-unknown-unknown
override: true
@@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [macos-15]
platform: [macos-12-large]
runs-on: ${{ matrix.platform }}
outputs:
@@ -30,7 +30,7 @@ jobs:
- name: Node
uses: actions/setup-node@v4
with:
node-version: 21
node-version: 18
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
@@ -36,7 +36,7 @@ jobs:
- name: Node
uses: actions/setup-node@v4
with:
node-version: 21
node-version: 18
- name: Install Rust stable
uses: actions-rs/toolchain@v1
@@ -49,7 +49,7 @@ jobs:
- name: Node
uses: actions/setup-node@v4
with:
node-version: 21
node-version: 18
- name: Install Rust stable
uses: actions-rs/toolchain@v1
-76
View File
@@ -4,82 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.6-chuckles] (2025-03-31)
- Remove Google public DNS ([#5660])
- Revert using AsyncWrite sink in IPR ([#5656])
- Add fd callback for initial authentication ([#5654])
- Add concurrency limit to CI ([#5651])
- Remove UNIQUE constraint on node pubkey ([#5649])
- Add RUSTUP_PERMIT_COPY_RENAME in two workflows that we forgot about ([#5646])
- Upgrade sha2 to workspace version for validator-client ([#5644])
- Add max_retransmissions flag on each message ([#5642])
- build(deps): bump zip from 2.2.2 to 2.4.1 ([#5639])
- build(deps): bump dtolnay/rust-toolchain from 1.90.0 to 1.100.0 ([#5638])
- / regenerated yarn.lock ([#5636])
- Rework IPR codec to extract out timer and implement AsyncWrite ([#5632])
- build(deps): bump tempfile from 3.18.0 to 3.19.0 ([#5631])
- build(deps): bump zeroize from 1.6.0 to 1.8.1 ([#5630])
- build(deps): bump once_cell from 1.20.3 to 1.21.1 ([#5629])
- build(deps): bump uuid from 1.15.1 to 1.16.0 ([#5628])
- build(deps): bump celes from 2.5.0 to 2.6.0 ([#5627])
- build(deps): bump http from 1.2.0 to 1.3.1 ([#5626])
- build(deps): bump humantime from 2.1.0 to 2.2.0 ([#5625])
- build(deps): bump the patch-updates group with 8 updates ([#5624])
- build(deps): bump @babel/runtime from 7.16.3 to 7.26.10 in /testnet-faucet ([#5621])
- Feature/paginated ticketbooks challenge ([#5619])
- build(deps-dev): bump webpack from 5.77.0 to 5.98.0 in /wasm/client/internal-dev ([#5615])
- build(deps-dev): bump ws from 8.13.0 to 8.18.1 in /wasm/client/internal-dev ([#5614])
- build(deps): bump golang.org/x/net from 0.23.0 to 0.36.0 in /wasm/mix-fetch/go-mix-conn ([#5613])
- build(deps): bump braces from 3.0.2 to 3.0.3 in /sdk/typescript/packages/mix-fetch-node ([#5612])
- Wireguard exit policies (and tests) ([#5557])
- Explorer V2 ([#5548])
- Clean stale partially received buffers ([#5536])
- Corrected typos ([#5497])
- build(deps): bump @octokit/plugin-paginate-rest and @actions/github in /.github/actions/nym-hash-releases/src ([#5488])
- feature: upgrade cosmwasm to 2.2 ([#5479])
- build(deps): bump store2 from 2.14.3 to 2.14.4 ([#5391])
- build(deps): bump nanoid from 3.3.7 to 3.3.8 in /documentation/docs ([#5335])
- build(deps): bump next from 13.5.7 to 14.2.15 in /documentation/docs ([#5281])
- Bump http-proxy-middleware from 2.0.6 to 2.0.7 ([#5019])
[#5660]: https://github.com/nymtech/nym/pull/5660
[#5656]: https://github.com/nymtech/nym/pull/5656
[#5654]: https://github.com/nymtech/nym/pull/5654
[#5651]: https://github.com/nymtech/nym/pull/5651
[#5649]: https://github.com/nymtech/nym/pull/5649
[#5646]: https://github.com/nymtech/nym/pull/5646
[#5644]: https://github.com/nymtech/nym/pull/5644
[#5642]: https://github.com/nymtech/nym/pull/5642
[#5639]: https://github.com/nymtech/nym/pull/5639
[#5638]: https://github.com/nymtech/nym/pull/5638
[#5636]: https://github.com/nymtech/nym/pull/5636
[#5632]: https://github.com/nymtech/nym/pull/5632
[#5631]: https://github.com/nymtech/nym/pull/5631
[#5630]: https://github.com/nymtech/nym/pull/5630
[#5629]: https://github.com/nymtech/nym/pull/5629
[#5628]: https://github.com/nymtech/nym/pull/5628
[#5627]: https://github.com/nymtech/nym/pull/5627
[#5626]: https://github.com/nymtech/nym/pull/5626
[#5625]: https://github.com/nymtech/nym/pull/5625
[#5624]: https://github.com/nymtech/nym/pull/5624
[#5621]: https://github.com/nymtech/nym/pull/5621
[#5619]: https://github.com/nymtech/nym/pull/5619
[#5615]: https://github.com/nymtech/nym/pull/5615
[#5614]: https://github.com/nymtech/nym/pull/5614
[#5613]: https://github.com/nymtech/nym/pull/5613
[#5612]: https://github.com/nymtech/nym/pull/5612
[#5557]: https://github.com/nymtech/nym/pull/5557
[#5548]: https://github.com/nymtech/nym/pull/5548
[#5536]: https://github.com/nymtech/nym/pull/5536
[#5497]: https://github.com/nymtech/nym/pull/5497
[#5488]: https://github.com/nymtech/nym/pull/5488
[#5479]: https://github.com/nymtech/nym/pull/5479
[#5391]: https://github.com/nymtech/nym/pull/5391
[#5335]: https://github.com/nymtech/nym/pull/5335
[#5281]: https://github.com/nymtech/nym/pull/5281
[#5019]: https://github.com/nymtech/nym/pull/5019
## [2025.5-chokito] (2025-03-18)
- build(deps): bump braces from 3.0.2 to 3.0.3 in /sdk/typescript/packages/nodejs-client ([#5611])
Generated
+275 -321
View File
File diff suppressed because it is too large Load Diff
+14 -12
View File
@@ -32,8 +32,9 @@ members = [
"common/client-libs/validator-client",
"common/commands",
"common/config",
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
"common/cosmwasm-smart-contracts/coconut-dkg",
"common/cosmwasm-smart-contracts/contracts-common", "common/cosmwasm-smart-contracts/easy_addr",
"common/cosmwasm-smart-contracts/contracts-common",
"common/cosmwasm-smart-contracts/ecash-contract",
"common/cosmwasm-smart-contracts/group-contract",
"common/cosmwasm-smart-contracts/mixnet-contract",
@@ -360,7 +361,7 @@ vergen = { version = "=8.3.1", default-features = false }
walkdir = "2"
wasm-bindgen-test = "0.3.49"
x25519-dalek = "2.0.0"
zeroize = "1.7.0"
zeroize = "1.8.1"
prometheus = { version = "0.13.0" }
@@ -374,18 +375,19 @@ ff = { version = "0.13.1", default-features = false }
subtle = "2.5.0"
# cosmwasm-related
cosmwasm-schema = "=2.2.2"
cosmwasm-std = "=2.2.2"
# use 1.0.1 as that's the version used by cosmwasm-std 2.2.1
cosmwasm-schema = "=1.4.3"
cosmwasm-std = "=1.4.3"
# use 0.5.0 as that's the version used by cosmwasm-std 1.4.3
# (and ideally we don't want to pull the same dependency twice)
serde-json-wasm = "=1.0.1"
serde-json-wasm = "=0.5.0"
cosmwasm-storage = "=1.4.3"
# same version as used by cosmwasm
cw-utils = "=2.0.0"
cw-storage-plus = "=2.0.0"
cw2 = { version = "=2.0.0" }
cw3 = { version = "=2.0.0" }
cw4 = { version = "=2.0.0" }
cw-controllers = { version = "=2.0.0" }
cw-utils = "=1.0.1"
cw-storage-plus = "=1.2.0"
cw2 = { version = "=1.1.2" }
cw3 = { version = "=1.1.2" }
cw4 = { version = "=1.1.2" }
cw-controllers = { version = "=1.1.0" }
# cosmrs-related
bip32 = { version = "0.5.3", default-features = false }
+18 -8
View File
@@ -133,22 +133,17 @@ clippy: sdk-wasm-lint
# Build contracts ready for deploy
# -----------------------------------------------------------------------------
CONTRACTS=vesting_contract mixnet_contract nym_ecash cw3_flex_multisig cw4_group nym_coconut_dkg
CONTRACTS=vesting_contract mixnet_contract nym_ecash
CONTRACTS_WASM=$(addsuffix .wasm, $(CONTRACTS))
CONTRACTS_OUT_DIR=contracts/target/wasm32-unknown-unknown/release
contracts: build-release-contracts wasm-opt-contracts cosmwasm-check-contracts
contracts: build-release-contracts wasm-opt-contracts
wasm-opt-contracts:
for contract in $(CONTRACTS_WASM); do \
wasm-opt --signext-lowering -Os $(CONTRACTS_OUT_DIR)/$$contract -o $(CONTRACTS_OUT_DIR)/$$contract; \
done
cosmwasm-check-contracts:
for contract in $(CONTRACTS_WASM); do \
cosmwasm-check $(CONTRACTS_OUT_DIR)/$$contract; \
done
# Consider adding 's' to make plural consistent (beware: used in github workflow)
contract-schema:
$(MAKE) -C contracts schema
@@ -157,9 +152,18 @@ contract-schema:
# Convenience targets for crates that are already part of the main workspace
# -----------------------------------------------------------------------------
build-explorer-api:
cargo build -p explorer-api
build-nym-cli:
cargo build -p nym-cli --release
build-nym-gateway:
cargo build -p nym-gateway --release
build-nym-mixnode:
cargo build -p nym-mixnode --release
# -----------------------------------------------------------------------------
# Misc
# -----------------------------------------------------------------------------
@@ -172,7 +176,13 @@ run-api-tests:
cd nym-api/tests/functional_test && yarn test:qa
# Build debian package, and update PPA
deb-mixnode: build-nym-mixnode
cargo deb -p nym-mixnode
deb-gateway: build-nym-gateway
cargo deb -p nym-gateway
deb-cli: build-nym-cli
cargo deb -p nym-cli
deb: deb-cli
deb: deb-mixnode deb-gateway deb-cli
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.52"
version = "1.1.51"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.52"
version = "1.1.51"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -139,8 +139,6 @@ where
let gateway_setup = GatewaySetup::New {
specification: selection_spec,
available_gateways,
#[cfg(unix)]
connection_fd_callback: None,
};
let init_details =
@@ -187,8 +187,6 @@ where
let gateway_setup = GatewaySetup::New {
specification: selection_spec,
available_gateways,
#[cfg(unix)]
connection_fd_callback: None,
};
let init_details =
+2 -10
View File
@@ -11,8 +11,6 @@ use nym_topology::node::RoutingNode;
use nym_validator_client::client::IdentityKeyRef;
use nym_validator_client::UserAgent;
use rand::{seq::SliceRandom, Rng};
#[cfg(unix)]
use std::os::fd::RawFd;
use std::{sync::Arc, time::Duration};
use tungstenite::Message;
use url::Url;
@@ -315,15 +313,9 @@ pub(super) async fn register_with_gateway(
gateway_id: identity::PublicKey,
gateway_listener: Url,
our_identity: Arc<identity::KeyPair>,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
) -> Result<RegistrationResult, ClientCoreError> {
let mut gateway_client = GatewayClient::new_init(
gateway_listener,
gateway_id,
our_identity.clone(),
#[cfg(unix)]
connection_fd_callback,
);
let mut gateway_client =
GatewayClient::new_init(gateway_listener, gateway_id, our_identity.clone());
gateway_client.establish_connection().await.map_err(|err| {
log::warn!("Failed to establish connection with gateway!");
+4 -22
View File
@@ -23,8 +23,6 @@ use nym_topology::node::RoutingNode;
use rand::rngs::OsRng;
use rand::{CryptoRng, RngCore};
use serde::Serialize;
#[cfg(unix)]
use std::{os::fd::RawFd, sync::Arc};
pub mod helpers;
pub mod types;
@@ -55,7 +53,6 @@ async fn setup_new_gateway<K, D>(
details_store: &D,
selection_specification: GatewaySelectionSpecification,
available_gateways: Vec<RoutingNode>,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
) -> Result<InitialisationResult, ClientCoreError>
where
K: KeyStore,
@@ -111,14 +108,9 @@ where
// if we're using a 'normal' gateway setup, do register
let our_identity = client_keys.identity_keypair();
let registration = helpers::register_with_gateway(
gateway_id,
gateway_listener.clone(),
our_identity,
#[cfg(unix)]
connection_fd_callback,
)
.await?;
let registration =
helpers::register_with_gateway(gateway_id, gateway_listener.clone(), our_identity)
.await?;
(
GatewayDetails::new_remote(
gateway_id,
@@ -211,19 +203,9 @@ where
GatewaySetup::New {
specification,
available_gateways,
#[cfg(unix)]
connection_fd_callback,
} => {
log::debug!("GatewaySetup::New with spec: {specification:?}");
setup_new_gateway(
key_store,
details_store,
specification,
available_gateways,
#[cfg(unix)]
connection_fd_callback,
)
.await
setup_new_gateway(key_store, details_store, specification, available_gateways).await
}
GatewaySetup::ReuseConnection {
authenticated_ephemeral_client,
-10
View File
@@ -18,8 +18,6 @@ use nym_validator_client::client::IdentityKey;
use nym_validator_client::nyxd::AccountId;
use serde::Serialize;
use std::fmt::{Debug, Display};
#[cfg(unix)]
use std::os::fd::RawFd;
use std::sync::Arc;
use time::OffsetDateTime;
use url::Url;
@@ -210,10 +208,6 @@ pub enum GatewaySetup {
// TODO: seems to be a bit inefficient to pass them by value
available_gateways: Vec<RoutingNode>,
/// Callback useful for allowing initial connection to gateway
#[cfg(unix)]
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
},
ReuseConnection {
@@ -237,8 +231,6 @@ impl Debug for GatewaySetup {
GatewaySetup::New {
specification,
available_gateways,
#[cfg(unix)]
connection_fd_callback: _,
} => f
.debug_struct("GatewaySetup::New")
.field("specification", specification)
@@ -278,8 +270,6 @@ impl GatewaySetup {
additional_data: None,
},
available_gateways: vec![],
#[cfg(unix)]
connection_fd_callback: None,
}
}
@@ -1065,7 +1065,6 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
gateway_listener: Url,
gateway_identity: identity::PublicKey,
local_identity: Arc<identity::KeyPair>,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
) -> Self {
log::trace!("Initialising gateway client");
use futures::channel::mpsc;
@@ -1091,7 +1090,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
stats_reporter: ClientStatsSender::new(None, task_client.clone()),
negotiated_protocol: None,
#[cfg(unix)]
connection_fd_callback,
connection_fd_callback: None,
task_client,
}
}
@@ -16,6 +16,7 @@ nym-coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" }
nym-contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" }
nym-mixnet-contract-common = { path = "../../cosmwasm-smart-contracts/mixnet-contract" }
nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-contract" }
nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-ecash-contract-common = { path = "../../cosmwasm-smart-contracts/ecash-contract" }
nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
@@ -11,9 +11,7 @@ use crate::{
use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
IssuedTicketbooksChallengeCommitmentRequest, IssuedTicketbooksChallengeCommitmentResponse,
IssuedTicketbooksDataRequest, IssuedTicketbooksDataResponse, IssuedTicketbooksForCountResponse,
IssuedTicketbooksForResponse, VerifyEcashTicketBody,
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, VerifyEcashTicketBody,
};
use nym_api_requests::ecash::{
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
@@ -27,14 +25,15 @@ use nym_api_requests::models::{
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::nym_nodes::{NodesByAddressesResponse, SkimmedNode};
use nym_coconut_dkg_common::types::EpochId;
use nym_ecash_contract_common::deposit::DepositId;
use nym_http_api_client::UserAgent;
use nym_mixnet_contract_common::EpochRewardedSet;
use nym_network_defaults::NymNetworkDetails;
use std::net::IpAddr;
use time::Date;
use url::Url;
pub use crate::nym_api::NymApiClientExt;
use nym_mixnet_contract_common::EpochRewardedSet;
pub use nym_mixnet_contract_common::{
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId, NymNodeDetails,
};
@@ -702,33 +701,17 @@ impl NymApiClient {
Ok(self.nym_api.issued_ticketbooks_for(expiration_date).await?)
}
pub async fn issued_ticketbooks_for_count(
pub async fn issued_ticketbooks_challenge(
&self,
expiration_date: Date,
) -> Result<IssuedTicketbooksForCountResponse, ValidatorClientError> {
deposits: Vec<DepositId>,
) -> Result<IssuedTicketbooksChallengeResponse, ValidatorClientError> {
Ok(self
.nym_api
.issued_ticketbooks_for_count(expiration_date)
.issued_ticketbooks_challenge(expiration_date, deposits)
.await?)
}
pub async fn issued_ticketbooks_challenge_commitment(
&self,
request: &IssuedTicketbooksChallengeCommitmentRequest,
) -> Result<IssuedTicketbooksChallengeCommitmentResponse, ValidatorClientError> {
Ok(self
.nym_api
.issued_ticketbooks_challenge_commitment(request)
.await?)
}
pub async fn issued_ticketbooks_data(
&self,
request: &IssuedTicketbooksDataRequest,
) -> Result<IssuedTicketbooksDataResponse, ValidatorClientError> {
Ok(self.nym_api.issued_ticketbooks_data(request).await?)
}
pub async fn nodes_by_addresses(
&self,
addresses: Vec<IpAddr>,
@@ -7,8 +7,7 @@ use async_trait::async_trait;
use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
IssuedTicketbooksChallengeCommitmentRequest, IssuedTicketbooksChallengeCommitmentResponse,
IssuedTicketbooksDataRequest, IssuedTicketbooksDataResponse, IssuedTicketbooksForCountResponse,
IssuedTicketbooksChallengeRequest, IssuedTicketbooksChallengeResponse,
IssuedTicketbooksForResponse, VerifyEcashTicketBody,
};
use nym_api_requests::ecash::VerificationKeyResponse;
@@ -37,7 +36,10 @@ pub use nym_api_requests::{
nym_nodes::{CachedNodesResponse, SkimmedNode},
NymNetworkDetailsResponse,
};
pub use nym_coconut_dkg_common::types::EpochId;
use nym_contracts_common::IdentityKey;
use nym_ecash_contract_common::deposit::DepositId;
pub use nym_http_api_client::Client;
use nym_http_api_client::{ApiClient, NO_PARAMS};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
@@ -46,9 +48,6 @@ use time::format_description::BorrowedFormatItem;
use time::Date;
use tracing::instrument;
pub use nym_coconut_dkg_common::types::EpochId;
pub use nym_http_api_client::Client;
pub mod error;
pub mod routes;
@@ -1013,52 +1012,22 @@ pub trait NymApiClientExt: ApiClient {
}
#[instrument(level = "debug", skip(self))]
async fn issued_ticketbooks_for_count(
async fn issued_ticketbooks_challenge(
&self,
expiration_date: Date,
) -> Result<IssuedTicketbooksForCountResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::ECASH_ROUTES,
routes::ECASH_ISSUED_TICKETBOOKS_FOR_COUNT,
&expiration_date.to_string(),
],
NO_PARAMS,
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn issued_ticketbooks_challenge_commitment(
&self,
request: &IssuedTicketbooksChallengeCommitmentRequest,
) -> Result<IssuedTicketbooksChallengeCommitmentResponse, NymAPIError> {
deposits: Vec<DepositId>,
) -> Result<IssuedTicketbooksChallengeResponse, NymAPIError> {
self.post_json(
&[
routes::API_VERSION,
routes::ECASH_ROUTES,
routes::ECASH_ISSUED_TICKETBOOKS_CHALLENGE_COMMITMENT,
routes::ECASH_ISSUED_TICKETBOOKS_CHALLENGE,
],
NO_PARAMS,
request,
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn issued_ticketbooks_data(
&self,
request: &IssuedTicketbooksDataRequest,
) -> Result<IssuedTicketbooksDataResponse, NymAPIError> {
self.post_json(
&[
routes::API_VERSION,
routes::ECASH_ROUTES,
routes::ECASH_ISSUED_TICKETBOOKS_DATA,
],
NO_PARAMS,
request,
&IssuedTicketbooksChallengeRequest {
expiration_date,
deposits,
},
)
.await
}
@@ -26,12 +26,7 @@ pub mod ecash {
pub const GLOBAL_COIN_INDICES_SIGNATURES: &str = "aggregated-coin-indices-signatures";
pub const MASTER_VERIFICATION_KEY: &str = "master-verification-key";
pub const ECASH_ISSUED_TICKETBOOKS_FOR: &str = "issued-ticketbooks-for";
pub const ECASH_ISSUED_TICKETBOOKS_COUNT: &str = "issued-ticketbooks-count";
pub const ECASH_ISSUED_TICKETBOOKS_FOR_COUNT: &str = "issued-ticketbooks-for-count";
pub const ECASH_ISSUED_TICKETBOOKS_ON_COUNT: &str = "issued-ticketbooks-on-count";
pub const ECASH_ISSUED_TICKETBOOKS_CHALLENGE_COMMITMENT: &str =
"issued-ticketbooks-challenge-commitment";
pub const ECASH_ISSUED_TICKETBOOKS_DATA: &str = "issued-ticketbooks-data";
pub const ECASH_ISSUED_TICKETBOOKS_CHALLENGE: &str = "issued-ticketbooks-challenge";
pub const EXPIRATION_DATE_PARAM: &str = "expiration_date";
pub const EPOCH_ID_PARAM: &str = "epoch_id";
@@ -48,7 +48,7 @@ impl Div<GasPrice> for &Coin {
panic!("attempted to divide by zero!")
};
let implicit_gas_limit = Uint128::new(self.amount).mul_floor(gas_price_inv);
let implicit_gas_limit = gas_price_inv * Uint128::new(self.amount);
if implicit_gas_limit.u128() >= u64::MAX as u128 {
u64::MAX
} else {
@@ -169,7 +169,13 @@ impl CoinConverter for CosmosCoin {
type Target = CosmWasmCoin;
fn convert_coin(&self) -> Self::Target {
CosmWasmCoin::new(self.amount, self.denom.to_string())
CosmWasmCoin::new(
self.amount
.to_string()
.parse()
.expect("cosmos coin had an invalid amount assigned"),
self.denom.to_string(),
)
}
}
@@ -7,10 +7,10 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::{Coin, Fee, SigningCosmWasmClient};
use crate::signing::signer::OfflineSigner;
use async_trait::async_trait;
use cosmwasm_std::{CosmosMsg, Empty};
use cosmwasm_std::{to_binary, CosmosMsg, WasmMsg};
use cw3::Vote;
use cw4::{MemberChangedHookMsg, MemberDiff};
use cw_utils::Expiration;
use nym_coconut_bandwidth_contract_common::msg::ExecuteMsg as CoconutBandwidthExecuteMsg;
use nym_multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -24,23 +24,35 @@ pub trait MultisigSigningClient: NymContractsProvider {
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError>;
async fn propose(
async fn propose_release_funds(
&self,
title: String,
description: String,
msgs: Vec<CosmosMsg<Empty>>,
latest: Option<Expiration>,
blinded_serial_number: String,
voucher_value: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let ecash_contract_address = self
.ecash_contract_address()
.ok_or_else(|| NyxdError::unavailable_contract_address("coconut bandwidth contract"))?;
let release_funds_req = CoconutBandwidthExecuteMsg::ReleaseFunds {
funds: voucher_value.into(),
};
let release_funds_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: ecash_contract_address.to_string(),
msg: to_binary(&release_funds_req)?,
funds: vec![],
});
let req = MultisigExecuteMsg::Propose {
title,
description: blinded_serial_number,
msgs: vec![release_funds_msg],
latest: None,
};
self.execute_multisig_contract(
fee,
MultisigExecuteMsg::Propose {
title,
description,
msgs,
latest,
},
"Multisig::Propose".to_string(),
req,
"Multisig::Propose::Execute::ReleaseFunds".to_string(),
vec![],
)
.await
@@ -149,7 +161,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::nyxd::contract_traits::tests::IgnoreValue;
use crate::nyxd::contract_traits::tests::{mock_coin, IgnoreValue};
// it's enough that this compiles and clippy is happy about it
#[allow(dead_code)]
@@ -159,12 +171,9 @@ mod tests {
) {
match msg {
MultisigExecuteMsg::Propose {
title,
description,
msgs,
latest,
title, description, ..
} => client
.propose(title, description, msgs, latest, None)
.propose_release_funds(title, description, mock_coin(), None)
.ignore(),
MultisigExecuteMsg::Vote { proposal_id, vote } => {
client.vote(proposal_id, vote, None).ignore()
@@ -27,7 +27,7 @@ impl Mul<Gas> for &GasPrice {
fn mul(self, gas_limit: Gas) -> Self::Output {
let limit_uint128 = Uint128::from(gas_limit);
let mut amount = limit_uint128.mul_floor(self.amount);
let mut amount = self.amount * limit_uint128;
let gas_price_numerator = self.amount.numerator();
let gas_price_denominator = self.amount.denominator();
@@ -35,7 +35,7 @@ impl Mul<Gas> for &GasPrice {
// gas price is a fraction of the smallest fee token unit, so we must ensure that
// for any multiplication, we have rounded up
//
// I don't really like this solution as it has a theoretical chance of
// I don't really like the this solution as it has a theoretical chance of
// overflowing (internally cosmwasm uses U256 to avoid that)
// 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)
@@ -155,7 +155,7 @@ async fn fetch_delegation_data(
match event.event.kind {
// If a pending undelegate tx is found, remove it from delegation map
PendingEpochEventKind::Undelegate { owner, node_id, .. } => {
if owner.as_str() == address.as_ref()
if owner == address.as_ref()
&& existing_delegation_map.contains_key(&node_id.to_string())
{
existing_delegation_map.remove(&node_id.to_string());
@@ -169,7 +169,7 @@ async fn fetch_delegation_data(
amount,
..
} => {
if owner.as_str() == address.as_ref() {
if owner == address.as_ref() {
let mut amount = Coin::from(amount);
if let Some(pending_record) = pending_delegation_map.get(&node_id.to_string()) {
amount.amount += pending_record.amount;
@@ -54,7 +54,7 @@ pub async fn create(args: Args, client: SigningClient, network_details: &NymNetw
let denom = network_details.chain_details.mix_denom.base.to_string();
let coin = Coin::new(args.amount, &denom);
let coin = Coin::new(args.amount.into(), &denom);
let res = client
.create_periodic_vesting_account(
@@ -0,0 +1,16 @@
[package]
name = "nym-coconut-bandwidth-contract-common"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = { workspace = true }
cosmwasm-schema = { workspace = true }
cw2 = { workspace = true, optional = true }
nym-multisig-contract-common = { path = "../multisig-contract" }
[features]
schema = ["cw2"]
@@ -0,0 +1,33 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_schema::cw_serde;
#[cw_serde]
pub struct DepositData {
deposit_info: String,
identity_key: String,
encryption_key: String,
}
impl DepositData {
pub fn new(deposit_info: String, identity_key: String, encryption_key: String) -> Self {
DepositData {
deposit_info,
identity_key,
encryption_key,
}
}
pub fn deposit_info(&self) -> &str {
&self.deposit_info
}
pub fn identity_key(&self) -> &str {
&self.identity_key
}
pub fn encryption_key(&self) -> &str {
&self.encryption_key
}
}
@@ -0,0 +1,4 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub const BANDWIDTH_PROPOSAL_ID: &str = "proposal_id";
@@ -0,0 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// event types
pub const DEPOSITED_FUNDS_EVENT_TYPE: &str = "deposited-funds";
// attributes that are used in multiple places
pub const DEPOSIT_VALUE: &str = "deposit-value";
pub const DEPOSIT_INFO: &str = "deposit-info";
pub const DEPOSIT_IDENTITY_KEY: &str = "deposit-identity-key";
pub const DEPOSIT_ENCRYPTION_KEY: &str = "deposit-encryption-key";
@@ -0,0 +1,5 @@
pub mod deposit;
pub mod event_attributes;
pub mod events;
pub mod msg;
pub mod spend_credential;
@@ -0,0 +1,41 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{deposit::DepositData, spend_credential::SpendCredentialData};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Coin;
#[cfg(feature = "schema")]
use crate::spend_credential::{PagedSpendCredentialResponse, SpendCredentialResponse};
#[cfg(feature = "schema")]
use cosmwasm_schema::QueryResponses;
#[cw_serde]
pub struct InstantiateMsg {
pub multisig_addr: String,
pub pool_addr: String,
pub mix_denom: String,
}
#[cw_serde]
pub enum ExecuteMsg {
DepositFunds { data: DepositData },
SpendCredential { data: SpendCredentialData },
ReleaseFunds { funds: Coin },
}
#[cw_serde]
#[cfg_attr(feature = "schema", derive(QueryResponses))]
pub enum QueryMsg {
#[cfg_attr(feature = "schema", returns(SpendCredentialResponse))]
GetSpentCredential { blinded_serial_number: String },
#[cfg_attr(feature = "schema", returns(PagedSpendCredentialResponse))]
GetAllSpentCredentials {
limit: Option<u32>,
start_after: Option<String>,
},
}
#[cw_serde]
pub struct MigrateMsg {}
@@ -0,0 +1,152 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{from_binary, to_binary, Addr, Coin, CosmosMsg, StdResult, WasmMsg};
use nym_multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg;
use crate::msg::ExecuteMsg;
#[cw_serde]
pub struct SpendCredentialData {
funds: Coin,
blinded_serial_number: String,
gateway_cosmos_address: String,
}
impl SpendCredentialData {
pub fn new(funds: Coin, blinded_serial_number: String, gateway_cosmos_address: String) -> Self {
SpendCredentialData {
funds,
blinded_serial_number,
gateway_cosmos_address,
}
}
pub fn funds(&self) -> &Coin {
&self.funds
}
pub fn blinded_serial_number(&self) -> &str {
&self.blinded_serial_number
}
pub fn gateway_cosmos_address(&self) -> &str {
&self.gateway_cosmos_address
}
}
#[cw_serde]
#[derive(Copy)]
pub enum SpendCredentialStatus {
#[serde(alias = "InProgress")]
InProgress,
#[serde(alias = "Spent")]
Spent,
}
#[cw_serde]
pub struct SpendCredential {
funds: Coin,
blinded_serial_number: String,
gateway_cosmos_address: Addr,
status: SpendCredentialStatus,
}
impl SpendCredential {
pub fn new(funds: Coin, blinded_serial_number: String, gateway_cosmos_address: Addr) -> Self {
SpendCredential {
funds,
blinded_serial_number,
gateway_cosmos_address,
status: SpendCredentialStatus::InProgress,
}
}
pub fn blinded_serial_number(&self) -> &str {
&self.blinded_serial_number
}
pub fn status(&self) -> SpendCredentialStatus {
self.status
}
pub fn mark_as_spent(&mut self) {
self.status = SpendCredentialStatus::Spent;
}
}
#[cw_serde]
pub struct PagedSpendCredentialResponse {
pub spend_credentials: Vec<SpendCredential>,
pub per_page: usize,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<String>,
}
impl PagedSpendCredentialResponse {
pub fn new(
spend_credentials: Vec<SpendCredential>,
per_page: usize,
start_next_after: Option<String>,
) -> Self {
PagedSpendCredentialResponse {
spend_credentials,
per_page,
start_next_after,
}
}
}
#[cw_serde]
pub struct SpendCredentialResponse {
pub spend_credential: Option<SpendCredential>,
}
impl SpendCredentialResponse {
pub fn new(spend_credential: Option<SpendCredential>) -> Self {
SpendCredentialResponse { spend_credential }
}
}
pub fn to_cosmos_msg(
funds: Coin,
blinded_serial_number: String,
coconut_bandwidth_addr: String,
multisig_addr: String,
) -> StdResult<CosmosMsg> {
let release_funds_req = ExecuteMsg::ReleaseFunds { funds };
let release_funds_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: coconut_bandwidth_addr,
msg: to_binary(&release_funds_req)?,
funds: vec![],
});
let req = MultisigExecuteMsg::Propose {
title: String::from("Release funds, as ordered by Coconut Bandwidth Contract"),
description: blinded_serial_number,
msgs: vec![release_funds_msg],
latest: None,
};
let msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: multisig_addr,
msg: to_binary(&req)?,
funds: vec![],
});
Ok(msg)
}
pub fn funds_from_cosmos_msgs(msgs: Vec<CosmosMsg>) -> Option<Coin> {
if let Some(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: _,
msg,
funds: _,
})) = msgs.first()
{
if let Ok(ExecuteMsg::ReleaseFunds { funds }) = from_binary::<ExecuteMsg>(msg) {
return Some(funds);
}
}
None
}
@@ -4,7 +4,7 @@
use crate::msg::ExecuteMsg;
use crate::types::{EpochId, NodeIndex};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{from_json, to_json_binary, Addr, CosmosMsg, StdResult, Timestamp, WasmMsg};
use cosmwasm_std::{from_binary, to_binary, Addr, CosmosMsg, StdResult, Timestamp, WasmMsg};
use cw_utils::Expiration;
use nym_multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg;
@@ -49,7 +49,7 @@ pub fn to_cosmos_msg(
};
let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: coconut_dkg_addr,
msg: to_json_binary(&verify_vk_share_req)?,
msg: to_binary(&verify_vk_share_req)?,
funds: vec![],
});
let req = MultisigExecuteMsg::Propose {
@@ -60,7 +60,7 @@ pub fn to_cosmos_msg(
};
let msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: multisig_addr,
msg: to_json_binary(&req)?,
msg: to_binary(&req)?,
funds: vec![],
});
@@ -82,7 +82,7 @@ pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option<String> {
})) = msgs.first()
{
if let Ok(ExecuteMsg::VerifyVerificationKeyShare { owner, .. }) =
from_json::<ExecuteMsg>(msg)
from_binary::<ExecuteMsg>(msg)
{
return Some(owner);
}
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{from_json, to_json_vec, Addr, Coin, MessageInfo, StdResult};
use cosmwasm_std::{from_slice, to_vec, Addr, Coin, MessageInfo, StdResult};
use schemars::JsonSchema;
use serde::de::DeserializeOwned;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
@@ -164,7 +164,7 @@ where
where
T: Serialize,
{
to_json_vec(self)
to_vec(self)
}
pub fn to_sha256_plaintext_digest(&self) -> StdResult<Vec<u8>>
@@ -195,7 +195,7 @@ where
where
T: DeserializeOwned,
{
from_json(bytes)
from_slice(bytes)
}
pub fn try_from_string(raw: &str) -> StdResult<SignableMessage<T>>
@@ -2,9 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Decimal;
use cosmwasm_std::OverflowError;
use cosmwasm_std::Uint128;
use cosmwasm_std::{Decimal, Fraction};
use serde::de::Error;
use serde::{Deserialize, Deserializer};
use std::fmt::{self, Display, Formatter};
@@ -17,7 +17,7 @@ pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
pub fn truncate_decimal(amount: Decimal) -> Uint128 {
Uint128::new(1).mul_floor(amount)
amount * Uint128::new(1)
}
#[derive(Error, Debug)]
@@ -113,17 +113,11 @@ impl Mul<Percent> for Decimal {
}
}
impl Fraction<Uint128> for Percent {
fn numerator(&self) -> Uint128 {
self.0.numerator()
}
impl Mul<Uint128> for Percent {
type Output = Uint128;
fn denominator(&self) -> Uint128 {
self.0.denominator()
}
fn inv(&self) -> Option<Self> {
Percent::new(self.0.inv()?).ok()
fn mul(self, rhs: Uint128) -> Self::Output {
self.0 * rhs
}
}
@@ -1,14 +0,0 @@
[package]
name = "easy-addr"
version = "0.1.0"
edition = "2021"
publish = false
license.workspace = true
[lib]
proc-macro = true
[dependencies]
cosmwasm-std = { workspace = true }
quote = { workspace = true }
syn = { workspace = true, features = ["full", "printing", "extra-traits"] }
@@ -1,12 +0,0 @@
use cosmwasm_std::testing::MockApi;
use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input;
#[proc_macro]
pub fn addr(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::LitStr).value();
let addr = MockApi::default().addr_make(input.as_str()).to_string();
TokenStream::from(quote! {#addr})
}
@@ -241,10 +241,10 @@ mod tests {
#[test]
fn gateway_bond_partial_ord() {
let _150foos = Coin::new(150u32, "foo");
let _140foos = Coin::new(140u32, "foo");
let _50foos = Coin::new(50u32, "foo");
let _0foos = Coin::new(0u32, "foo");
let _150foos = Coin::new(150, "foo");
let _140foos = Coin::new(140, "foo");
let _50foos = Coin::new(50, "foo");
let _0foos = Coin::new(0, "foo");
let gate1 = GatewayBond {
pledge_amount: _150foos.clone(),
@@ -34,10 +34,8 @@ where
{
fn into_base_decimal(self) -> StdResult<Decimal> {
let atomics = self.into();
Decimal::from_atomics(atomics, 0).map_err(|_| {
StdError::generic_err(format!(
"Decimal range exceeded for {atomics} with 0 decimal places."
))
Decimal::from_atomics(atomics, 0).map_err(|_| StdError::GenericErr {
msg: format!("Decimal range exceeded for {atomics} with 0 decimal places."),
})
}
}
@@ -77,8 +77,6 @@ impl<'a> PrimaryKey<'a> for Role {
impl KeyDeserialize for Role {
type Output = Role;
const KEY_ELEMS: u16 = 1;
fn from_vec(value: Vec<u8>) -> StdResult<Self::Output> {
let u8_key: <u8 as KeyDeserialize>::Output = <u8 as KeyDeserialize>::from_vec(value)?;
Role::try_from(u8_key).map_err(|err| StdError::generic_err(err.to_string()))
@@ -242,7 +242,7 @@ mod tests {
#[allow(clippy::unwrap_used)]
fn base_simulator(initial_pledge: u128) -> Simulator {
let profit_margin = Percent::from_percentage_value(10).unwrap();
let interval_operating_cost = Coin::new(40_000_000u64, "unym");
let interval_operating_cost = Coin::new(40_000_000, "unym");
let epochs_in_interval = 720u32;
let interval_pool_emission = Percent::from_percentage_value(2).unwrap();
@@ -347,7 +347,7 @@ mod tests {
fn single_delegation_at_genesis() {
let mut simulator = base_simulator(10000_000000);
simulator
.delegate("alice", Coin::new(18000_000000u64, "unym"), 0)
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
let node_params = NodeRewardingParameters::new(
@@ -393,7 +393,7 @@ mod tests {
compare_decimals(rewards1.operator, expected_operator1, None);
simulator
.delegate("alice", Coin::new(18000_000000u64, "unym"), 0)
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
let rewards2 = simulator.simulate_epoch_single_node(node_params).unwrap();
@@ -439,10 +439,10 @@ mod tests {
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work)
simulator
.delegate("alice", Coin::new(18000_000000u64, "unym"), 0)
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
simulator
.delegate("bob", Coin::new(4000_000000u64, "unym"), 0)
.delegate("bob", Coin::new(4000_000000, "unym"), 0)
.unwrap();
// "normal", sanity check rewarding
@@ -484,10 +484,10 @@ mod tests {
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work)
simulator
.delegate("alice", Coin::new(18000_000000u64, "unym"), 0)
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
simulator
.delegate("bob", Coin::new(4000_000000u64, "unym"), 0)
.delegate("bob", Coin::new(4000_000000, "unym"), 0)
.unwrap();
// "normal", sanity check rewarding
@@ -553,12 +553,12 @@ mod tests {
for epoch in 0..720 {
if epoch == 0 {
simulator
.delegate("a", Coin::new(18000_000000u64, "unym"), 0)
.delegate("a", Coin::new(18000_000000, "unym"), 0)
.unwrap()
}
if epoch == 42 {
simulator
.delegate("b", Coin::new(2000_000000u64, "unym"), 0)
.delegate("b", Coin::new(2000_000000, "unym"), 0)
.unwrap()
}
if epoch == 89 {
@@ -566,7 +566,7 @@ mod tests {
}
if epoch == 123 {
simulator
.delegate("c", Coin::new(6666_000000u64, "unym"), 0)
.delegate("c", Coin::new(6666_000000, "unym"), 0)
.unwrap()
}
if epoch == 167 {
@@ -574,7 +574,7 @@ mod tests {
}
if epoch == 245 {
simulator
.delegate("d", Coin::new(2050_000000u64, "unym"), 0)
.delegate("d", Coin::new(2050_000000, "unym"), 0)
.unwrap()
}
if epoch == 264 {
@@ -597,7 +597,7 @@ mod tests {
}
if epoch == 545 {
simulator
.delegate("e", Coin::new(5000_000000u64, "unym"), 0)
.delegate("e", Coin::new(5000_000000, "unym"), 0)
.unwrap()
}
@@ -666,132 +666,132 @@ mod tests {
let n0 = simulator
.bond(
Coin::new(11_000_000_000000u64, "unym"),
Coin::new(11_000_000_000000, "unym"),
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000u64, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(1_000_000_000000u64, "unym"), n0)
.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n0)
.unwrap();
let n1 = simulator
.bond(
Coin::new(1_000_000_000000u64, "unym"),
Coin::new(1_000_000_000000, "unym"),
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000u64, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(11_000_000_000000u64, "unym"), n1)
.delegate("delegator", Coin::new(11_000_000_000000, "unym"), n1)
.unwrap();
let n2 = simulator
.bond(
Coin::new(1_000_000_000000u64, "unym"),
Coin::new(1_000_000_000000, "unym"),
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000u64, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(9_000_000_000000u64, "unym"), n2)
.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n2)
.unwrap();
let n3 = simulator
.bond(
Coin::new(1_000_000_000000u64, "unym"),
Coin::new(1_000_000_000000, "unym"),
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
interval_operating_cost: Coin::new(500_000_000u64, "unym"),
interval_operating_cost: Coin::new(500_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000u64, "unym"), n3)
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n3)
.unwrap();
let n4 = simulator
.bond(
Coin::new(1000_000000u64, "unym"),
Coin::new(1000_000000, "unym"),
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000u64, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_999_000_000000u64, "unym"), n4)
.delegate("delegator", Coin::new(7_999_000_000000, "unym"), n4)
.unwrap();
let n5 = simulator
.bond(
Coin::new(1_000_000_000000u64, "unym"),
Coin::new(1_000_000_000000, "unym"),
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000u64, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000u64, "unym"), n5)
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n5)
.unwrap();
let n6 = simulator
.bond(
Coin::new(11_000_000_000000u64, "unym"),
Coin::new(11_000_000_000000, "unym"),
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000u64, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(1_000_000_000000u64, "unym"), n6)
.delegate("delegator", Coin::new(1_000_000_000000, "unym"), n6)
.unwrap();
let n7 = simulator
.bond(
Coin::new(1_000_000_000000u64, "unym"),
Coin::new(1_000_000_000000, "unym"),
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000u64, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(9_000_000_000000u64, "unym"), n7)
.delegate("delegator", Coin::new(9_000_000_000000, "unym"), n7)
.unwrap();
let n8 = simulator
.bond(
Coin::new(1_000_000_000000u64, "unym"),
Coin::new(1_000_000_000000, "unym"),
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
interval_operating_cost: Coin::new(500_000_000u64, "unym"),
interval_operating_cost: Coin::new(500_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000u64, "unym"), n8)
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n8)
.unwrap();
let n9 = simulator
.bond(
Coin::new(1_000_000_000000u64, "unym"),
Coin::new(1_000_000_000000, "unym"),
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000u64, "unym"),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
)
.unwrap();
simulator
.delegate("delegator", Coin::new(7_000_000_000000u64, "unym"), n9)
.delegate("delegator", Coin::new(7_000_000_000000, "unym"), n9)
.unwrap();
let uptime_1 = Percent::from_percentage_value(100).unwrap();
@@ -3,7 +3,7 @@
use crate::ecash::error::EcashTicketError;
use crate::Error;
use cosmwasm_std::{from_json, CosmosMsg, WasmMsg};
use cosmwasm_std::{from_binary, CosmosMsg, WasmMsg};
use nym_credentials_interface::VerificationKeyAuth;
use nym_ecash_contract_common::msg::ExecuteMsg;
use nym_gateway_storage::GatewayStorage;
@@ -72,7 +72,7 @@ impl SharedState {
let CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) = msg else {
return false;
};
let Ok(ExecuteMsg::RedeemTickets { gw, .. }) = from_json(msg) else {
let Ok(ExecuteMsg::RedeemTickets { gw, .. }) = from_binary(msg) else {
return false;
};
@@ -16,20 +16,3 @@ pub mod bs58_ed25519_pubkey {
PublicKey::from_base58_string(s).map_err(serde::de::Error::custom)
}
}
pub mod bs58_ed25519_signature {
use crate::asymmetric::identity::Signature;
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(
signature: &Signature,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&signature.to_base58_string())
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Signature, D::Error> {
let s = String::deserialize(deserializer)?;
Signature::from_base58_string(s).map_err(serde::de::Error::custom)
}
}
+2 -11
View File
@@ -12,13 +12,7 @@ license.workspace = true
[dependencies]
async-trait = { workspace = true }
reqwest = { workspace = true, features = [
"json",
"gzip",
"deflate",
"brotli",
"zstd",
] }
reqwest = { workspace = true, features = ["json", "gzip"] }
http.workspace = true
url = { workspace = true }
once_cell = { workspace = true }
@@ -36,10 +30,7 @@ mime = { workspace = true }
nym-bin-common = { path = "../bin-common" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
hickory-resolver = { workspace = true, features = [
"dns-over-https-rustls",
"webpki-roots",
] }
hickory-resolver = { workspace = true, features = ["dns-over-https-rustls", "webpki-roots"] }
# for request timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
+7 -3
View File
@@ -36,10 +36,11 @@ use std::{
use hickory_resolver::{
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts},
error::{ResolveError, ResolveErrorKind},
lookup_ip::{LookupIp, LookupIpIntoIter},
error::ResolveError,
lookup_ip::LookupIpIntoIter,
TokioAsyncResolver,
};
use hickory_resolver::{error::ResolveErrorKind, lookup_ip::LookupIp};
use once_cell::sync::OnceCell;
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
use tracing::warn;
@@ -213,7 +214,10 @@ impl HickoryDnsResolver {
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
/// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm.
fn new_resolver() -> Result<TokioAsyncResolver, HickoryDnsError> {
let mut name_servers = NameServerConfigGroup::quad9_tls();
let mut name_servers = NameServerConfigGroup::google_tls();
name_servers.merge(NameServerConfigGroup::google_https());
// name_servers.merge(NameServerConfigGroup::google_h3());
name_servers.merge(NameServerConfigGroup::quad9_tls());
name_servers.merge(NameServerConfigGroup::quad9_https());
name_servers.merge(NameServerConfigGroup::cloudflare_tls());
name_servers.merge(NameServerConfigGroup::cloudflare_https());
+12 -11
View File
@@ -265,18 +265,14 @@ impl ClientBuilder {
#[cfg(not(target_arch = "wasm32"))]
let reqwest_client_builder = {
// Note: I believe the manual enable calls for the compression methods are extra
// as the various compression features for `reqwest` crate should be enabled
// just by including the feature which:
// `"Enable[s] auto decompression by checking the Content-Encoding response header."`
let r = reqwest::ClientBuilder::new();
// Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."`
//
// I am going to leave these here anyways so that removing a decompression method
// from the features list will throw an error if it is not also removed here.
reqwest::ClientBuilder::new()
.gzip(true)
.deflate(true)
.brotli(true)
.zstd(true)
// I am going to leave it here anyways so that gzip decompression is attempted even if
// that feature is removed.
r.gzip(true)
};
ClientBuilder {
@@ -539,6 +535,11 @@ impl ApiClientCore for Client {
let mut request = self.reqwest_client.request(method.clone(), url);
// Indicate that compressed responses are preferred, but if not supported other encodings are fine.
// TODO: Down the road we can be more selective about adding this, but it's inclusion here guarantees
// that we use compression when available.
request = request.header(reqwest::header::ACCEPT_ENCODING, "gzip;q=1.0, *;q=0.5");
if let Some(body) = json_body {
request = request.json(body);
}
+8
View File
@@ -23,12 +23,14 @@ const LENGTH_PREFIX_SIZE: usize = 2;
// long for the buffer to fill up, since this kills latency.
pub struct MultiIpPacketCodec {
buffer: BytesMut,
pub counter: u64,
}
impl MultiIpPacketCodec {
pub fn new() -> Self {
MultiIpPacketCodec {
buffer: BytesMut::new(),
counter: 0,
}
}
@@ -89,6 +91,8 @@ impl Encoder<IprPacket> for MultiIpPacketCodec {
let packet = match packet {
IprPacket::Flush => {
dst.extend_from_slice(&self.buffer);
self.counter += 1;
println!("Encoding packet: {}", self.counter);
self.buffer = BytesMut::new();
return Ok(());
}
@@ -103,6 +107,8 @@ impl Encoder<IprPacket> for MultiIpPacketCodec {
dst.extend_from_slice(&(packet_size as u16).to_be_bytes());
// Add the packet to the buffer
dst.extend_from_slice(&packet);
self.counter += 1;
println!("Encoding packet: {}", self.counter);
return Ok(());
}
@@ -111,6 +117,8 @@ impl Encoder<IprPacket> for MultiIpPacketCodec {
if self.buffer.len() + packet_size + LENGTH_PREFIX_SIZE > MAX_PACKET_SIZE {
// Send the existing buffer
dst.extend_from_slice(&self.buffer);
self.counter += 1;
println!("Encoding packet: {}", self.counter);
// Start a new buffer
self.buffer = BytesMut::new();
}
-3
View File
@@ -7,9 +7,6 @@ pub const TICKETBOOK_VALIDITY_DAYS: u32 = 7;
/// Specifies the number of tickets in each issued ticketbook.
pub const TICKETBOOK_SIZE: u64 = 50;
/// Specifies the minimum request size each signer must support
pub const MINIMUM_TICKETBOOK_DATA_REQUEST_SIZE: usize = 50;
/// This type is defined mostly for the purposes of having constants (like sizes) associated with given variants
/// It's not meant to be serialised or have any fancy traits defined on it (in this crate)
#[derive(Copy, Clone, Debug, PartialEq)]
@@ -269,9 +269,10 @@ pub fn check_vk_pairing(
#[cfg(test)]
mod tests {
use super::*;
use rand::RngCore;
use super::*;
#[test]
fn polynomial_evaluation() {
// y = 42 (it should be 42 regardless of x)
-1
View File
@@ -176,7 +176,6 @@ pub struct IssuedTicketbooksFullMerkleProof {
included_leaves: Vec<MerkleLeaf>,
total_leaves: usize,
#[schemars(with = "String")]
#[schema(value_type = String)]
#[serde(with = "nym_serde_helpers::hex")]
root: Vec<u8>,
}
+483 -555
View File
File diff suppressed because it is too large Load Diff
+19 -17
View File
@@ -1,7 +1,9 @@
[workspace]
resolver = "2"
members = [
# "coconut-bandwidth",
"coconut-dkg",
"coconut-test",
"ecash",
"mixnet",
"mixnet-vesting-integration-tests",
@@ -31,24 +33,24 @@ overflow-checks = true
[workspace.dependencies]
anyhow = "1.0.86"
bs58 = "0.5.1"
cosmwasm-crypto = "=2.2.2"
cosmwasm-derive = "=2.2.2"
cosmwasm-schema = "=2.2.2"
cosmwasm-std = "=2.2.2"
cw-controllers = "=2.0.0"
cw-multi-test = "=2.3.2"
cw-storage-plus = "=2.0.0"
cw-utils = "=2.0.0"
cw2 = "=2.0.0"
cw3 = "=2.0.0"
cw3-fixed-multisig = "=2.0.0"
cw4 = "=2.0.0"
cw20 = "=2.0.0"
cw20-base = "2.0.0"
bs58 = "0.4.0"
cosmwasm-crypto = "=1.4.3"
cosmwasm-derive = "=1.4.3"
cosmwasm-schema = "=1.4.3"
cosmwasm-std = "=1.4.3"
cosmwasm-storage = "=1.4.3"
cw-controllers = "=1.1.0"
cw-multi-test = "=0.16.5"
cw-storage-plus = "=1.2.0"
cw-utils = "=1.0.1"
cw2 = "=1.1.2"
cw3 = "=1.1.2"
cw3-fixed-multisig = "=1.1.2"
cw4 = "=1.1.2"
cw20 = "=1.1.2"
semver = "1.0.21"
serde = "1.0.196"
sylvia = "1.3.3"
sylvia = "0.8.0"
schemars = "0.8.16"
thiserror = "2.0.11"
thiserror = "1.0.48"
+3
View File
@@ -1,5 +1,8 @@
schema: coconut-dkg-schema mixnet-schema vesting-schema multisig-schema group-schema ecash-schema
#coconut-bandwidth-schema:
# $(MAKE) -C coconut-bandwidth generate-schema
coconut-dkg-schema:
$(MAKE) -C coconut-dkg generate-schema
@@ -0,0 +1,4 @@
[alias]
wasm = "build --release --lib --target wasm32-unknown-unknown"
unit-test = "test --lib"
schema = "run --bin schema --features=schema-gen"
+29
View File
@@ -0,0 +1,29 @@
[package]
name = "nym-coconut-bandwidth"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "schema"
required-features = ["schema-gen"]
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
nym-coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
cosmwasm-std = { workspace = true }
cosmwasm-schema = { workspace = true, optional = true }
cosmwasm-storage = { workspace = true }
cw-storage-plus = { workspace = true }
cw-controllers = { workspace = true }
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { workspace = true }
[features]
schema-gen = ["nym-coconut-bandwidth-contract-common/schema", "cosmwasm-schema"]
+2
View File
@@ -0,0 +1,2 @@
generate-schema:
cargo schema
@@ -0,0 +1,382 @@
{
"contract_name": "nym-coconut-bandwidth",
"contract_version": "0.1.0",
"idl_version": "1.0.0",
"instantiate": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "InstantiateMsg",
"type": "object",
"required": [
"mix_denom",
"multisig_addr",
"pool_addr"
],
"properties": {
"mix_denom": {
"type": "string"
},
"multisig_addr": {
"type": "string"
},
"pool_addr": {
"type": "string"
}
},
"additionalProperties": false
},
"execute": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ExecuteMsg",
"oneOf": [
{
"type": "object",
"required": [
"deposit_funds"
],
"properties": {
"deposit_funds": {
"type": "object",
"required": [
"data"
],
"properties": {
"data": {
"$ref": "#/definitions/DepositData"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"spend_credential"
],
"properties": {
"spend_credential": {
"type": "object",
"required": [
"data"
],
"properties": {
"data": {
"$ref": "#/definitions/SpendCredentialData"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"release_funds"
],
"properties": {
"release_funds": {
"type": "object",
"required": [
"funds"
],
"properties": {
"funds": {
"$ref": "#/definitions/Coin"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
],
"definitions": {
"Coin": {
"type": "object",
"required": [
"amount",
"denom"
],
"properties": {
"amount": {
"$ref": "#/definitions/Uint128"
},
"denom": {
"type": "string"
}
}
},
"DepositData": {
"type": "object",
"required": [
"deposit_info",
"encryption_key",
"identity_key"
],
"properties": {
"deposit_info": {
"type": "string"
},
"encryption_key": {
"type": "string"
},
"identity_key": {
"type": "string"
}
},
"additionalProperties": false
},
"SpendCredentialData": {
"type": "object",
"required": [
"blinded_serial_number",
"funds",
"gateway_cosmos_address"
],
"properties": {
"blinded_serial_number": {
"type": "string"
},
"funds": {
"$ref": "#/definitions/Coin"
},
"gateway_cosmos_address": {
"type": "string"
}
},
"additionalProperties": false
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
}
}
},
"query": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "QueryMsg",
"oneOf": [
{
"type": "object",
"required": [
"get_spent_credential"
],
"properties": {
"get_spent_credential": {
"type": "object",
"required": [
"blinded_serial_number"
],
"properties": {
"blinded_serial_number": {
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_all_spent_credentials"
],
"properties": {
"get_all_spent_credentials": {
"type": "object",
"properties": {
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
]
},
"migrate": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "MigrateMsg",
"type": "object",
"additionalProperties": false
},
"sudo": null,
"responses": {
"get_all_spent_credentials": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedSpendCredentialResponse",
"type": "object",
"required": [
"per_page",
"spend_credentials"
],
"properties": {
"per_page": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"spend_credentials": {
"type": "array",
"items": {
"$ref": "#/definitions/SpendCredential"
}
},
"start_next_after": {
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
"type": [
"string",
"null"
]
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"Coin": {
"type": "object",
"required": [
"amount",
"denom"
],
"properties": {
"amount": {
"$ref": "#/definitions/Uint128"
},
"denom": {
"type": "string"
}
}
},
"SpendCredential": {
"type": "object",
"required": [
"blinded_serial_number",
"funds",
"gateway_cosmos_address",
"status"
],
"properties": {
"blinded_serial_number": {
"type": "string"
},
"funds": {
"$ref": "#/definitions/Coin"
},
"gateway_cosmos_address": {
"$ref": "#/definitions/Addr"
},
"status": {
"$ref": "#/definitions/SpendCredentialStatus"
}
},
"additionalProperties": false
},
"SpendCredentialStatus": {
"type": "string",
"enum": [
"in_progress",
"spent"
]
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
}
}
},
"get_spent_credential": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "SpendCredentialResponse",
"type": "object",
"properties": {
"spend_credential": {
"anyOf": [
{
"$ref": "#/definitions/SpendCredential"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"Coin": {
"type": "object",
"required": [
"amount",
"denom"
],
"properties": {
"amount": {
"$ref": "#/definitions/Uint128"
},
"denom": {
"type": "string"
}
}
},
"SpendCredential": {
"type": "object",
"required": [
"blinded_serial_number",
"funds",
"gateway_cosmos_address",
"status"
],
"properties": {
"blinded_serial_number": {
"type": "string"
},
"funds": {
"$ref": "#/definitions/Coin"
},
"gateway_cosmos_address": {
"$ref": "#/definitions/Addr"
},
"status": {
"$ref": "#/definitions/SpendCredentialStatus"
}
},
"additionalProperties": false
},
"SpendCredentialStatus": {
"type": "string",
"enum": [
"in_progress",
"spent"
]
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
}
}
}
}
}
@@ -0,0 +1,130 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ExecuteMsg",
"oneOf": [
{
"type": "object",
"required": [
"deposit_funds"
],
"properties": {
"deposit_funds": {
"type": "object",
"required": [
"data"
],
"properties": {
"data": {
"$ref": "#/definitions/DepositData"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"spend_credential"
],
"properties": {
"spend_credential": {
"type": "object",
"required": [
"data"
],
"properties": {
"data": {
"$ref": "#/definitions/SpendCredentialData"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"release_funds"
],
"properties": {
"release_funds": {
"type": "object",
"required": [
"funds"
],
"properties": {
"funds": {
"$ref": "#/definitions/Coin"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
],
"definitions": {
"Coin": {
"type": "object",
"required": [
"amount",
"denom"
],
"properties": {
"amount": {
"$ref": "#/definitions/Uint128"
},
"denom": {
"type": "string"
}
}
},
"DepositData": {
"type": "object",
"required": [
"deposit_info",
"encryption_key",
"identity_key"
],
"properties": {
"deposit_info": {
"type": "string"
},
"encryption_key": {
"type": "string"
},
"identity_key": {
"type": "string"
}
},
"additionalProperties": false
},
"SpendCredentialData": {
"type": "object",
"required": [
"blinded_serial_number",
"funds",
"gateway_cosmos_address"
],
"properties": {
"blinded_serial_number": {
"type": "string"
},
"funds": {
"$ref": "#/definitions/Coin"
},
"gateway_cosmos_address": {
"type": "string"
}
},
"additionalProperties": false
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
}
}
}
@@ -0,0 +1,22 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "InstantiateMsg",
"type": "object",
"required": [
"mix_denom",
"multisig_addr",
"pool_addr"
],
"properties": {
"mix_denom": {
"type": "string"
},
"multisig_addr": {
"type": "string"
},
"pool_addr": {
"type": "string"
}
},
"additionalProperties": false
}
@@ -0,0 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "MigrateMsg",
"type": "object",
"additionalProperties": false
}
@@ -0,0 +1,56 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "QueryMsg",
"oneOf": [
{
"type": "object",
"required": [
"get_spent_credential"
],
"properties": {
"get_spent_credential": {
"type": "object",
"required": [
"blinded_serial_number"
],
"properties": {
"blinded_serial_number": {
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_all_spent_credentials"
],
"properties": {
"get_all_spent_credentials": {
"type": "object",
"properties": {
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
]
}
@@ -0,0 +1,86 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedSpendCredentialResponse",
"type": "object",
"required": [
"per_page",
"spend_credentials"
],
"properties": {
"per_page": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"spend_credentials": {
"type": "array",
"items": {
"$ref": "#/definitions/SpendCredential"
}
},
"start_next_after": {
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
"type": [
"string",
"null"
]
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"Coin": {
"type": "object",
"required": [
"amount",
"denom"
],
"properties": {
"amount": {
"$ref": "#/definitions/Uint128"
},
"denom": {
"type": "string"
}
}
},
"SpendCredential": {
"type": "object",
"required": [
"blinded_serial_number",
"funds",
"gateway_cosmos_address",
"status"
],
"properties": {
"blinded_serial_number": {
"type": "string"
},
"funds": {
"$ref": "#/definitions/Coin"
},
"gateway_cosmos_address": {
"$ref": "#/definitions/Addr"
},
"status": {
"$ref": "#/definitions/SpendCredentialStatus"
}
},
"additionalProperties": false
},
"SpendCredentialStatus": {
"type": "string",
"enum": [
"in_progress",
"spent"
]
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
}
}
}
@@ -0,0 +1,74 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "SpendCredentialResponse",
"type": "object",
"properties": {
"spend_credential": {
"anyOf": [
{
"$ref": "#/definitions/SpendCredential"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"Coin": {
"type": "object",
"required": [
"amount",
"denom"
],
"properties": {
"amount": {
"$ref": "#/definitions/Uint128"
},
"denom": {
"type": "string"
}
}
},
"SpendCredential": {
"type": "object",
"required": [
"blinded_serial_number",
"funds",
"gateway_cosmos_address",
"status"
],
"properties": {
"blinded_serial_number": {
"type": "string"
},
"funds": {
"$ref": "#/definitions/Coin"
},
"gateway_cosmos_address": {
"$ref": "#/definitions/Addr"
},
"status": {
"$ref": "#/definitions/SpendCredentialStatus"
}
},
"additionalProperties": false
},
"SpendCredentialStatus": {
"type": "string",
"enum": [
"in_progress",
"spent"
]
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
}
}
}
@@ -0,0 +1,16 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_schema::write_api;
use nym_coconut_bandwidth_contract_common::msg::{
ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
};
fn main() {
write_api! {
instantiate: InstantiateMsg,
query: QueryMsg,
execute: ExecuteMsg,
migrate: MigrateMsg,
}
}
+110
View File
@@ -0,0 +1,110 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{
entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
};
use nym_coconut_bandwidth_contract_common::msg::{
ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
};
use crate::error::ContractError;
use crate::queries::{query_all_spent_credentials_paged, query_spent_credential};
use crate::state::{Config, ADMIN, CONFIG};
use crate::transactions;
/// Instantiate the contract.
///
/// `deps` contains Storage, API and Querier
/// `msg` is the contract initialization message, sort of like a constructor call.
#[entry_point]
pub fn instantiate(
mut deps: DepsMut<'_>,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
let multisig_addr = deps.api.addr_validate(&msg.multisig_addr)?;
let pool_addr = deps.api.addr_validate(&msg.pool_addr)?;
let mix_denom = msg.mix_denom;
ADMIN.set(deps.branch(), Some(multisig_addr.clone()))?;
let cfg = Config {
multisig_addr,
pool_addr,
mix_denom,
};
CONFIG.save(deps.storage, &cfg)?;
Ok(Response::default())
}
/// Handle an incoming message
#[entry_point]
pub fn execute(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::DepositFunds { data } => transactions::deposit_funds(deps, env, info, data),
ExecuteMsg::SpendCredential { data } => {
transactions::spend_credential(deps, env, info, data)
}
ExecuteMsg::ReleaseFunds { funds } => transactions::release_funds(deps, env, info, funds),
}
}
#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GetAllSpentCredentials { limit, start_after } => to_binary(
&query_all_spent_credentials_paged(deps, start_after, limit)?,
),
QueryMsg::GetSpentCredential {
blinded_serial_number,
} => to_binary(&query_spent_credential(deps, blinded_serial_number)?),
}
}
#[entry_point]
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
Ok(Default::default())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::support::tests::fixtures::TEST_MIX_DENOM;
use crate::support::tests::helpers::*;
use cosmwasm_std::coins;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
#[test]
fn initialize_contract() {
let mut deps = mock_dependencies();
let env = mock_env();
let msg = InstantiateMsg {
multisig_addr: String::from(MULTISIG_CONTRACT),
pool_addr: String::from(POOL_CONTRACT),
mix_denom: TEST_MIX_DENOM.to_string(),
};
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
assert_eq!(0, res.messages.len());
// Contract balance should be 0
assert_eq!(
coins(0, TEST_MIX_DENOM),
vec![deps
.as_ref()
.querier
.query_balance(env.contract.address, TEST_MIX_DENOM)
.unwrap()]
);
}
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::StdError;
use cw_controllers::AdminError;
use thiserror::Error;
/// Custom errors for contract failure conditions.
///
/// Add any other custom errors you like here.
/// Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details.
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error(transparent)]
Std(#[from] StdError),
#[error("Received multiple coin types")]
MultipleDenoms,
#[error("No coin was sent for voucher")]
NoCoin,
#[error("Wrong coin denomination, you must send {mix_denom}")]
WrongDenom { mix_denom: String },
#[error("There aren't enough funds in the contract")]
NotEnoughFunds,
#[error("Credential already spent or in process of spending")]
DuplicateBlindedSerialNumber,
#[error(transparent)]
Admin(#[from] AdminError),
}
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod contract;
pub mod error;
mod queries;
mod state;
mod storage;
mod support;
mod transactions;
+178
View File
@@ -0,0 +1,178 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
use nym_coconut_bandwidth_contract_common::spend_credential::{
PagedSpendCredentialResponse, SpendCredential, SpendCredentialResponse,
};
use crate::storage::{self, SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT, SPEND_CREDENTIAL_PAGE_MAX_LIMIT};
pub(crate) fn query_all_spent_credentials_paged(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedSpendCredentialResponse> {
let limit = limit
.unwrap_or(SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT)
.min(SPEND_CREDENTIAL_PAGE_MAX_LIMIT) as usize;
let start = start_after.as_deref().map(Bound::exclusive);
let nodes = storage::spent_credentials()
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<SpendCredential>>>()?;
let start_next_after = nodes
.last()
.map(|spend_credential| spend_credential.blinded_serial_number().to_string());
Ok(PagedSpendCredentialResponse::new(
nodes,
limit,
start_next_after,
))
}
pub(crate) fn query_spent_credential(
deps: Deps<'_>,
blinded_serial_number: String,
) -> StdResult<SpendCredentialResponse> {
let spend_credential =
storage::spent_credentials().may_load(deps.storage, &blinded_serial_number)?;
Ok(SpendCredentialResponse::new(spend_credential))
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::support::tests::fixtures::spend_credential_data_fixture;
use crate::support::tests::helpers::init_contract;
use crate::transactions::spend_credential;
use cosmwasm_std::testing::{mock_env, mock_info};
#[test]
fn spent_credentials_empty_on_init() {
let deps = init_contract();
let response =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(2)).unwrap();
assert_eq!(0, response.spend_credentials.len());
}
#[test]
fn spent_credentials_paged_retrieval_obeys_limits() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let limit = 2;
for n in 0..1000 {
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
}
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.spend_credentials.len() as u32);
}
#[test]
fn spent_credentials_paged_retrieval_has_default_limit() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
for n in 0..1000 {
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
}
// query without explicitly setting a limit
let page1 = query_all_spent_credentials_paged(deps.as_ref(), None, None).unwrap();
assert_eq!(
SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT,
page1.spend_credentials.len() as u32
);
}
#[test]
fn spent_credentials_paged_retrieval_has_max_limit() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
for n in 0..1000 {
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
}
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * SPEND_CREDENTIAL_PAGE_MAX_LIMIT;
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(crazy_limit))
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = SPEND_CREDENTIAL_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.spend_credentials.len() as u32);
}
#[test]
fn spent_credentials_pagination_works() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let data = spend_credential_data_fixture("blinded_serial_number1");
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
let per_page = 2;
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.spend_credentials.len());
// save another
let data = spend_credential_data_fixture("blinded_serial_number2");
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
// page1 should have 2 results on it
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.spend_credentials.len());
let data = spend_credential_data_fixture("blinded_serial_number3");
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
// page1 still has 2 results
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.spend_credentials.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_spent_credentials_paged(
deps.as_ref(),
Option::from(start_after.clone()),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.spend_credentials.len());
let data = spend_credential_data_fixture("blinded_serial_number4");
spend_credential(deps.as_mut(), env, info, data).unwrap();
let page2 = query_all_spent_credentials_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.spend_credentials.len());
}
}
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Addr;
use cw_controllers::Admin;
use cw_storage_plus::Item;
use serde::{Deserialize, Serialize};
pub const ADMIN: Admin = Admin::new("admin");
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub struct Config {
pub multisig_addr: Addr,
pub pool_addr: Addr,
pub mix_denom: String,
}
pub const CONFIG: Item<Config> = Item::new("config");
+106
View File
@@ -0,0 +1,106 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cw_storage_plus::{Index, IndexList, IndexedMap, UniqueIndex};
use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredential;
// storage prefixes
const SPEND_CREDENTIAL_PK_NAMESPACE: &str = "sc";
const SPEND_CREDENTIAL_BLINDED_SERIAL_NO_IDX_NAMESPACE: &str = "scn";
// paged retrieval limits for all queries and transactions
pub(crate) const SPEND_CREDENTIAL_PAGE_MAX_LIMIT: u32 = 75;
pub(crate) const SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT: u32 = 50;
pub(crate) struct SpendCredentialIndex<'a> {
pub(crate) blinded_serial_number: UniqueIndex<'a, String, SpendCredential>,
}
// 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 IndexList<SpendCredential> for SpendCredentialIndex<'_> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<SpendCredential>> + '_> {
let v: Vec<&dyn Index<SpendCredential>> = vec![&self.blinded_serial_number];
Box::new(v.into_iter())
}
}
// spent_credentials() is the storage access function.
pub(crate) fn spent_credentials<'a>(
) -> IndexedMap<'a, &'a str, SpendCredential, SpendCredentialIndex<'a>> {
let indexes = SpendCredentialIndex {
blinded_serial_number: UniqueIndex::new(
|d| d.blinded_serial_number().to_string(),
SPEND_CREDENTIAL_BLINDED_SERIAL_NO_IDX_NAMESPACE,
),
};
IndexedMap::new(SPEND_CREDENTIAL_PK_NAMESPACE, indexes)
}
// currently not used outside tests
#[cfg(test)]
mod tests {
use super::super::storage;
use crate::storage::SpendCredential;
use crate::support::tests::fixtures;
use crate::support::tests::fixtures::TEST_MIX_DENOM;
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::Addr;
use cosmwasm_std::Coin;
use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialStatus;
#[test]
fn spend_credential_single_read_retrieval() {
let mut storage = MockStorage::new();
let blind_serial_number1 = "number1";
let blind_serial_number2 = "number2";
let spend1 = fixtures::spend_credential_fixture(blind_serial_number1);
let spend2 = fixtures::spend_credential_fixture(blind_serial_number2);
storage::spent_credentials()
.save(&mut storage, blind_serial_number1, &spend1)
.unwrap();
storage::spent_credentials()
.save(&mut storage, blind_serial_number2, &spend2)
.unwrap();
let res1 = storage::spent_credentials()
.load(&storage, blind_serial_number1)
.unwrap();
let res2 = storage::spent_credentials()
.load(&storage, blind_serial_number2)
.unwrap();
assert_eq!(spend1, res1);
assert_eq!(spend2, res2);
}
#[test]
fn mark_as_spent_credential() {
let mut mock_storage = MockStorage::new();
let funds = Coin::new(100, TEST_MIX_DENOM);
let blind_serial_number = "blind_serial_number";
let gateway_cosmos_address: Addr = Addr::unchecked("gateway_cosmos_address");
let res = storage::spent_credentials()
.may_load(&mock_storage, blind_serial_number)
.unwrap();
assert!(res.is_none());
let mut spend_credential = SpendCredential::new(
funds,
blind_serial_number.to_string(),
gateway_cosmos_address,
);
spend_credential.mark_as_spent();
storage::spent_credentials()
.save(&mut mock_storage, blind_serial_number, &spend_credential)
.unwrap();
let ret = storage::spent_credentials()
.load(&mock_storage, blind_serial_number)
.unwrap();
assert_eq!(ret, spend_credential);
assert_eq!(ret.status(), SpendCredentialStatus::Spent);
}
}
@@ -0,0 +1,5 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
pub mod tests;
@@ -0,0 +1,25 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Addr, Coin};
use nym_coconut_bandwidth_contract_common::spend_credential::{
SpendCredential, SpendCredentialData,
};
pub const TEST_MIX_DENOM: &str = "unym";
pub fn spend_credential_fixture(blinded_serial_number: &str) -> SpendCredential {
SpendCredential::new(
Coin::new(100, TEST_MIX_DENOM),
blinded_serial_number.to_string(),
Addr::unchecked("gateway_owner_addr"),
)
}
pub fn spend_credential_data_fixture(blinded_serial_number: &str) -> SpendCredentialData {
SpendCredentialData::new(
Coin::new(100, TEST_MIX_DENOM),
blinded_serial_number.to_string(),
"gateway_owner_addr".to_string(),
)
}
@@ -0,0 +1,25 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
pub const POOL_CONTRACT: &str = "mix pool contract address";
use crate::contract::instantiate;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{Empty, MemoryStorage, OwnedDeps};
use nym_coconut_bandwidth_contract_common::msg::InstantiateMsg;
use super::fixtures::TEST_MIX_DENOM;
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
multisig_addr: String::from(MULTISIG_CONTRACT),
pool_addr: String::from(POOL_CONTRACT),
mix_denom: TEST_MIX_DENOM.to_string(),
};
let env = mock_env();
let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env, info, msg).unwrap();
deps
}
@@ -0,0 +1,5 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod fixtures;
pub mod helpers;
@@ -0,0 +1,369 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{BankMsg, Coin, DepsMut, Env, Event, MessageInfo, Response};
use nym_coconut_bandwidth_contract_common::spend_credential::{
to_cosmos_msg, SpendCredential, SpendCredentialData,
};
use crate::error::ContractError;
use crate::state::{ADMIN, CONFIG};
use crate::storage;
use nym_coconut_bandwidth_contract_common::deposit::DepositData;
use nym_coconut_bandwidth_contract_common::events::{
DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO,
DEPOSIT_VALUE,
};
pub(crate) fn deposit_funds(
deps: DepsMut<'_>,
_env: Env,
info: MessageInfo,
data: DepositData,
) -> Result<Response, ContractError> {
if info.funds.is_empty() {
return Err(ContractError::NoCoin);
}
if info.funds.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
if info.funds[0].denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
}
let voucher_value = info.funds.last().unwrap();
let event = Event::new(DEPOSITED_FUNDS_EVENT_TYPE)
.add_attribute(DEPOSIT_VALUE, voucher_value.amount)
.add_attribute(DEPOSIT_INFO, data.deposit_info())
.add_attribute(DEPOSIT_IDENTITY_KEY, data.identity_key())
.add_attribute(DEPOSIT_ENCRYPTION_KEY, data.encryption_key());
Ok(Response::new().add_event(event))
}
pub(crate) fn spend_credential(
deps: DepsMut<'_>,
env: Env,
_info: MessageInfo,
data: SpendCredentialData,
) -> Result<Response, ContractError> {
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
if data.funds().denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
}
if storage::spent_credentials().has(deps.storage, data.blinded_serial_number()) {
return Err(ContractError::DuplicateBlindedSerialNumber);
}
let cfg = CONFIG.load(deps.storage)?;
let gateway_cosmos_address = deps.api.addr_validate(data.gateway_cosmos_address())?;
storage::spent_credentials().save(
deps.storage,
data.blinded_serial_number(),
&SpendCredential::new(
data.funds().to_owned(),
data.blinded_serial_number().to_owned(),
gateway_cosmos_address,
),
)?;
let msg = to_cosmos_msg(
data.funds().clone(),
data.blinded_serial_number().to_string(),
env.contract.address.into_string(),
cfg.multisig_addr.into_string(),
)?;
Ok(Response::new().add_message(msg))
}
pub(crate) fn release_funds(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
funds: Coin,
) -> Result<Response, ContractError> {
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
if funds.denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
}
let current_balance = deps
.querier
.query_balance(env.contract.address, mix_denom)?;
if funds.amount > current_balance.amount {
return Err(ContractError::NotEnoughFunds);
}
ADMIN.assert_admin(deps.as_ref(), &info.sender)?;
let cfg = CONFIG.load(deps.storage)?;
let return_tokens = BankMsg::Send {
to_address: cfg.pool_addr.into(),
amount: vec![funds],
};
let response = Response::new().add_message(return_tokens);
Ok(response)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::support::tests::fixtures::spend_credential_data_fixture;
use crate::support::tests::helpers::{self, MULTISIG_CONTRACT, POOL_CONTRACT};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{from_binary, CosmosMsg, WasmMsg};
use cw_controllers::AdminError;
use nym_coconut_bandwidth_contract_common::msg::ExecuteMsg;
use nym_multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg;
#[test]
fn invalid_deposit() {
let mut deps = helpers::init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let deposit_info = String::from("Deposit info");
let verification_key = String::from("Verification key");
let encryption_key = String::from("Encryption key");
let data = DepositData::new(deposit_info, verification_key, encryption_key);
assert_eq!(
deposit_funds(deps.as_mut(), env.clone(), info, data.clone()),
Err(ContractError::NoCoin)
);
let coin = Coin::new(1000000, crate::support::tests::fixtures::TEST_MIX_DENOM);
let second_coin = Coin::new(1000000, "some_denom");
let info = mock_info("requester", &[coin, second_coin.clone()]);
assert_eq!(
deposit_funds(deps.as_mut(), env.clone(), info, data.clone()),
Err(ContractError::MultipleDenoms)
);
let info = mock_info("requester", &[second_coin]);
assert_eq!(
deposit_funds(deps.as_mut(), env, info, data),
Err(ContractError::WrongDenom {
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
})
);
}
#[test]
fn valid_deposit() {
let mut deps = helpers::init_contract();
let env = mock_env();
let deposit_info = String::from("Deposit info");
let verification_key = String::from("Verification key");
let encryption_key = String::from("Encryption key");
let deposit_value = 424242;
let data = DepositData::new(
deposit_info.clone(),
verification_key.clone(),
encryption_key.clone(),
);
let coin = Coin::new(
deposit_value,
crate::support::tests::fixtures::TEST_MIX_DENOM,
);
let info = mock_info("requester", &[coin]);
let tx = deposit_funds(deps.as_mut(), env, info, data).unwrap();
let events: Vec<_> = tx
.events
.iter()
.filter(|event| event.ty == DEPOSITED_FUNDS_EVENT_TYPE)
.collect();
assert_eq!(events.len(), 1);
let event = events[0];
assert_eq!(event.attributes.len(), 4);
let deposit_attr = event
.attributes
.iter()
.find(|attr| attr.key == DEPOSIT_VALUE)
.unwrap();
assert_eq!(deposit_attr.value, deposit_value.to_string());
let info_attr = event
.attributes
.iter()
.find(|attr| attr.key == DEPOSIT_INFO)
.unwrap();
assert_eq!(info_attr.value, deposit_info);
let verification_key_attr = event
.attributes
.iter()
.find(|attr| attr.key == DEPOSIT_IDENTITY_KEY)
.unwrap();
assert_eq!(verification_key_attr.value, verification_key);
let encryption_key_attr = event
.attributes
.iter()
.find(|attr| attr.key == DEPOSIT_ENCRYPTION_KEY)
.unwrap();
assert_eq!(encryption_key_attr.value, encryption_key);
}
#[test]
fn invalid_release() {
let mut deps = helpers::init_contract();
let env = mock_env();
let invalid_admin = "invalid admin";
let funds = Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM);
let err = release_funds(
deps.as_mut(),
env.clone(),
mock_info(invalid_admin, &[]),
Coin::new(1, "invalid denom"),
)
.unwrap_err();
assert_eq!(
err,
ContractError::WrongDenom {
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
}
);
let err = release_funds(
deps.as_mut(),
env.clone(),
mock_info(invalid_admin, &[]),
funds.clone(),
)
.unwrap_err();
assert_eq!(err, ContractError::NotEnoughFunds);
deps.querier
.update_balance(env.contract.address.clone(), vec![funds.clone()]);
let err =
release_funds(deps.as_mut(), env, mock_info(invalid_admin, &[]), funds).unwrap_err();
assert_eq!(err, ContractError::Admin(AdminError::NotAdmin {}));
}
#[test]
fn valid_release() {
let mut deps = helpers::init_contract();
let env = mock_env();
let coin = Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM);
deps.querier
.update_balance(env.contract.address.clone(), vec![coin.clone()]);
let res = release_funds(
deps.as_mut(),
env,
mock_info(MULTISIG_CONTRACT, &[]),
coin.clone(),
)
.unwrap();
assert_eq!(
res.messages[0].msg,
CosmosMsg::Bank(BankMsg::Send {
to_address: String::from(POOL_CONTRACT),
amount: vec![coin]
})
);
}
#[test]
fn valid_spend() {
let mut deps = helpers::init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let data = spend_credential_data_fixture("blinded_serial_number");
let res = spend_credential(deps.as_mut(), env.clone(), info, data.clone()).unwrap();
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &res.messages[0].msg
{
assert_eq!(contract_addr, MULTISIG_CONTRACT);
assert!(funds.is_empty());
let multisig_msg: MultisigExecuteMsg = from_binary(msg).unwrap();
if let MultisigExecuteMsg::Propose {
title: _,
description,
msgs,
latest,
} = multisig_msg
{
assert_eq!(description, data.blinded_serial_number().to_string());
assert!(latest.is_none());
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &msgs[0]
{
assert_eq!(*contract_addr, env.contract.address.into_string());
assert!(funds.is_empty());
let release_funds_req: ExecuteMsg = from_binary(msg).unwrap();
if let ExecuteMsg::ReleaseFunds { funds } = release_funds_req {
assert_eq!(funds, *data.funds());
} else {
panic!("Could not extract release funds message from proposal");
}
}
} else {
panic!("Could not extract proposal from binary blob");
}
} else {
panic!("Wasm execute message not found");
}
}
#[test]
fn invalid_spend_attempts() {
let mut deps = helpers::init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let invalid_data = SpendCredentialData::new(
Coin::new(1, "invalid_denom".to_string()),
String::new(),
String::new(),
);
let ret = spend_credential(deps.as_mut(), env.clone(), info.clone(), invalid_data);
assert_eq!(
ret.unwrap_err(),
ContractError::WrongDenom {
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
}
);
let invalid_data = SpendCredentialData::new(
Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM),
String::new(),
"Blinded Serial Number".to_string(),
);
let ret = spend_credential(deps.as_mut(), env.clone(), info.clone(), invalid_data);
assert_eq!(
ret.unwrap_err().to_string(),
"Generic error: Invalid input: address not normalized".to_string()
);
let invalid_data = spend_credential_data_fixture("blined_serial_number");
spend_credential(
deps.as_mut(),
env.clone(),
info.clone(),
invalid_data.clone(),
)
.unwrap();
let ret = spend_credential(deps.as_mut(), env, info, invalid_data);
assert_eq!(
ret.unwrap_err(),
ContractError::DuplicateBlindedSerialNumber
);
}
}
+3 -6
View File
@@ -1,10 +1,7 @@
[package]
name = "nym-coconut-dkg"
version = "0.1.0"
edition = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -21,15 +18,15 @@ nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts
cosmwasm-schema = { workspace = true, optional = true }
cosmwasm-std = { workspace = true }
cosmwasm-storage = { workspace = true }
cw-storage-plus = { workspace = true }
cw-controllers = { workspace = true }
cw2 = { workspace = true }
cw4 = { workspace = true }
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { workspace = true }
[dev-dependencies]
easy-addr = { path = "../../common/cosmwasm-smart-contracts/easy_addr" }
cw-multi-test = { workspace = true }
cw4-group = { path = "../multisig/cw4-group" }
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
@@ -1357,7 +1357,9 @@
},
"dealing_submission_status": {
"type": "object",
"additionalProperties": false
"additionalProperties": {
"$ref": "#/definitions/DealingStatus"
}
},
"epoch_id": {
"type": "integer",
@@ -1395,7 +1397,9 @@
"properties": {
"chunk_submission_status": {
"type": "object",
"additionalProperties": false
"additionalProperties": {
"$ref": "#/definitions/ChunkSubmissionStatus"
}
},
"fully_submitted": {
"type": "boolean"
@@ -1740,7 +1744,9 @@
"properties": {
"chunk_submission_status": {
"type": "object",
"additionalProperties": false
"additionalProperties": {
"$ref": "#/definitions/ChunkSubmissionStatus"
}
},
"fully_submitted": {
"type": "boolean"
@@ -1835,7 +1841,9 @@
},
"submitted_chunks": {
"type": "object",
"additionalProperties": false
"additionalProperties": {
"$ref": "#/definitions/SubmittedChunk"
}
}
},
"additionalProperties": false
@@ -17,7 +17,9 @@
},
"dealing_submission_status": {
"type": "object",
"additionalProperties": false
"additionalProperties": {
"$ref": "#/definitions/DealingStatus"
}
},
"epoch_id": {
"type": "integer",
@@ -55,7 +57,9 @@
"properties": {
"chunk_submission_status": {
"type": "object",
"additionalProperties": false
"additionalProperties": {
"$ref": "#/definitions/ChunkSubmissionStatus"
}
},
"fully_submitted": {
"type": "boolean"
@@ -56,7 +56,9 @@
"properties": {
"chunk_submission_status": {
"type": "object",
"additionalProperties": false
"additionalProperties": {
"$ref": "#/definitions/ChunkSubmissionStatus"
}
},
"fully_submitted": {
"type": "boolean"
@@ -80,7 +80,9 @@
},
"submitted_chunks": {
"type": "object",
"additionalProperties": false
"additionalProperties": {
"$ref": "#/definitions/SubmittedChunk"
}
}
},
"additionalProperties": false
+24 -30
View File
@@ -26,7 +26,7 @@ use crate::verification_key_shares::queries::{query_vk_share, query_vk_shares_pa
use crate::verification_key_shares::transactions::try_commit_verification_key_share;
use crate::verification_key_shares::transactions::try_verify_verification_key_share;
use cosmwasm_std::{
entry_point, to_json_binary, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
entry_point, to_binary, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
};
use cw4::Cw4Contract;
use nym_coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
@@ -129,52 +129,50 @@ pub fn execute(
#[entry_point]
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let response = match msg {
QueryMsg::GetState {} => to_json_binary(&query_state(deps.storage)?)?,
QueryMsg::GetCurrentEpochState {} => to_json_binary(&query_current_epoch(deps.storage)?)?,
QueryMsg::CanAdvanceState {} => {
to_json_binary(&query_can_advance_state(deps.storage, env)?)?
}
QueryMsg::GetState {} => to_binary(&query_state(deps.storage)?)?,
QueryMsg::GetCurrentEpochState {} => to_binary(&query_current_epoch(deps.storage)?)?,
QueryMsg::CanAdvanceState {} => to_binary(&query_can_advance_state(deps.storage, env)?)?,
QueryMsg::GetCurrentEpochThreshold {} => {
to_json_binary(&query_current_epoch_threshold(deps.storage)?)?
to_binary(&query_current_epoch_threshold(deps.storage)?)?
}
QueryMsg::GetEpochThreshold { epoch_id } => {
to_json_binary(&query_epoch_threshold(deps.storage, epoch_id)?)?
to_binary(&query_epoch_threshold(deps.storage, epoch_id)?)?
}
QueryMsg::GetRegisteredDealer {
dealer_address,
epoch_id,
} => to_json_binary(&query_registered_dealer_details(
} => to_binary(&query_registered_dealer_details(
deps,
dealer_address,
epoch_id,
)?)?,
QueryMsg::GetDealerDetails { dealer_address } => {
to_json_binary(&query_dealer_details(deps, dealer_address)?)?
to_binary(&query_dealer_details(deps, dealer_address)?)?
}
QueryMsg::GetCurrentDealers { limit, start_after } => {
to_json_binary(&query_current_dealers_paged(deps, start_after, limit)?)?
to_binary(&query_current_dealers_paged(deps, start_after, limit)?)?
}
QueryMsg::GetDealerIndices { limit, start_after } => {
to_json_binary(&query_dealers_indices_paged(deps, start_after, limit)?)?
to_binary(&query_dealers_indices_paged(deps, start_after, limit)?)?
}
QueryMsg::GetDealingsMetadata {
epoch_id,
dealer,
dealing_index,
} => to_json_binary(&query_dealing_metadata(
} => to_binary(&query_dealing_metadata(
deps,
epoch_id,
dealer,
dealing_index,
)?)?,
QueryMsg::GetDealerDealingsStatus { epoch_id, dealer } => {
to_json_binary(&query_dealer_dealings_status(deps, epoch_id, dealer)?)?
to_binary(&query_dealer_dealings_status(deps, epoch_id, dealer)?)?
}
QueryMsg::GetDealingStatus {
epoch_id,
dealer,
dealing_index,
} => to_json_binary(&query_dealing_status(
} => to_binary(&query_dealing_status(
deps,
epoch_id,
dealer,
@@ -185,7 +183,7 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
dealer,
dealing_index,
chunk_index,
} => to_json_binary(&query_dealing_chunk_status(
} => to_binary(&query_dealing_chunk_status(
deps,
epoch_id,
dealer,
@@ -197,7 +195,7 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
dealer,
dealing_index,
chunk_index,
} => to_json_binary(&query_dealing_chunk(
} => to_binary(&query_dealing_chunk(
deps,
epoch_id,
dealer,
@@ -205,16 +203,14 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
chunk_index,
)?)?,
QueryMsg::GetVerificationKey { owner, epoch_id } => {
to_json_binary(&query_vk_share(deps, owner, epoch_id)?)?
to_binary(&query_vk_share(deps, owner, epoch_id)?)?
}
QueryMsg::GetVerificationKeys {
epoch_id,
limit,
start_after,
} => to_json_binary(&query_vk_shares_paged(deps, epoch_id, start_after, limit)?)?,
QueryMsg::GetCW2ContractVersion {} => {
to_json_binary(&cw2::get_contract_version(deps.storage)?)?
}
} => to_binary(&query_vk_shares_paged(deps, epoch_id, start_after, limit)?)?,
QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?)?,
};
Ok(response)
@@ -240,7 +236,7 @@ mod tests {
use super::*;
use crate::support::tests::fixtures::TEST_MIX_DENOM;
use crate::support::tests::helpers::{ADMIN_ADDRESS, MULTISIG_CONTRACT};
use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env, MockApi};
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, Addr};
use cw4::Member;
use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor};
@@ -315,13 +311,13 @@ mod tests {
let mut deps = mock_dependencies();
let env = mock_env();
let msg = InstantiateMsg {
group_addr: deps.api.addr_make("group_addr").to_string(),
multisig_addr: deps.api.addr_make("multisig_addr").to_string(),
group_addr: "group_addr".to_string(),
multisig_addr: "multisig_addr".to_string(),
time_configuration: None,
mix_denom: "nym".to_string(),
key_size: 5,
};
let info = message_info(&deps.api.addr_make("creator"), &[]);
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), env, info, msg);
assert!(res.is_ok())
@@ -330,11 +326,9 @@ mod tests {
#[test]
fn execute_add_dealer() {
let init_funds = coins(100, TEST_MIX_DENOM);
let api = MockApi::default();
const MEMBER_SIZE: usize = 100;
let members: [Addr; MEMBER_SIZE] =
std::array::from_fn(|idx| api.addr_make(&format!("member{}", idx)));
std::array::from_fn(|idx| Addr::unchecked(format!("member{}", idx)));
let mut app = AppBuilder::new().build(|router, _, storage| {
router
@@ -384,7 +378,7 @@ mod tests {
assert_eq!(ContractError::AlreadyADealer, err.downcast().unwrap());
}
let unauthorized_member = MockApi::default().addr_make("not_a_member");
let unauthorized_member = Addr::unchecked("not_a_member");
let err = app
.execute_contract(
unauthorized_member,
+22 -31
View File
@@ -131,30 +131,21 @@ pub(crate) mod tests {
use crate::dealers::storage::{DEALERS_PAGE_DEFAULT_LIMIT, DEALERS_PAGE_MAX_LIMIT};
use crate::support::tests::fixtures::dealer_details_fixture;
use crate::support::tests::helpers::{init_contract, insert_dealer};
use cosmwasm_std::testing::{MockApi, MockQuerier};
use cosmwasm_std::{Empty, MemoryStorage, OwnedDeps};
use cosmwasm_std::DepsMut;
fn fill_dealers(
deps: &mut OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>>,
epoch_id: EpochId,
size: usize,
) {
fn fill_dealers(mut deps: DepsMut<'_>, epoch_id: EpochId, size: usize) {
for assigned_index in 0..size {
let dealer_details = dealer_details_fixture(&deps.api, assigned_index as u64);
insert_dealer(deps.as_mut(), epoch_id, &dealer_details);
let dealer_details = dealer_details_fixture(assigned_index as u64);
insert_dealer(deps.branch(), epoch_id, &dealer_details);
}
}
fn remove_dealers(
deps: &mut OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>>,
epoch_id: EpochId,
size: usize,
) {
fn remove_dealers(deps: DepsMut<'_>, epoch_id: EpochId, size: usize) {
for assigned_index in 0..size {
let dealer_details = dealer_details_fixture(&deps.api, assigned_index as u64);
DEALERS_INDICES.remove(deps.as_mut().storage, &dealer_details.address);
let dealer_details = dealer_details_fixture(assigned_index as u64);
DEALERS_INDICES.remove(deps.storage, &dealer_details.address);
EPOCH_DEALERS_MAP.remove(deps.as_mut().storage, (epoch_id, &dealer_details.address));
EPOCH_DEALERS_MAP.remove(deps.storage, (epoch_id, &dealer_details.address));
}
}
@@ -171,26 +162,26 @@ pub(crate) mod tests {
let mut deps = init_contract();
let limit = 2;
fill_dealers(&mut deps, 0, 1000);
fill_dealers(deps.as_mut(), 0, 1000);
let page1 = query_current_dealers_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.dealers.len() as u32);
remove_dealers(&mut deps, 0, 1000);
remove_dealers(deps.as_mut(), 0, 1000);
}
#[test]
fn dealers_paged_retrieval_has_default_limit() {
let mut deps = init_contract();
fill_dealers(&mut deps, 0, 1000);
fill_dealers(deps.as_mut(), 0, 1000);
// query without explicitly setting a limit
let page1 = query_current_dealers_paged(deps.as_ref(), None, None).unwrap();
assert_eq!(DEALERS_PAGE_DEFAULT_LIMIT, page1.dealers.len() as u32);
remove_dealers(&mut deps, 0, 1000);
remove_dealers(deps.as_mut(), 0, 1000);
}
#[test]
@@ -200,7 +191,7 @@ pub(crate) mod tests {
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * DEALERS_PAGE_MAX_LIMIT;
fill_dealers(&mut deps, 0, 1000);
fill_dealers(deps.as_mut(), 0, 1000);
let page1 =
query_current_dealers_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap();
@@ -209,7 +200,7 @@ pub(crate) mod tests {
let expected_limit = DEALERS_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.dealers.len() as u32);
remove_dealers(&mut deps, 0, 1000);
remove_dealers(deps.as_mut(), 0, 1000);
}
#[test]
@@ -218,22 +209,22 @@ pub(crate) mod tests {
let per_page = 2;
fill_dealers(&mut deps, 0, 1);
fill_dealers(deps.as_mut(), 0, 1);
let page1 =
query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.dealers.len());
remove_dealers(&mut deps, 0, 1);
remove_dealers(deps.as_mut(), 0, 1);
fill_dealers(&mut deps, 0, 2);
fill_dealers(deps.as_mut(), 0, 2);
// page1 should have 2 results on it
let page1 =
query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.dealers.len());
remove_dealers(&mut deps, 0, 2);
remove_dealers(deps.as_mut(), 0, 2);
fill_dealers(&mut deps, 0, 3);
fill_dealers(deps.as_mut(), 0, 3);
// page1 still has 2 results
let page1 =
query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
@@ -249,9 +240,9 @@ pub(crate) mod tests {
.unwrap();
assert_eq!(1, page2.dealers.len());
remove_dealers(&mut deps, 0, 3);
remove_dealers(deps.as_mut(), 0, 3);
fill_dealers(&mut deps, 0, 4);
fill_dealers(deps.as_mut(), 0, 4);
let page1 =
query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
let start_after = page1.start_next_after.unwrap();
@@ -264,6 +255,6 @@ pub(crate) mod tests {
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.dealers.len());
remove_dealers(&mut deps, 0, 4);
remove_dealers(deps.as_mut(), 0, 4);
}
}
@@ -88,7 +88,7 @@ pub(crate) mod tests {
use crate::epoch_state::transactions::{try_advance_epoch_state, try_initiate_dkg};
use crate::support::tests::helpers;
use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS};
use cosmwasm_std::testing::{message_info, mock_env};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::Addr;
use nym_coconut_dkg_common::types::TimeConfiguration;
@@ -96,15 +96,10 @@ pub(crate) mod tests {
fn invalid_state() {
let mut deps = helpers::init_contract();
let mut env = mock_env();
try_initiate_dkg(
deps.as_mut(),
env.clone(),
message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]),
)
.unwrap();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let owner = deps.api.addr_make("owner");
let info = message_info(&owner, &[]);
let owner = Addr::unchecked("owner");
let info = mock_info(owner.as_str(), &[]);
let bte_key_with_proof = String::from("bte_key_with_proof");
let identity = String::from("identity");
let announce_address = String::from("localhost:8000");
+12 -11
View File
@@ -168,26 +168,29 @@ pub(crate) mod tests {
let mut deps = init_contract();
let bad_address = "FOOMP".to_string();
let good_address = deps.api.addr_make("foo");
assert!(query_dealing_chunk(deps.as_ref(), 0, bad_address, 0, 0).is_err());
let empty = query_dealing_chunk(deps.as_ref(), 0, good_address.to_string(), 0, 0).unwrap();
let empty = query_dealing_chunk(deps.as_ref(), 0, "foo".to_string(), 0, 0).unwrap();
assert_eq!(empty.epoch_id, 0);
assert_eq!(empty.dealing_index, 0);
assert_eq!(empty.chunk_index, 0);
assert_eq!(empty.dealer, good_address);
assert_eq!(empty.dealer, Addr::unchecked("foo"));
assert!(empty.chunk.is_none());
// insert the dealing chunk
let dealing = partial_dealing_fixture();
StoredDealing::save(deps.as_mut().storage, 0, &good_address, dealing.clone());
StoredDealing::save(
deps.as_mut().storage,
0,
&Addr::unchecked("foo"),
dealing.clone(),
);
let retrieved =
query_dealing_chunk(deps.as_ref(), 0, good_address.to_string(), 0, 0).unwrap();
let retrieved = query_dealing_chunk(deps.as_ref(), 0, "foo".to_string(), 0, 0).unwrap();
assert_eq!(retrieved.epoch_id, 0);
assert_eq!(retrieved.dealing_index, dealing.dealing_index);
assert_eq!(retrieved.chunk_index, dealing.chunk_index);
assert_eq!(retrieved.dealer, good_address);
assert_eq!(retrieved.dealer, Addr::unchecked("foo"));
assert_eq!(retrieved.chunk.unwrap(), dealing.data);
}
@@ -198,12 +201,10 @@ pub(crate) mod tests {
let bad_address = "FOOMP".to_string();
assert!(query_dealing_status(deps.as_ref(), 0, bad_address, 0).is_err());
let empty =
query_dealing_status(deps.as_ref(), 0, deps.api.addr_make("foo").to_string(), 0)
.unwrap();
let empty = query_dealing_status(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
assert_eq!(empty.epoch_id, 0);
assert_eq!(empty.dealing_index, 0);
assert_eq!(empty.dealer, deps.api.addr_make("foo"));
assert_eq!(empty.dealer, Addr::unchecked("foo"));
assert!(!empty.status.fully_submitted);
assert!(!empty.status.has_metadata);
assert!(empty.status.chunk_submission_status.is_empty());
+32 -30
View File
@@ -77,10 +77,17 @@ impl StoredDealing {
#[cfg(test)]
fn prefix(
prefix: (EpochId, Dealer, DealingIndex),
) -> cw_storage_plus::Prefix<ChunkIndex, PartialContractDealingData> {
) -> cw_storage_plus::Prefix<ChunkIndex, PartialContractDealingData, ChunkIndex> {
use cw_storage_plus::Prefixer;
cw_storage_plus::Prefix::new(Self::NAMESPACE, &prefix.prefix())
cw_storage_plus::Prefix::with_deserialization_functions(
Self::NAMESPACE,
&prefix.prefix(),
&[],
// explicitly panic to make sure we're never attempting to call an unexpected deserializer on our data
|_, _, kv| Self::deserialize_dealing_record(kv),
|_, _, _| panic!("attempted to call custom de_fn_v"),
)
}
// prefix-range related should we need it
@@ -91,23 +98,15 @@ impl StoredDealing {
start: Option<cw_storage_plus::Bound<ChunkIndex>>,
) -> impl Iterator<Item = cosmwasm_std::StdResult<PartialContractDealing>> + 'a {
let dealing_index = prefix.2;
let prefix = Self::prefix(prefix);
cw_storage_plus::range_with_prefix(
storage,
&prefix,
start.map(|b| b.to_raw_bound()),
None,
cosmwasm_std::Order::Ascending,
)
.map(Self::deserialize_dealing_record)
.map(move |maybe_record| {
maybe_record.map(|(chunk_index, data)| PartialContractDealing {
dealing_index,
chunk_index,
data,
Self::prefix(prefix)
.range(storage, start, None, cosmwasm_std::Order::Ascending)
.map(move |maybe_record| {
maybe_record.map(|(chunk_index, data)| PartialContractDealing {
dealing_index,
chunk_index,
data,
})
})
})
}
fn storage_key(
@@ -187,19 +186,22 @@ impl StoredDealing {
type StorageKey<'a> = (EpochId, Dealer<'a>, (DealingIndex, ChunkIndex));
let empty_prefix: cw_storage_plus::Prefix<StorageKey, PartialContractDealingData> =
cw_storage_plus::Prefix::new(Self::NAMESPACE, &[]);
let empty_prefix: cw_storage_plus::Prefix<
StorageKey,
PartialContractDealingData,
StorageKey,
> = cw_storage_plus::Prefix::with_deserialization_functions(
Self::NAMESPACE,
&[],
&[],
|_, _, kv| StorageKey::from_vec(kv.0).map(|kt| (kt, ContractSafeBytes(kv.1))),
|_, _, _| unimplemented!(),
);
cw_storage_plus::range_with_prefix(
storage,
&empty_prefix,
None,
None,
cosmwasm_std::Order::Ascending,
)
.map(|kv| StorageKey::from_vec(kv.0).map(|kt| (kt, ContractSafeBytes(kv.1))))
.collect::<cosmwasm_std::StdResult<_>>()
.unwrap()
empty_prefix
.range(storage, None, None, cosmwasm_std::Order::Ascending)
.collect::<cosmwasm_std::StdResult<_>>()
.unwrap()
}
}
@@ -210,7 +210,7 @@ pub(crate) mod tests {
use crate::support::tests::fixtures::{dealing_metadata_fixture, partial_dealing_fixture};
use crate::support::tests::helpers;
use crate::support::tests::helpers::{add_current_dealer, re_register_dealer, ADMIN_ADDRESS};
use cosmwasm_std::testing::{message_info, mock_env};
use cosmwasm_std::testing::{mock_env, mock_info};
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::types::{ContractSafeBytes, TimeConfiguration};
@@ -218,15 +218,10 @@ pub(crate) mod tests {
fn invalid_commit_dealing_chunk() {
let mut deps = helpers::init_contract();
let mut env = mock_env();
try_initiate_dkg(
deps.as_mut(),
env.clone(),
message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]),
)
.unwrap();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let owner = deps.api.addr_make("owner1");
let info = message_info(&owner, &[]);
let owner = Addr::unchecked("owner1");
let info = mock_info(owner.as_str(), &[]);
let chunk = partial_dealing_fixture();
// no dealing metadata
@@ -57,8 +57,7 @@ pub(crate) mod test {
use super::*;
use crate::epoch_state::transactions::try_initiate_dkg;
use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS};
use cosmwasm_std::testing::{message_info, mock_env};
use cosmwasm_std::Addr;
use cosmwasm_std::testing::{mock_env, mock_info};
use nym_coconut_dkg_common::types::TimeConfiguration;
#[test]
@@ -69,12 +68,7 @@ pub(crate) mod test {
assert_eq!(epoch.deadline, None);
let env = mock_env();
try_initiate_dkg(
deps.as_mut(),
env.clone(),
message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]),
)
.unwrap();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let epoch = query_current_epoch(deps.as_mut().storage).unwrap();
assert_eq!(
@@ -4,7 +4,7 @@
use cw_storage_plus::{Item, Map};
use nym_coconut_dkg_common::types::{Epoch, EpochId};
pub(crate) const CURRENT_EPOCH: Item<Epoch> = Item::new("current_epoch");
pub(crate) const CURRENT_EPOCH: Item<'_, Epoch> = Item::new("current_epoch");
pub const THRESHOLD: Item<u64> = Item::new("threshold");
@@ -95,8 +95,8 @@ mod tests {
use crate::error::ContractError::EarlyEpochStateAdvancement;
use crate::state::storage::STATE;
use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS};
use cosmwasm_std::testing::{message_info, mock_env};
use cosmwasm_std::{Addr, StdResult, Storage};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{StdResult, Storage};
use nym_coconut_dkg_common::types::TimeConfiguration;
#[test]
@@ -359,12 +359,7 @@ mod tests {
ContractError::WaitingInitialisation
);
try_initiate_dkg(
deps.as_mut(),
env.clone(),
message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]),
)
.unwrap();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
@@ -589,12 +584,7 @@ mod tests {
fn verify_threshold() {
let mut deps = init_contract();
let mut env = mock_env();
try_initiate_dkg(
deps.as_mut(),
env.clone(),
message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]),
)
.unwrap();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
assert!(THRESHOLD.may_load(deps.as_mut().storage).unwrap().is_none());
@@ -90,8 +90,7 @@ pub(crate) fn try_trigger_resharing(
pub(crate) mod tests {
use super::*;
use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS};
use cosmwasm_std::testing::{message_info, mock_env};
use cosmwasm_std::Addr;
use cosmwasm_std::testing::{mock_env, mock_info};
use cw_controllers::AdminError;
#[test]
@@ -102,26 +101,17 @@ pub(crate) mod tests {
let initial_epoch_info = CURRENT_EPOCH.load(&deps.storage).unwrap();
assert!(initial_epoch_info.deadline.is_none());
let not_admin = deps.api.addr_make("not an admin");
// can only be executed by the admin
let res = try_initiate_dkg(deps.as_mut(), env.clone(), message_info(&not_admin, &[]))
let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info("not an admin", &[]))
.unwrap_err();
assert_eq!(ContractError::Admin(AdminError::NotAdmin {}), res);
let res = try_initiate_dkg(
deps.as_mut(),
env.clone(),
message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]),
);
let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[]));
assert!(res.is_ok());
// can't be initialised more than once
let res = try_initiate_dkg(
deps.as_mut(),
env.clone(),
message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]),
)
.unwrap_err();
let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[]))
.unwrap_err();
assert_eq!(ContractError::AlreadyInitialised, res);
// sets the correct epoch data
@@ -1,7 +1,6 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::testing::MockApi;
use cosmwasm_std::Addr;
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::dealing::{DealingChunkInfo, PartialContractDealing};
@@ -41,9 +40,9 @@ pub fn dealing_metadata_fixture() -> Vec<DealingChunkInfo> {
}]
}
pub fn dealer_details_fixture(api: &MockApi, assigned_index: u64) -> DealerDetails {
pub fn dealer_details_fixture(assigned_index: u64) -> DealerDetails {
DealerDetails {
address: api.addr_make(&format!("owner{}", assigned_index)),
address: Addr::unchecked(format!("owner{}", assigned_index)),
bte_public_key_with_proof: "".to_string(),
ed25519_identity: "".to_string(),
announce_address: "".to_string(),
@@ -4,13 +4,12 @@
use crate::contract::instantiate;
use crate::dealers::storage::{DEALERS_INDICES, EPOCH_DEALERS_MAP};
use crate::epoch_state::storage::CURRENT_EPOCH;
use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env, MockApi, MockQuerier};
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{
from_json, to_json_binary, Addr, ContractResult, DepsMut, Empty, MemoryStorage, OwnedDeps,
from_binary, to_binary, Addr, ContractResult, DepsMut, Empty, MemoryStorage, OwnedDeps,
QuerierResult, SystemResult, WasmQuery,
};
use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse};
use easy_addr::addr;
use nym_coconut_dkg_common::dealer::DealerRegistrationDetails;
use nym_coconut_dkg_common::dealing::DEFAULT_DEALINGS;
use nym_coconut_dkg_common::msg::InstantiateMsg;
@@ -19,9 +18,9 @@ use std::sync::Mutex;
use super::fixtures::TEST_MIX_DENOM;
pub const ADMIN_ADDRESS: &str = addr!("admin address");
pub const GROUP_CONTRACT: &str = addr!("group contract address");
pub const MULTISIG_CONTRACT: &str = addr!("multisig contract address");
pub const ADMIN_ADDRESS: &str = "admin address";
pub const GROUP_CONTRACT: &str = "group contract address";
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
// wtf, why is this a thing?
pub(crate) static GROUP_MEMBERS: Mutex<Vec<(Member, u64)>> = Mutex::new(Vec::new());
@@ -80,7 +79,7 @@ fn querier_handler(query: &WasmQuery) -> QuerierResult {
if contract_addr != GROUP_CONTRACT {
panic!("Not supported");
}
match from_json(msg) {
match from_binary(msg) {
Ok(Cw4QueryMsg::Member { addr, at_height }) => {
let weight = GROUP_MEMBERS.lock().unwrap().iter().find_map(|(m, h)| {
if m.addr == addr {
@@ -94,7 +93,7 @@ fn querier_handler(query: &WasmQuery) -> QuerierResult {
None
}
});
to_json_binary(&MemberResponse { weight }).unwrap()
to_binary(&MemberResponse { weight }).unwrap()
}
Ok(Cw4QueryMsg::ListMembers { .. }) => {
let members = GROUP_MEMBERS
@@ -103,7 +102,7 @@ fn querier_handler(query: &WasmQuery) -> QuerierResult {
.iter()
.map(|m| m.0.clone())
.collect();
to_json_binary(&MemberListResponse { members }).unwrap()
to_binary(&MemberListResponse { members }).unwrap()
}
_ => panic!("Not supported"),
}
@@ -124,7 +123,7 @@ pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>>
key_size: DEFAULT_DEALINGS as u32,
};
let env = mock_env();
let info = message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]);
let info = mock_info(ADMIN_ADDRESS, &[]);
instantiate(deps.as_mut(), env, info, msg).unwrap();
deps
}
@@ -158,17 +158,8 @@ pub(crate) mod tests {
fn vk_shares_pagination_works() {
let mut deps = init_contract();
let mut owners = [
deps.api.addr_make("owner1"),
deps.api.addr_make("owner2"),
deps.api.addr_make("owner3"),
deps.api.addr_make("owner4"),
];
// sort them due to how values are saved
owners.sort();
let owner = owners[0].clone();
let vk_share = vk_share_fixture(owner.as_ref(), 0);
let owner = format!("owner{}", 1);
let vk_share = vk_share_fixture(&owner, 0);
let sender = Addr::unchecked(owner);
vk_shares()
.save(&mut deps.storage, (&sender, 0), &vk_share)
@@ -181,8 +172,8 @@ pub(crate) mod tests {
assert_eq!(1, page1.shares.len());
// save another
let owner = owners[1].clone();
let vk_share = vk_share_fixture(owner.as_str(), 0);
let owner = format!("owner{}", 2);
let vk_share = vk_share_fixture(&owner, 0);
let sender = Addr::unchecked(owner);
vk_shares()
.save(&mut deps.storage, (&sender, 0), &vk_share)
@@ -192,8 +183,8 @@ pub(crate) mod tests {
let page1 = query_vk_shares_paged(deps.as_ref(), 0, None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.shares.len());
let owner = owners[2].clone();
let vk_share = vk_share_fixture(owner.as_str(), 0);
let owner = format!("owner{}", 3);
let vk_share = vk_share_fixture(&owner, 0);
let sender = Addr::unchecked(owner);
vk_shares()
.save(&mut deps.storage, (&sender, 0), &vk_share)
@@ -215,8 +206,8 @@ pub(crate) mod tests {
assert_eq!(1, page2.shares.len());
let owner = owners[3].clone();
let vk_share = vk_share_fixture(owner.as_str(), 0);
let owner = format!("owner{}", 4);
let vk_share = vk_share_fixture(&owner, 0);
let sender = Addr::unchecked(owner);
vk_shares()
.save(&mut deps.storage, (&sender, 0), &vk_share)
@@ -23,7 +23,7 @@ impl IndexList<ContractVKShare> for VkShareIndex<'_> {
}
}
pub(crate) fn vk_shares<'a>() -> IndexedMap<VKShareKey<'a>, ContractVKShare, VkShareIndex<'a>> {
pub(crate) fn vk_shares<'a>() -> IndexedMap<'a, VKShareKey<'a>, ContractVKShare, VkShareIndex<'a>> {
let indexes = VkShareIndex {
epoch_id: MultiIndex::new(
|_pk, d| d.epoch_id,
@@ -106,7 +106,7 @@ mod tests {
use crate::support::tests::helpers::{
add_current_dealer, add_fixture_dealer, ADMIN_ADDRESS, MULTISIG_CONTRACT,
};
use cosmwasm_std::testing::{message_info, mock_env};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::Addr;
use cw_controllers::AdminError;
use nym_coconut_dkg_common::dealer::DealerDetails;
@@ -116,14 +116,9 @@ mod tests {
fn current_epoch_id() {
let mut deps = helpers::init_contract();
let mut env = mock_env();
try_initiate_dkg(
deps.as_mut(),
env.clone(),
message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]),
)
.unwrap();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let info = message_info(&deps.api.addr_make("requester"), &[]);
let info = mock_info("requester", &[]);
let share = "share".to_string();
add_fixture_dealer(deps.as_mut());
@@ -137,7 +132,7 @@ mod tests {
.time
.plus_seconds(TimeConfiguration::default().dealing_exchange_time_secs);
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let dealer = deps.api.addr_make("requester");
let dealer = Addr::unchecked("requester");
let announce_address = String::from("localhost");
let dealer_details = DealerDetails {
address: dealer.clone(),
@@ -168,14 +163,9 @@ mod tests {
fn commit_vk_share() {
let mut deps = helpers::init_contract();
let mut env = mock_env();
try_initiate_dkg(
deps.as_mut(),
env.clone(),
message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]),
)
.unwrap();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let info = message_info(&deps.api.addr_make("requester"), &[]);
let info = mock_info("requester", &[]);
let share = "share".to_string();
let ret = try_commit_verification_key_share(
@@ -215,7 +205,7 @@ mod tests {
.unwrap_err();
assert_eq!(ret, ContractError::NotADealer { epoch_id: 0 });
let dealer = deps.api.addr_make("requester");
let dealer = Addr::unchecked("requester");
let dealer_details = DealerDetails {
address: dealer.clone(),
bte_public_key_with_proof: String::new(),
@@ -248,16 +238,11 @@ mod tests {
fn invalid_verify_vk_share() {
let mut deps = helpers::init_contract();
let mut env = mock_env();
try_initiate_dkg(
deps.as_mut(),
env.clone(),
message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]),
)
.unwrap();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let info = message_info(&deps.api.addr_make("requester"), &[]);
let owner = deps.api.addr_make("owner").to_string();
let multisig_info = message_info(&Addr::unchecked(MULTISIG_CONTRACT), &[]);
let info = mock_info("requester", &[]);
let owner = "owner".to_string();
let multisig_info = mock_info(MULTISIG_CONTRACT, &[]);
let ret =
try_verify_verification_key_share(deps.as_mut(), info.clone(), owner.clone(), false)
@@ -312,17 +297,12 @@ mod tests {
fn verify_vk_share() {
let mut deps = helpers::init_contract();
let mut env = mock_env();
try_initiate_dkg(
deps.as_mut(),
env.clone(),
message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]),
)
.unwrap();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let owner = deps.api.addr_make("owner");
let info = message_info(&owner, &[]);
let owner = "owner".to_string();
let info = mock_info(owner.as_ref(), &[]);
let share = "share".to_string();
let multisig_info = message_info(&Addr::unchecked(MULTISIG_CONTRACT), &[]);
let multisig_info = mock_info(MULTISIG_CONTRACT, &[]);
add_fixture_dealer(deps.as_mut());
env.block.time = env
@@ -358,7 +338,6 @@ mod tests {
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
try_advance_epoch_state(deps.as_mut(), env).unwrap();
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.to_string(), false)
.unwrap();
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner, false).unwrap();
}
}
+39
View File
@@ -0,0 +1,39 @@
[package]
name = "coconut-test"
version = "0.1.0"
edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nym-coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" }
nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
cosmwasm-std = { workspace = true }
cosmwasm-storage = { workspace = true }
cw3 = { workspace = true }
cw4 = { workspace = true }
cw-storage-plus = { workspace = true }
cw-controllers = { workspace = true }
cw-utils = { workspace = true }
subtle-encoding = { version = "0.5", features = ["bech32-preview"] }
bs58 = "0.4.0"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { workspace = true }
nym-coconut-bandwidth = { path = "../coconut-bandwidth" }
nym-coconut-dkg = { path = "../coconut-dkg" }
cw-multi-test = { workspace = true }
cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig" }
cw4-group = { path = "../multisig/cw4-group" }
rand_chacha = "0.3"
[[test]]
name = "coconut-test"
path = "src/tests.rs"

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