Compare commits

...

80 Commits

Author SHA1 Message Date
Simon Wicky 7915e05021 change log level for key storage 2023-06-05 13:34:25 +02:00
Simon Wicky dfc12b1aaa freeze secret for packet gen 2023-05-30 14:44:39 +00:00
Simon Wicky 5ed97fb3c9 add rey reuse 2023-05-30 13:50:10 +00:00
farbanas 8e021a4419 update Cargo.lock 2023-05-16 10:33:04 +02:00
farbanas e5db7cb915 update changelog for release v1.1.19 2023-05-16 10:30:06 +02:00
mx 9bd03af3e9 version bump 2023-05-16 10:14:27 +02:00
mx e8a026ef0b updated sandbox peer 2023-05-16 10:02:39 +02:00
mx 86755aa6ba updated admonishments for uniformity 2023-05-16 10:00:01 +02:00
mx 77e0c6425e * updated validator go version + added link to precompiled binaries
* general version bump
2023-05-16 09:54:40 +02:00
Pierre Dommerc 0373e2b02a feat(wallet): select validator (#3375) 2023-05-15 10:29:47 +02:00
Jon Häggblad e8dd347186 Update Cargo.lock (#3410) 2023-05-14 22:00:17 +02:00
farbanas 4ebd1dd7f5 Merge branch 'master' into develop 2023-05-10 09:51:33 +01:00
Jon Häggblad 62ab760656 Add name-service endpoint to nym-api (#3394) 2023-05-10 10:42:49 +02:00
Nadim Kobeissi 32de7efc32 Fix #3371 2023-05-09 19:43:57 +02:00
farbanas deae210b82 update changelog in for release v1.1.18 2023-05-09 12:21:30 +01:00
farbanas 5b2b45a6eb updated versions for release v1.1.18 2023-05-09 12:20:07 +01:00
mx 896a3e1be6 temporarily removed compatibility table: will reintroduce once fixed 2023-05-09 11:40:07 +02:00
Jon Häggblad 800390db85 Fix warning about default-features being ignored (#3398) 2023-05-09 11:32:21 +02:00
mx 1eaa13155c Merge pull request #3392 from nymtech/feature/release-1-1-18-docs
version bump to 1.1.18
2023-05-09 09:17:02 +00:00
Tommy Verrall fad3346096 Merge pull request #3391 from nymtech/bugfix/wallet-signin-ui
Bug fix: resolve dead-lock when switching signin to main app window in the Nym Wallet
2023-05-09 09:19:07 +01:00
Tommy Verrall 150f832f8e Merge pull request #3388 from nymtech/feature/wallet_enforce_semver
feat(wallet-bonding): enforce semver for node version
2023-05-09 09:18:23 +01:00
Nadim Kobeissi 202336b8a1 Fix Typescript SDK compilation errors 2023-05-05 16:23:46 +02:00
Jon Häggblad f0e94f8e5e Add name-service support to validator-client (#3384)
* Add name-service support to validator-client

* Add default_memo

* contract address for wallet

* rustfmt

* lock file

* Tidy up nym-wallet-types network config

* Typo

* Remove some unused contract constants
2023-05-05 15:39:38 +02:00
Fouad 6cd00b8d10 estimated fees for sending tokens (#3389)
* estimated fees for sending tokens
2023-05-05 14:08:16 +01:00
mx 534187cc8f Merge pull request #3368 from nymtech/add-docs-template
Update issue templates
2023-05-05 12:21:17 +00:00
mx 25b4934f69 added ntv blog to community guides section 2023-05-05 14:12:20 +02:00
mx 5ef7e24893 removed additional whitespace 2023-05-05 13:56:35 +02:00
mx 87ef46bc05 version bump to 1.1.18 2023-05-05 13:49:52 +02:00
Mark Sinclair f7bc5be8e4 Bug fix: resolve dead-lock when switching signin to main app window in the Nym Wallet
- change operations to async
- open the new window first and then try to close the old window, to prevent the process from exiting
2023-05-05 12:12:08 +01:00
Nadim Kobeissi b309583886 Run wasm-opt manually (Apple Silicon issue)
wasm-opt has a known issue on Apple Silicon:
https://github.com/rustwasm/wasm-pack/issues/913

The workaround currently seems to be running wasm-opt locally instead of
defining it as part of the Rust package's build pipeline in Cargo.toml.

I hope this is okay!
2023-05-05 12:09:13 +02:00
pierre 245185710a strip off v in node version 2023-05-05 11:03:44 +02:00
Jon Häggblad b7cfe31d72 Initial version of nym-name-service contract (only) (#3380)
* Initial version of nym-name-service, based on nym-service-provider-directory

* rustfmt

* Rename to NameEntry

* Restrict address format

* Remove deprecated random test

* Fix clippy

* Add to top-level Makefile

* Restore wasm-opt Makefile rule

* Restore NymAddress as enum

* rustfmt

* Add contract address to qa-qwerty.env

* Rename NymAddress to Address

* Tweak event output

* rustfmt

* add event_tag()

* qwerty contract address
2023-05-04 15:57:41 +02:00
Pierre Dommerc 68ca41a6be refactor(wallet-bonding): fetch node data concurrently (#3362) 2023-05-04 10:10:50 +02:00
Pierre Dommerc 5621e7d22e refactor(wallet-bonding): fetch node data concurrently (#3362) 2023-05-04 10:02:29 +02:00
Fouad a1a5c7772d Use Loading Modal component when loading Delegations data (#3377)
* allow loading modal to display custom text

* use loading modal

* dont repeatedly reset delegation state

* show loading modal when loading + no other modal is open

* fix lint errors

* log any delegations errors

* fix typo

* refresh interval in delegations page
2023-05-03 17:41:24 +02:00
Tommy Verrall b47deafc14 Merge pull request #3381 from nymtech/feature/add-nyxd-builds-ci
Feature/add nyxd builds ci
2023-05-03 16:30:46 +01:00
benedettadavico cc6a6d8db2 tweaking file 2023-05-03 17:19:28 +02:00
benedettadavico 5b28e24c17 workflow to add nyxd to builds ci 2023-05-03 17:15:47 +02:00
farbanas f8d68d8ef0 fix: merge resolve 2023-05-02 15:34:15 +02:00
farbanas a9d86508b5 Merge branch 'master' into develop 2023-05-02 14:38:11 +02:00
Tommy Verrall bb7fa587de formatting 2023-05-02 13:19:17 +02:00
Tommy Verrall 6585732dfc fix broken network address - set to none 2023-05-02 13:14:20 +02:00
farbanas 2065e0fc17 Merge branch 'master' into develop 2023-05-02 12:09:52 +02:00
farbanas 3f7bdad59c update lock files 2023-05-02 10:54:54 +02:00
farbanas 6209e78c1e bump crates 2023-05-02 10:53:42 +02:00
farbanas 8e5062af96 bump versions and update changelogs for release v1.1.17 2023-05-02 10:39:24 +02:00
mx 496e642d7f Merge pull request #3370 from nymtech/feature/1-1-17-docs
Feature/1 1 17 docs
2023-05-02 08:10:32 +00:00
mx 07e18ec198 added tokio dependency note 2023-04-28 15:51:20 +02:00
mx d5953c28c1 added note on running example code 2023-04-28 15:45:38 +02:00
mx 3aa4b66588 added info re buying NYM from wallet with BTC 2023-04-28 15:43:57 +02:00
mx 005f0ce340 * added correct version variable to sign command output
* added info that you can buy NYM from wallet with BTC
2023-04-28 15:41:55 +02:00
mx 8d1d025fa2 bumped point version 2023-04-28 15:41:44 +02:00
mx 1d53a2f954 updated readme with more details re: each directory having a readme and running them 2023-04-28 15:41:18 +02:00
mx 966d123608 Update issue templates 2023-04-27 16:15:10 +02:00
Tommy Verrall 963d55273f Merge pull request #3367 from nymtech/feature/adding-sp-api-tests
adding a test for SP endpoint
2023-04-27 14:47:26 +01:00
benedettadavico 6872d7bf77 adding a test for SP endpoint 2023-04-27 15:37:59 +02:00
Jon Häggblad 6fe93bcda0 Merge pull request #3332 from nymtech/jon/feat/sp-integrations
Service provider directory support in nym-api, nym-cli, validator-client
2023-04-27 11:51:00 +02:00
Jędrzej Stuczyński 78d568e04e Feature/store cipher (#3350)
* initial nym-store-cipher

* cleanup
2023-04-27 10:24:36 +01:00
Pierre Dommerc 796f5a678a feat(wallet): update bond amount (#3338) 2023-04-26 15:35:47 +02:00
Jon Häggblad 0dfd1cca44 Review comments 2023-04-26 10:58:12 +02:00
Jon Häggblad eacefd3697 nym-cli: add announce and delete service provider 2023-04-26 10:58:12 +02:00
Jon Häggblad 424c25768c Add schemars to lock file 2023-04-26 10:58:12 +02:00
Jon Häggblad e460c1700e Add support for listing services in nym-cli 2023-04-26 10:58:12 +02:00
Jon Häggblad 9935c99d41 nym-api: add service provider endpoint and caching 2023-04-26 10:58:12 +02:00
Jon Häggblad 2c4aee63bf Make the contract optional 2023-04-26 10:58:12 +02:00
Jon Häggblad 0ebc395df9 rustfmt 2023-04-26 10:58:12 +02:00
Jon Häggblad 1de3317e75 Add sp directory contract traits and methods to nyxd client 2023-04-26 10:58:12 +02:00
Tommy Verrall 91c20af893 Merge pull request #3328 from nymtech/feature/shared-network-monitor
Feature/shared network monitor
2023-04-26 09:49:54 +01:00
pierre 1365e2f246 chore(wallet): add v prefix in wallet version 2023-04-26 10:04:58 +02:00
Mark Sinclair 3f4f76859b Merge pull request #3188 from nymtech/feature/wallet-login
Split wallet sign in and main into two entry points
2023-04-25 11:07:00 +01:00
Jędrzej Stuczyński 221e1870e5 removed redundant trait 2023-04-25 09:53:32 +01:00
Jędrzej Stuczyński 9b36bccf0c wasm tester fixes 2023-04-25 09:53:32 +01:00
Jędrzej Stuczyński 80d7285497 further improvements + reduced log noise 2023-04-25 09:53:32 +01:00
Jędrzej Stuczyński b94f2a483d nym-api compiling again 2023-04-25 09:53:32 +01:00
Jędrzej Stuczyński f64cfb4cd1 wip 2023-04-25 09:53:32 +01:00
Mark Sinclair 30e2f27c64 Fix linting error 2023-04-24 15:51:50 +01:00
Mark Sinclair 3113c1e9a7 Fix build issues 2023-04-24 15:41:13 +01:00
Mark Sinclair 1d8a931e0c Do not keep mnemonic or password (and variations) in logs 2023-04-24 12:24:19 +01:00
Mark Sinclair 48d0883b31 Clear stashed state completely on logout 2023-04-24 12:24:19 +01:00
Mark Sinclair e271370326 Split wallet sign in and main into two entry points
Stash some of the state in the Rust process and load it when the React app mounts
Fix connect_with_mnemonic logging
2023-04-24 12:24:19 +01:00
193 changed files with 11588 additions and 5785 deletions
+14
View File
@@ -0,0 +1,14 @@
---
name: 'Documentation'
about: Suggest a fix or enhancement to the documentation or developer portal content
title: "[DOCS]"
labels: documentation
assignees: mfahampshire
---
Is your issue either:
- [ ] a fix to existing documentation (e.g. fixing a broken link or incorrect command)
- [ ] an enhancement (e.g. adding a description for an undocumented feature)
Please briefly describe your issue:
+79
View File
@@ -0,0 +1,79 @@
name: Upload nyxd to CI
on:
workflow_dispatch:
jobs:
publish-nyxd:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Prepare build output directory
shell: bash
env:
OUTPUT_DIR: ci-builds/nyxd
run: |
rm -rf ci-builds || true
mkdir -p $OUTPUT_DIR
echo $OUTPUT_DIR
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools git
continue-on-error: true
- name: Update env variables to include go
run: |
sudo rm -rf /usr/local/go
curl https://dl.google.com/go/go1.19.2.linux-amd64.tar.gz | sudo tar -C/usr/local -zxvf -
cat <<'EOF' >>$HOME/.profile
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export GO111MODULE=on
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
EOF
source $HOME/.profile
- name: Verify Go is installed
run: go version
- name: Clone nyxd repo
run: |
git clone https://github.com/tommyv1987/nyxd
cd nyxd
git checkout release/v0.30.2
- name: Run nyxd
run: |
pwd
cd nyxd && make build
sleep 10
ls /home/runner/work/nym/nym/nyxd/build
- name: Prepare build output
shell: bash
env:
OUTPUT_DIR: ci-builds/nyxd
run: |
cp /home/runner/work/nym/nym/nyxd/build/nyxd $OUTPUT_DIR
WASMVM_SO=$(ldd /home/runner/work/nym/nym/nyxd/build/nyxd | grep "libwasm*" | awk '{ print $3 }')
ls $WASMVM_SO
sleep 3
cp $(echo $WASMVM_SO) $OUTPUT_DIR
- name: Deploy nyxd to CI www
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-avzr"
SOURCE: "ci-builds/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
EXCLUDE: "/dist/, /node_modules/"
+40
View File
@@ -4,6 +4,46 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [v1.1.19] (2023-05-16)
- nym-name-service endpoint in nym-api ([#3403])
- Implement key storage for WASM client using IndexedDB (for browser) ([#3329])
- Initial version of nym-name-service contract providing name aliases for nym-addresses ([#3274])
- Update Cargo.lock ([#3410])
[#3403]: https://github.com/nymtech/nym/issues/3403
[#3329]: https://github.com/nymtech/nym/issues/3329
[#3274]: https://github.com/nymtech/nym/issues/3274
[#3410]: https://github.com/nymtech/nym/pull/3410
## [v1.1.18] (2023-05-09)
- Implement heartbeat messages between socks5 proxy and network requester ([#3215])
[#3215]: https://github.com/nymtech/nym/issues/3215
## [v1.1.17] (2023-05-02)
- Add service-provider-directory-contract support to nym-cli ([#3334])
- Start using the node-testing-utils (implemented in #3270) in nym-api Network monitor to simplify the logic there ([#3312])
- Add service-provider-directory support to validator-client ([#3296])
- Allow topology injection in our WASM client ('test my node' feature) ([#3270])
- Expose service-provider-directory contract data in nym-api endpoints ([#3242])
- Cache service provider contract in nym-api ([#3241])
- Feature/1 1 17 docs ([#3370])
- adding a test for SP endpoint ([#3367])
- Feature/store cipher ([#3350])
[#3334]: https://github.com/nymtech/nym/issues/3334
[#3312]: https://github.com/nymtech/nym/issues/3312
[#3296]: https://github.com/nymtech/nym/issues/3296
[#3270]: https://github.com/nymtech/nym/issues/3270
[#3242]: https://github.com/nymtech/nym/issues/3242
[#3241]: https://github.com/nymtech/nym/issues/3241
[#3370]: https://github.com/nymtech/nym/pull/3370
[#3367]: https://github.com/nymtech/nym/pull/3367
[#3350]: https://github.com/nymtech/nym/pull/3350
## [v1.1.16] (2023-04-25)
- Explorer - Fix sorting function on Stake Saturation. It is currently working per page and not globally ([#3320])
Generated
+696 -575
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -37,6 +37,7 @@ members = [
"common/cosmwasm-smart-contracts/group-contract",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract",
"common/cosmwasm-smart-contracts/name-service",
"common/cosmwasm-smart-contracts/service-provider-directory",
"common/cosmwasm-smart-contracts/vesting-contract",
"common/credential-storage",
@@ -60,12 +61,14 @@ members = [
"common/nymsphinx/forwarding",
"common/nymsphinx/framing",
"common/nymsphinx/params",
"common/nymsphinx/routing",
"common/nymsphinx/types",
"common/pemstore",
"common/socks5-client-core",
"common/socks5/proxy-helpers",
"common/socks5/requests",
"common/statistics",
"common/store-cipher",
"common/task",
"common/topology",
"common/types",
+2
View File
@@ -99,6 +99,7 @@ CONTRACTS_OUT_DIR=contracts/target/wasm32-unknown-unknown/release
VESTING_CONTRACT=$(CONTRACTS_OUT_DIR)/vesting_contract.wasm
MIXNET_CONTRACT=$(CONTRACTS_OUT_DIR)/mixnet_contract.wasm
SERVICE_PROVIDER_DIRECTORY_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_service_provider_directory.wasm
NAME_SERVICE_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_name_service.wasm
wasm: wasm-build wasm-opt
@@ -109,6 +110,7 @@ wasm-opt:
wasm-opt --disable-sign-ext -Os $(VESTING_CONTRACT) -o $(VESTING_CONTRACT)
wasm-opt --disable-sign-ext -Os $(MIXNET_CONTRACT) -o $(MIXNET_CONTRACT)
wasm-opt --disable-sign-ext -Os $(SERVICE_PROVIDER_DIRECTORY_CONTRACT) -o $(SERVICE_PROVIDER_DIRECTORY_CONTRACT)
wasm-opt --disable-sign-ext -Os $(NAME_SERVICE_CONTRACT) -o $(NAME_SERVICE_CONTRACT)
# -----------------------------------------------------------------------------
# Misc
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.16"
version = "1.1.19"
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.16"
version = "1.1.19"
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"
+23 -12
View File
@@ -34,7 +34,14 @@ import {
StakeSaturationResponse,
UnbondedMixnodeResponse,
VestingAccountInfo,
ContractState, VestingAccountsCoinPaged, VestingAccountsPaged, DelegationTimes, Delegations, Period, VestingAccountNode, DelegationBlock
ContractState,
VestingAccountsCoinPaged,
VestingAccountsPaged,
DelegationTimes,
Delegations,
Period,
VestingAccountNode,
DelegationBlock,
} from '@nymproject/types';
import QueryClient from './query-client';
import SigningClient, { ISigningClient } from './signing-client';
@@ -207,7 +214,7 @@ export default class ValidatorClient implements INymClient {
let mixNodes: UnbondedMixnodeResponse[] = [];
const limit = 50;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedUnbondedMixnodesResponse = await this.client.getUnbondedMixNodes(
this.mixnetContract,
@@ -230,7 +237,7 @@ export default class ValidatorClient implements INymClient {
let mixNodes: MixNodeBond[] = [];
const limit = 50;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixNodeBondResponse = await this.client.getMixNodeBonds(
this.mixnetContract,
@@ -252,7 +259,7 @@ export default class ValidatorClient implements INymClient {
let mixNodes: MixNodeDetails[] = [];
const limit = 50;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixNodeDetailsResponse = await this.client.getMixNodesDetailed(
this.mixnetContract,
@@ -284,7 +291,7 @@ export default class ValidatorClient implements INymClient {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixDelegationsResponse = await this.client.getMixNodeDelegationsPaged(
this.mixnetContract,
@@ -307,7 +314,7 @@ export default class ValidatorClient implements INymClient {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedDelegatorDelegationsResponse = await this.client.getDelegatorDelegationsPaged(
this.mixnetContract,
@@ -330,7 +337,7 @@ export default class ValidatorClient implements INymClient {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllDelegationsPaged(
this.mixnetContract,
@@ -518,11 +525,9 @@ export default class ValidatorClient implements INymClient {
return (this.client as ISigningClient).updateContractStateParams(this.mixnetContract, newParams, fee, memo);
}
// VESTING
// VESTING
// TODO - MOVE TO A DIFFERENT FILE
public async getVestingAccountsPaged(): Promise<VestingAccountsPaged> {
return this.client.getVestingAccountsPaged(this.vestingContract);
}
@@ -608,9 +613,9 @@ export default class ValidatorClient implements INymClient {
}
public async getDelegation(address: string, mix_id: number): Promise<DelegationBlock> {
return this.client.getDelegation(this.vestingContract, address, mix_id );
return this.client.getDelegation(this.vestingContract, address, mix_id);
}
public async getTotalDelegationAmount(address: string, mix_id: number, block_timestamp_sec: number): Promise<Coin> {
return this.client.getTotalDelegationAmount(this.vestingContract, address, mix_id, block_timestamp_sec);
}
@@ -618,4 +623,10 @@ export default class ValidatorClient implements INymClient {
public async getCurrentVestingPeriod(address: string): Promise<Period> {
return this.client.getCurrentVestingPeriod(this.vestingContract, address);
}
// SIMULATE
public async simulateSend(signingAddress: string, from: string, to: string, amount: Coin[]) {
return (this.client as SigningClient).simulateSend(signingAddress, from, to, amount);
}
}
+48 -9
View File
@@ -40,9 +40,18 @@ import {
RewardingParams,
UnbondedMixnodeResponse,
VestingAccountInfo,
ContractState, VestingAccountsCoinPaged, VestingAccountsPaged, DelegationTimes, Delegations, Period, VestingAccountNode, DelegationBlock
ContractState,
VestingAccountsCoinPaged,
VestingAccountsPaged,
DelegationTimes,
Delegations,
Period,
VestingAccountNode,
DelegationBlock,
} from '@nymproject/types';
import NymApiQuerier from './nym-api-querier';
import { makeBankMsgSend } from './utils';
import { ISimulateClient } from './types/simulate';
// methods exposed by `SigningCosmWasmClient`
export interface ICosmWasmSigning {
@@ -148,7 +157,7 @@ export interface INymSigning {
clientAddress: string;
}
export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSigning {
export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSigning, ISimulateClient {
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
@@ -511,11 +520,11 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
);
}
// vesting related
// vesting related
getVestingAccountsPaged(vestingContractAddress: string): Promise<VestingAccountsPaged> {
return this.nyxdQuerier.getVestingAccountsPaged(vestingContractAddress);
};
}
getVestingAmountsAccountsPaged(vestingContractAddress: string): Promise<VestingAccountsCoinPaged> {
return this.nyxdQuerier.getVestingAmountsAccountsPaged(vestingContractAddress);
@@ -569,7 +578,10 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nyxdQuerier.getEndTime(vestingContractAddress, vestingAccountAddress);
}
getOriginalVestingDetails(vestingContractAddress: string, vestingAccountAddress: string): Promise<OriginalVestingResponse> {
getOriginalVestingDetails(
vestingContractAddress: string,
vestingAccountAddress: string,
): Promise<OriginalVestingResponse> {
return this.nyxdQuerier.getOriginalVestingDetails(vestingContractAddress, vestingAccountAddress);
}
@@ -589,7 +601,11 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nyxdQuerier.getGateway(vestingContractAddress, address);
}
getDelegationTimes(vestingContractAddress: string, mix_id: number, delegatorAddress: string): Promise<DelegationTimes> {
getDelegationTimes(
vestingContractAddress: string,
mix_id: number,
delegatorAddress: string,
): Promise<DelegationTimes> {
return this.nyxdQuerier.getDelegationTimes(vestingContractAddress, mix_id, delegatorAddress);
}
@@ -597,15 +613,38 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nyxdQuerier.getAllDelegations(vestingContractAddress);
}
getDelegation(vestingContractAddress: string, vestingAccountAddress: string, mix_id: number): Promise<DelegationBlock> {
getDelegation(
vestingContractAddress: string,
vestingAccountAddress: string,
mix_id: number,
): Promise<DelegationBlock> {
return this.nyxdQuerier.getDelegation(vestingContractAddress, vestingAccountAddress, mix_id);
}
getTotalDelegationAmount(vestingContractAddress: string, vestingAccountAddress: string, mix_id: number, block_timestamp_sec: number): Promise<Coin> {
return this.nyxdQuerier.getTotalDelegationAmount(vestingContractAddress, vestingAccountAddress, mix_id, block_timestamp_sec);
getTotalDelegationAmount(
vestingContractAddress: string,
vestingAccountAddress: string,
mix_id: number,
block_timestamp_sec: number,
): Promise<Coin> {
return this.nyxdQuerier.getTotalDelegationAmount(
vestingContractAddress,
vestingAccountAddress,
mix_id,
block_timestamp_sec,
);
}
getCurrentVestingPeriod(vestingContractAddress: string, address: string): Promise<Period> {
return this.nyxdQuerier.getCurrentVestingPeriod(vestingContractAddress, address);
}
// simulation
// TODO consider adding multipling factor
simulateSend(signingAddress: string, from: string, to: string, amount: Coin[]) {
const sendMsg = makeBankMsgSend(from, to, amount);
return this.simulate(signingAddress, [sendMsg], 'simulate send tx');
}
}
@@ -0,0 +1,31 @@
import expect from 'expect';
import ValidatorClient from '../..';
const dotenv = require('dotenv');
dotenv.config();
// TODO: implement for QA with .env for mnemonics
describe('Simualtions', () => {
let client: ValidatorClient;
beforeEach(async () => {
client = await ValidatorClient.connect(
process.env.mnemonic || '',
process.env.rpcAddress || '',
process.env.validatorAddress || '',
process.env.prefix || '',
process.env.mixnetContractAddress || '',
process.env.vestingContractAddress || '',
process.env.denom || '',
);
});
it('can simulate sending tokens', async () => {
const res = await client.simulateSend(client.address, client.address, client.address, [
{ amount: '400000', denom: 'unym' },
]);
expect(typeof res).toBe('number');
}).timeout(10000);
});
+5
View File
@@ -0,0 +1,5 @@
import { Coin } from '@cosmjs/proto-signing';
export interface ISimulateClient {
simulateSend(signingAddress: string, from: string, to: string, amount: Coin[]): Promise<number>;
}
+1 -1
View File
@@ -64,7 +64,7 @@ wee_alloc = { version = "0.4", optional = true }
wasm-bindgen-test = "0.3"
[package.metadata.wasm-pack.profile.release]
wasm-opt = true
wasm-opt = false
[profile.release]
lto = true
+4 -15
View File
@@ -13,7 +13,7 @@ use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_log, js_error, simple_js_error};
use wasm_utils::{console_log, simple_js_error};
#[wasm_bindgen]
pub struct NymClientTestRequest {
@@ -142,20 +142,9 @@ impl WasmTopologyExt for Arc<ClientState> {
return Err(WasmClientError::NonExistentMixnode { mixnode_identity }.into());
};
let mut test_msgs = Vec::with_capacity(num_test_packets as usize);
for i in 1..=num_test_packets {
let msg = NodeTestMessage::new_mix(
mix,
i,
num_test_packets,
WasmTestMessageExt::new(test_id),
);
let serialized = match msg.as_bytes() {
Ok(bytes) => bytes,
Err(err) => return Err(js_error!("failed to serialize test message: {err}")),
};
test_msgs.push(serialized);
}
let ext = WasmTestMessageExt::new(test_id);
let test_msgs = NodeTestMessage::mix_plaintexts(mix, num_test_packets, ext)
.map_err(WasmClientError::from)?;
let mut updated = current_topology.clone();
updated.set_mixes_in_layer(mix.layer.into(), vec![mix.to_owned()]);
@@ -1,10 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::tester::helpers::NodeTestResult;
use crate::tester::NodeTestMessage;
use crate::tester::helpers::{NodeTestResult, WasmTestMessageExt};
use futures::StreamExt;
use nym_node_tester_utils::receiver::{Received, ReceivedReceiver};
use nym_node_tester_utils::processor::Received;
use nym_node_tester_utils::receiver::ReceivedReceiver;
use nym_sphinx::chunking::fragment::FragmentIdentifier;
use std::collections::HashSet;
use std::time::Duration;
@@ -21,7 +21,7 @@ pub(crate) struct EphemeralTestReceiver<'a> {
duplicate_acks: u32,
timeout_duration: Duration,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver>,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver<WasmTestMessageExt>>,
}
impl<'a> EphemeralTestReceiver<'a> {
@@ -38,7 +38,7 @@ impl<'a> EphemeralTestReceiver<'a> {
pub(crate) fn new(
sent_packets: u32,
expected_acks: HashSet<FragmentIdentifier>,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver>,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver<WasmTestMessageExt>>,
timeout: Duration,
) -> Self {
EphemeralTestReceiver {
@@ -53,23 +53,18 @@ impl<'a> EphemeralTestReceiver<'a> {
}
}
fn on_next_received_packet(&mut self, packet: Option<Received>) -> bool {
fn on_next_received_packet(&mut self, packet: Option<Received<WasmTestMessageExt>>) -> bool {
let Some(received_packet) = packet else {
// can't do anything more...
console_error!("packet receiver has stopped processing results!");
return true
};
match received_packet {
Received::Message(msg) => match NodeTestMessage::try_recover(msg) {
Ok(test_msg) => {
if !self.received_valid_messages.insert(test_msg.msg_id) {
self.duplicate_packets += 1;
}
Received::Message(msg) => {
if !self.received_valid_messages.insert(msg.msg_id) {
self.duplicate_packets += 1;
}
Err(err) => {
console_warn!("failed to recover test message from received packet: {err}")
}
},
}
Received::Ack(frag_id) => {
if self.expected_acks.contains(&frag_id) {
if !self.received_valid_acks.insert(frag_id) {
+5 -4
View File
@@ -4,7 +4,8 @@
// due to expansion of #[wasm_bindgen] macro on NodeTestResult
#![allow(clippy::drop_non_drop)]
use nym_node_tester_utils::receiver::{Received, ReceivedReceiver};
use nym_node_tester_utils::processor::Received;
use nym_node_tester_utils::receiver::ReceivedReceiver;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::sync::atomic::{AtomicBool, Ordering};
@@ -14,10 +15,10 @@ use wasm_bindgen::prelude::*;
use wasm_utils::{console_log, console_warn};
#[derive(Clone)]
pub(super) struct ReceivedReceiverWrapper(Arc<AsyncMutex<ReceivedReceiver>>);
pub(super) struct ReceivedReceiverWrapper(Arc<AsyncMutex<ReceivedReceiver<WasmTestMessageExt>>>);
impl ReceivedReceiverWrapper {
pub(super) fn new(inner: ReceivedReceiver) -> Self {
pub(super) fn new(inner: ReceivedReceiver<WasmTestMessageExt>) -> Self {
ReceivedReceiverWrapper(Arc::new(AsyncMutex::new(inner)))
}
@@ -36,7 +37,7 @@ impl ReceivedReceiverWrapper {
}
}
pub(super) async fn lock(&self) -> AsyncMutexGuard<'_, ReceivedReceiver> {
pub(super) async fn lock(&self) -> AsyncMutexGuard<'_, ReceivedReceiver<WasmTestMessageExt>> {
self.0.lock().await
}
}
+7 -2
View File
@@ -162,7 +162,7 @@ impl NymNodeTesterBuilder {
let tester = NodeTester::new(
rng,
self.base_topology,
address(&self.key_manager, gateway_identity),
Some(address(&self.key_manager, gateway_identity)),
PacketSize::default(),
Duration::from_millis(5),
Duration::from_millis(5),
@@ -266,7 +266,12 @@ impl NymNodeTester {
let test_ext = WasmTestMessageExt::new(test_nonce);
let mut tester_permit = self.tester.lock().expect("mutex got poisoned");
tester_permit
.existing_identity_mixnode_test_packets(mixnode_identity, test_ext, num_test_packets)
.existing_identity_mixnode_test_packets(
mixnode_identity,
test_ext,
num_test_packets,
None,
)
.map_err(Into::into)
}
@@ -17,7 +17,9 @@ nym-mixnet-contract-common = { path = "../../cosmwasm-smart-contracts/mixnet-con
nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-contract" }
nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
nym-name-service-common = { path = "../../cosmwasm-smart-contracts/name-service" }
nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
nym-service-provider-directory-common = { path = "../../cosmwasm-smart-contracts/service-provider-directory" }
nym-vesting-contract = { path = "../../../contracts/vesting" }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
@@ -63,6 +65,14 @@ name = "offline_signing"
# (traits would need to be moved around and refactored themselves)
required-features = ["nyxd-client"]
[[example]]
name = "query_service_provider_directory"
required-features = ["nyxd-client"]
[[example]]
name = "query_name_service"
required-features = ["nyxd-client"]
[features]
nyxd-client = [
"async-trait",
@@ -0,0 +1,35 @@
use std::str::FromStr;
use cosmrs::AccountId;
use nym_name_service_common::Address;
use nym_network_defaults::{setup_env, NymNetworkDetails};
use nym_validator_client::nyxd::traits::NameServiceQueryClient;
#[tokio::main]
async fn main() {
setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap()));
let network_details = NymNetworkDetails::new_from_env();
let config =
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
let client = nym_validator_client::Client::new_query(config).unwrap();
let config = client.nyxd.get_name_service_config().await.unwrap();
println!("config: {config:?}");
let names_paged = client.nyxd.get_names_paged(None, None).await.unwrap();
println!("names (paged): {names_paged:#?}");
let names = client.nyxd.get_all_names().await.unwrap();
println!("names: {names:#?}");
let owner = AccountId::from_str("n1hmf957kc7arcd39rl7xq8l0a4zyg7kxnv7su87").unwrap();
let names_by_owner = client.nyxd.get_names_by_owner(owner).await.unwrap();
println!("names (by owner): {names_by_owner:#?}");
let nym_address = Address::new("client_id.client_key@gateway_id");
let names_by_address = client.nyxd.get_names_by_address(nym_address).await.unwrap();
println!("names (by address): {names_by_address:#?}");
let service_info = client.nyxd.get_name_entry(1).await;
println!("service info: {service_info:#?}");
}
@@ -0,0 +1,43 @@
use std::str::FromStr;
use cosmrs::AccountId;
use nym_network_defaults::{setup_env, NymNetworkDetails};
use nym_service_provider_directory_common::NymAddress;
use nym_validator_client::nyxd::traits::SpDirectoryQueryClient;
#[tokio::main]
async fn main() {
setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap()));
let network_details = NymNetworkDetails::new_from_env();
let config =
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
let client = nym_validator_client::Client::new_query(config).unwrap();
let config = client.nyxd.get_service_config().await.unwrap();
println!("config: {config:?}");
let services_paged = client.nyxd.get_services_paged(None, None).await.unwrap();
println!("services (paged): {services_paged:#?}");
let services = client.nyxd.get_all_services().await.unwrap();
println!("services: {services:#?}");
let announcer = AccountId::from_str("n1hmf957kc7arcd39rl7xq8l0a4zyg7kxnv7su87").unwrap();
let services_by_announcer = client
.nyxd
.get_services_by_announcer(announcer)
.await
.unwrap();
println!("services (by announcer): {services_by_announcer:#?}");
let nym_address = NymAddress::new("foo.bar@gateway");
let services_by_nym_address = client
.nyxd
.get_services_by_nym_address(nym_address)
.await
.unwrap();
assert_eq!(services_by_announcer, services_by_nym_address);
let service_info = client.nyxd.get_service_info(1).await;
println!("service info: {service_info:#?}");
}
@@ -1,6 +1,6 @@
use crate::nyxd::error::NyxdError;
use crate::nyxd::{Config as ClientConfig, NyxdClient, QueryNyxdClient};
use crate::NymApiClient;
use crate::{NymApiClient, ValidatorClientError};
use crate::nyxd::traits::MixnetQueryClient;
use colored::Colorize;
@@ -45,6 +45,23 @@ pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
)
}
pub async fn test_nyxd_url_connection(
network: NymNetworkDetails,
nyxd_url: Url,
address: cosmrs::AccountId,
) -> Result<bool, ValidatorClientError> {
let config = ClientConfig::try_from_nym_network_details(&network)
.expect("failed to create valid nyxd client config");
let mut nyxd_client = NyxdClient::<QueryNyxdClient>::connect(config, nyxd_url.as_str())?;
// possibly redundant, but lets just leave it here
nyxd_client.set_mixnet_contract_address(address);
match test_nyxd_connection(network, &nyxd_url, &nyxd_client).await {
ConnectionResult::Nyxd(_, _, res) => Ok(res),
_ => Ok(false), // ✶ not possible to happens
}
}
fn setup_connection_tests<H: BuildHasher + 'static>(
nyxd_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
api_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
@@ -105,7 +122,7 @@ async fn test_nyxd_connection(
{
Ok(Err(NyxdError::TendermintError(e))) => {
// If we get a tendermint-rpc error, we classify the node as not contactable
log::debug!("Checking: nyxd url: {url}: {}: {}", "failed".red(), e);
log::warn!("Checking: nyxd url: {url}: {}: {}", "failed".red(), e);
false
}
Ok(Err(NyxdError::AbciError { code, log, .. })) => {
@@ -117,13 +134,13 @@ async fn test_nyxd_connection(
);
code == 18
}
Ok(Err(error @ NyxdError::NoContractAddressAvailable)) => {
log::debug!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
Ok(Err(error @ NyxdError::NoContractAddressAvailable(_))) => {
log::warn!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
false
}
Ok(Err(e)) => {
// For any other error, we're optimistic and just try anyway.
log::debug!(
log::warn!(
"Checking: nyxd_url: {url}: {}, but with error: {e}",
"success".green()
);
@@ -134,7 +151,7 @@ async fn test_nyxd_connection(
true
}
Err(e) => {
log::debug!("Checking: nyxd_url: {url}: {}: {e}", "failed".red());
log::warn!("Checking: nyxd_url: {url}: {}: {e}", "failed".red());
false
}
};
@@ -9,6 +9,9 @@ pub enum NymAPIError {
source: reqwest::Error,
},
#[error("Not found")]
NotFound,
#[error("Request failed with error message - {0}")]
GenericRequestFailure(String),
@@ -15,7 +15,8 @@ use nym_api_requests::models::{
};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use reqwest::Response;
use nym_service_provider_directory_common::ServiceInfo;
use reqwest::{Response, StatusCode};
use serde::{Deserialize, Serialize};
use url::Url;
@@ -76,6 +77,8 @@ impl Client {
let res = self.send_get_request(path, params).await?;
if res.status().is_success() {
Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND {
Err(NymAPIError::NotFound)
} else {
Err(NymAPIError::GenericRequestFailure(res.text().await?))
}
@@ -166,7 +169,7 @@ impl Client {
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.query_nym_api(
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
&[routes::API_VERSION, routes::MIXNODES],
NO_PARAMS,
)
.await
@@ -480,6 +483,11 @@ impl Client {
)
.await
}
pub async fn get_service_providers(&self) -> Result<Vec<ServiceInfo>, NymAPIError> {
self.query_nym_api(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
.await
}
}
// utility function that should solve the double slash problem in validator API forever.
@@ -32,3 +32,5 @@ pub const COMPUTE_REWARD_ESTIMATION: &str = "compute-reward-estimation";
pub const AVG_UPTIME: &str = "avg_uptime";
pub const STAKE_SATURATION: &str = "stake-saturation";
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
pub const SERVICE_PROVIDERS: &str = "service-providers";
@@ -21,8 +21,8 @@ use std::{io, time::Duration};
#[derive(Debug, Error)]
pub enum NyxdError {
#[error("No contract address is available to perform the call")]
NoContractAddressAvailable,
#[error("No contract address is available to perform the call: {0}")]
NoContractAddressAvailable(String),
#[error(transparent)]
WalletError(#[from] DirectSecp256k1HdWalletError),
@@ -162,7 +162,7 @@ fn try_parse_abci_log(log: &abci::Log) -> Option<String> {
.value()
.contains("Maximum amount of locked coins has already been pledged")
{
Some("Maximum amount of locked tokens has alredy been used. You can only use up to 10% of your locked tokens for bonding and delegating.".to_string())
Some("Maximum amount of locked tokens has already been used. You can only use up to 10% of your locked tokens for bonding and delegating.".to_string())
} else {
None
}
@@ -67,6 +67,8 @@ pub struct Config {
pub(crate) group_contract_address: Option<AccountId>,
pub(crate) multisig_contract_address: Option<AccountId>,
pub(crate) coconut_dkg_contract_address: Option<AccountId>,
pub(crate) service_provider_contract_address: Option<AccountId>,
pub(crate) name_service_contract_address: Option<AccountId>,
// TODO: add this in later commits
// pub(crate) gas_price: GasPrice,
}
@@ -131,6 +133,17 @@ impl Config {
details.contracts.coconut_dkg_contract_address.as_ref(),
prefix,
)?,
service_provider_contract_address: Self::parse_optional_account(
details
.contracts
.service_provider_directory_contract_address
.as_ref(),
prefix,
)?,
name_service_contract_address: Self::parse_optional_account(
details.contracts.name_service_contract_address.as_ref(),
prefix,
)?,
})
}
}
@@ -246,6 +259,10 @@ impl<C> NyxdClient<C> {
self.config.multisig_contract_address = Some(address);
}
pub fn set_service_provider_contract_address(&mut self, address: AccountId) {
self.config.service_provider_contract_address = Some(address);
}
// TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits
// note: what unwrap is doing here is just moving a failure that would have normally
// occurred in `connect` when attempting to parse an empty address,
@@ -304,6 +321,16 @@ impl<C> NyxdClient<C> {
self.config.coconut_dkg_contract_address.as_ref().unwrap()
}
// The service provider directory contract is optional, so we return an Option not a Result
pub fn service_provider_contract_address(&self) -> Option<&AccountId> {
self.config.service_provider_contract_address.as_ref()
}
// The name service contract is optional, so we return an Option not a Result
pub fn name_service_contract_address(&self) -> Option<&AccountId> {
self.config.name_service_contract_address.as_ref()
}
pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) {
self.simulated_gas_multiplier = multiplier;
}
@@ -16,15 +16,25 @@ mod mixnet_signing_client;
mod multisig_signing_client;
mod vesting_signing_client;
mod sp_directory_query_client;
mod sp_directory_signing_client;
mod name_service_query_client;
mod name_service_signing_client;
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use dkg_query_client::DkgQueryClient;
pub use group_query_client::GroupQueryClient;
pub use mixnet_query_client::MixnetQueryClient;
pub use multisig_query_client::MultisigQueryClient;
pub use name_service_query_client::NameServiceQueryClient;
pub use sp_directory_query_client::SpDirectoryQueryClient;
pub use vesting_query_client::VestingQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use dkg_signing_client::DkgSigningClient;
pub use mixnet_signing_client::MixnetSigningClient;
pub use multisig_signing_client::MultisigSigningClient;
pub use name_service_signing_client::NameServiceSigningClient;
pub use sp_directory_signing_client::SpDirectorySigningClient;
pub use vesting_signing_client::VestingSigningClient;
@@ -0,0 +1,109 @@
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::ContractBuildInformation;
use nym_name_service_common::{
msg::QueryMsg as NameQueryMsg,
response::{ConfigResponse, NamesListResponse, PagedNamesListResponse},
Address, NameEntry, NameId,
};
use serde::Deserialize;
use crate::nyxd::{error::NyxdError, CosmWasmClient, NyxdClient};
#[async_trait]
pub trait NameServiceQueryClient {
async fn query_name_service_contract<T>(&self, query: NameQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>;
async fn get_name_service_config(&self) -> Result<ConfigResponse, NyxdError> {
self.query_name_service_contract(NameQueryMsg::Config {})
.await
}
async fn get_name_entry(&self, name_id: NameId) -> Result<NameEntry, NyxdError> {
self.query_name_service_contract(NameQueryMsg::NameId { name_id })
.await
}
async fn get_names_paged(
&self,
start_after: Option<NameId>,
limit: Option<u32>,
) -> Result<PagedNamesListResponse, NyxdError> {
self.query_name_service_contract(NameQueryMsg::All { limit, start_after })
.await
}
async fn get_names_by_owner(&self, owner: AccountId) -> Result<NamesListResponse, NyxdError> {
self.query_name_service_contract(NameQueryMsg::ByOwner {
owner: owner.to_string(),
})
.await
}
async fn get_names_by_address(&self, address: Address) -> Result<NamesListResponse, NyxdError> {
self.query_name_service_contract(NameQueryMsg::ByAddress { address })
.await
}
async fn get_name_service_contract_version(
&self,
) -> Result<ContractBuildInformation, NyxdError> {
self.query_name_service_contract(NameQueryMsg::GetContractVersion {})
.await
}
async fn get_all_names(&self) -> Result<Vec<NameEntry>, NyxdError> {
let mut services = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self.get_names_paged(start_after.take(), None).await?;
let last_id = paged_response.names.last().map(|serv| serv.name_id);
services.append(&mut paged_response.names);
if let Some(start_after_res) = last_id {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(services)
}
}
#[async_trait]
impl<C> NameServiceQueryClient for NyxdClient<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn query_name_service_contract<T>(&self, query: NameQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
{
self.client
.query_contract_smart(
self.name_service_contract_address().ok_or(
NyxdError::NoContractAddressAvailable("name service contract".to_string()),
)?,
&query,
)
.await
}
}
#[async_trait]
impl<C> NameServiceQueryClient for crate::Client<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn query_name_service_contract<T>(&self, query: NameQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
{
self.nyxd.query_name_service_contract(query).await
}
}
@@ -0,0 +1,96 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_name_service_common::{msg::ExecuteMsg as NameExecuteMsg, Address, NameId, NymName};
use crate::nyxd::{
coin::Coin, cosmwasm_client::types::ExecuteResult, error::NyxdError, Fee, NyxdClient,
SigningCosmWasmClient,
};
#[async_trait]
pub trait NameServiceSigningClient {
async fn execute_name_service_contract(
&self,
fee: Option<Fee>,
msg: NameExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError>;
async fn register_name(
&self,
name: NymName,
address: Address,
deposit: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_name_service_contract(
fee,
NameExecuteMsg::Register { name, address },
vec![deposit],
)
.await
}
async fn delete_name_by_id(
&self,
name_id: NameId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_name_service_contract(fee, NameExecuteMsg::DeleteId { name_id }, vec![])
.await
}
async fn delete_service_provider_by_name(
&self,
name: NymName,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_name_service_contract(fee, NameExecuteMsg::DeleteName { name }, vec![])
.await
}
async fn update_deposit_required(
&self,
deposit_required: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_name_service_contract(
fee,
NameExecuteMsg::UpdateDepositRequired {
deposit_required: deposit_required.into(),
},
vec![],
)
.await
}
}
#[async_trait]
impl<C> NameServiceSigningClient for NyxdClient<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn execute_name_service_contract(
&self,
fee: Option<Fee>,
msg: NameExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.default_memo();
self.client
.execute(
self.address(),
self.name_service_contract_address().ok_or(
NyxdError::NoContractAddressAvailable("name service contract".to_string()),
)?,
&msg,
fee,
memo,
funds,
)
.await
}
}
@@ -0,0 +1,120 @@
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::ContractBuildInformation;
use nym_service_provider_directory_common::{
msg::QueryMsg as SpQueryMsg,
response::{
ConfigResponse, PagedServicesListResponse, ServiceInfoResponse, ServicesListResponse,
},
NymAddress, ServiceId, ServiceInfo,
};
use serde::Deserialize;
use crate::nyxd::{error::NyxdError, CosmWasmClient, NyxdClient};
#[async_trait]
pub trait SpDirectoryQueryClient {
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>;
async fn get_service_config(&self) -> Result<ConfigResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::Config {})
.await
}
async fn get_service_info(
&self,
service_id: ServiceId,
) -> Result<ServiceInfoResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::ServiceId { service_id })
.await
}
async fn get_services_paged(
&self,
start_after: Option<ServiceId>,
limit: Option<u32>,
) -> Result<PagedServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::All { limit, start_after })
.await
}
async fn get_services_by_announcer(
&self,
announcer: AccountId,
) -> Result<ServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::ByAnnouncer {
announcer: announcer.to_string(),
})
.await
}
async fn get_services_by_nym_address(
&self,
nym_address: NymAddress,
) -> Result<ServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::ByNymAddress { nym_address })
.await
}
async fn get_sp_contract_version(&self) -> Result<ContractBuildInformation, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::GetContractVersion {})
.await
}
async fn get_all_services(&self) -> Result<Vec<ServiceInfo>, NyxdError> {
let mut services = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self.get_services_paged(start_after.take(), None).await?;
let last_id = paged_response.services.last().map(|serv| serv.service_id);
services.append(&mut paged_response.services);
if let Some(start_after_res) = last_id {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(services)
}
}
#[async_trait]
impl<C> SpDirectoryQueryClient for NyxdClient<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
{
self.client
.query_contract_smart(
self.service_provider_contract_address().ok_or(
NyxdError::NoContractAddressAvailable(
"service provider directory contract".to_string(),
),
)?,
&query,
)
.await
}
}
#[async_trait]
impl<C> SpDirectoryQueryClient for crate::Client<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
{
self.nyxd.query_service_provider_contract(query).await
}
}
@@ -0,0 +1,111 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_service_provider_directory_common::{
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceId, ServiceType,
};
use crate::nyxd::{
coin::Coin, cosmwasm_client::types::ExecuteResult, error::NyxdError, Fee, NyxdClient,
SigningCosmWasmClient,
};
#[async_trait]
pub trait SpDirectorySigningClient {
async fn execute_service_provider_directory_contract(
&self,
fee: Option<Fee>,
msg: SpExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError>;
async fn announce_service_provider(
&self,
nym_address: NymAddress,
service_type: ServiceType,
deposit: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_service_provider_directory_contract(
fee,
SpExecuteMsg::Announce {
nym_address,
service_type,
},
vec![deposit],
)
.await
}
async fn delete_service_provider_by_id(
&self,
service_id: ServiceId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_service_provider_directory_contract(
fee,
SpExecuteMsg::DeleteId { service_id },
vec![],
)
.await
}
async fn delete_service_provider_by_nym_address(
&self,
nym_address: NymAddress,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_service_provider_directory_contract(
fee,
SpExecuteMsg::DeleteNymAddress { nym_address },
vec![],
)
.await
}
async fn update_deposit_required(
&self,
deposit_required: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_service_provider_directory_contract(
fee,
SpExecuteMsg::UpdateDepositRequired {
deposit_required: deposit_required.into(),
},
vec![],
)
.await
}
}
#[async_trait]
impl<C> SpDirectorySigningClient for NyxdClient<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn execute_service_provider_directory_contract(
&self,
fee: Option<Fee>,
msg: SpExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.default_memo();
self.client
.execute(
self.address(),
self.service_provider_contract_address().ok_or(
NyxdError::NoContractAddressAvailable(
"service provider directory contract".to_string(),
),
)?,
&msg,
fee,
memo,
funds,
)
.await
}
}
+1
View File
@@ -38,3 +38,4 @@ nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-cont
nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" }
nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" }
nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" }
@@ -5,6 +5,7 @@ use clap::{Args, Subcommand};
pub mod gateway;
pub mod mixnode;
pub mod service;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -19,4 +20,6 @@ pub enum MixnetOperatorsCommands {
Mixnode(mixnode::MixnetOperatorsMixnode),
/// Manage your gateway
Gateway(gateway::MixnetOperatorsGateway),
/// Manage your service
ServiceProvider(service::MixnetOperatorsService),
}
@@ -0,0 +1,33 @@
use clap::Parser;
use log::info;
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceType};
use nym_validator_client::nyxd::traits::SpDirectorySigningClient;
use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub nym_address: String,
/// Deposit to be made to the service provider directory, in curent DENOMINATION (e.g. 'unym')
#[clap(long)]
pub deposit: u128,
}
pub async fn announce(args: Args, client: SigningClient) {
info!("Annoucing service provider");
let nym_address = NymAddress::Address(args.nym_address);
let service_type = ServiceType::NetworkRequester;
let denom = client.current_chain_details().mix_denom.base.as_str();
let deposit = Coin::new(args.deposit, denom);
let res = client
.announce_service_provider(nym_address, service_type, deposit.into(), None)
.await
.expect("Failed to announce service provider");
info!("Announced service provider: {res:?}");
}
@@ -0,0 +1,23 @@
use clap::Parser;
use log::info;
use nym_service_provider_directory_common::ServiceId;
use nym_validator_client::nyxd::traits::SpDirectorySigningClient;
use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub id: ServiceId,
}
pub async fn delete(args: Args, client: SigningClient) {
info!("Deleting service provider with id {}", args.id);
let res = client
.delete_service_provider_by_id(args.id, None)
.await
.expect("Failed to delete service provider");
info!("Deleted: {res:?}");
}
@@ -0,0 +1,19 @@
use clap::{Args, Subcommand};
pub mod announce;
pub mod delete;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct MixnetOperatorsService {
#[clap(subcommand)]
pub command: MixnetOperatorsServiceCommands,
}
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsServiceCommands {
/// Announce service provider to the world
Announce(announce::Args),
/// Delete entry for service provider from the directory
Delete(delete::Args),
}
@@ -5,6 +5,7 @@ use clap::{Args, Subcommand};
pub mod query_all_gateways;
pub mod query_all_mixnodes;
pub mod query_all_service_providers;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -19,4 +20,6 @@ pub enum MixnetQueryCommands {
Mixnodes(query_all_mixnodes::Args),
/// Query gateways
Gateways(query_all_gateways::Args),
/// Query announced service-providers
ServiceProviders(query_all_service_providers::Args),
}
@@ -0,0 +1,55 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::nym_api::error::NymAPIError;
use crate::context::QueryClientWithNyxd;
use crate::utils::show_error;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(value_parser)]
#[clap(help = "Optionally, the service provider to display")]
pub nym_address: Option<String>,
}
pub async fn query(args: Args, client: &QueryClientWithNyxd) {
match client.nym_api.get_service_providers().await {
Ok(res) => {
if let Some(nym_address) = args.nym_address {
let service = res.iter().find(|service| {
service
.service
.nym_address
.to_string()
.eq_ignore_ascii_case(&nym_address)
});
println!(
"{}",
::serde_json::to_string_pretty(&service).expect("json formatting error")
);
} else {
let mut table = Table::new();
table.set_header(vec!["Service Id", "Announcer", "Nym Address"]);
for service in res {
table.add_row(vec![
service.service_id.to_string(),
service.service.announcer.to_string(),
service.service.service_type.to_string(),
service.service.nym_address.to_string(),
]);
}
println!("The service providers in the directory are:");
println!("{table}");
}
}
Err(NymAPIError::NotFound) => {
println!("nym-api reports no service provider endpoint available");
}
Err(e) => show_error(e),
}
}
@@ -0,0 +1,11 @@
[package]
name = "nym-name-service-common"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = { workspace = true }
schemars = "0.8"
serde = { workspace = true, features = ["derive"] }
@@ -0,0 +1,66 @@
use cosmwasm_std::{Coin, Event};
use crate::{NameId, RegisteredName};
pub enum NameEventType {
Register,
DeleteId,
DeleteName,
UpdateDepositRequired,
}
impl std::fmt::Display for NameEventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NameEventType::Register => write!(f, "register"),
NameEventType::DeleteId => write!(f, "delete_id"),
NameEventType::DeleteName => write!(f, "delete_name"),
NameEventType::UpdateDepositRequired => write!(f, "update_deposit_required"),
}
}
}
impl From<NameEventType> for String {
fn from(event_type: NameEventType) -> Self {
event_type.to_string()
}
}
pub const ACTION: &str = "action";
pub const NAME_ID: &str = "name_id";
pub const NAME: &str = "name";
pub const OWNER: &str = "owner";
pub const DEPOSIT_REQUIRED: &str = "deposit_required";
pub fn new_register_event(name_id: NameId, name: RegisteredName) -> Event {
Event::new(NameEventType::Register)
.add_attribute(ACTION, NameEventType::Register)
.add_attribute(NAME_ID, name_id.to_string())
.add_attribute(NAME, name.name.to_string())
.add_attribute(name.address.event_tag(), name.address.to_string())
.add_attribute(OWNER, name.owner.to_string())
}
pub fn new_delete_id_event(name_id: NameId, name: RegisteredName) -> Event {
Event::new(NameEventType::DeleteId)
.add_attribute(ACTION, NameEventType::DeleteId)
.add_attribute(NAME_ID, name_id.to_string())
.add_attribute(NAME, name.name.to_string())
.add_attribute(name.address.event_tag(), name.address.to_string())
}
pub fn new_delete_name_event(name_id: NameId, name: RegisteredName) -> Event {
Event::new(NameEventType::DeleteId)
.add_attribute(ACTION, NameEventType::DeleteName)
.add_attribute(NAME_ID, name_id.to_string())
.add_attribute(NAME, name.name.to_string())
.add_attribute(name.address.event_tag(), name.address.to_string())
}
pub fn new_update_deposit_required_event(deposit_required: Coin) -> Event {
Event::new(NameEventType::UpdateDepositRequired)
.add_attribute(ACTION, NameEventType::UpdateDepositRequired)
.add_attribute(DEPOSIT_REQUIRED, deposit_required.to_string())
}
@@ -0,0 +1,7 @@
pub mod events;
pub mod msg;
pub mod response;
pub mod types;
// Re-export all types at the top-level
pub use types::*;
@@ -0,0 +1,91 @@
use crate::{Address, NameId, NymName};
use cosmwasm_std::Coin;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct InstantiateMsg {
pub deposit_required: Coin,
}
impl InstantiateMsg {
pub fn new(deposit_required: Coin) -> Self {
Self { deposit_required }
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
/// Announcing a name pointing to a nym-address
Register { name: NymName, address: Address },
/// Delete a name entry by id
DeleteId { name_id: NameId },
/// Delete a name entry by name
DeleteName { name: NymName },
/// Change the deposit required for announcing a name
UpdateDepositRequired { deposit_required: Coin },
}
impl ExecuteMsg {
pub fn delete_id(name_id: NameId) -> Self {
ExecuteMsg::DeleteId { name_id }
}
pub fn default_memo(&self) -> String {
match self {
ExecuteMsg::Register { name, address } => {
format!("registering {address} as name: {name}")
}
ExecuteMsg::DeleteId { name_id } => {
format!("deleting name with id {name_id}")
}
ExecuteMsg::DeleteName { name } => {
format!("deleting name: {name}")
}
ExecuteMsg::UpdateDepositRequired { deposit_required } => {
format!("updating the deposit required to {deposit_required}")
}
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
/// Query the name by it's assigned id
NameId {
name_id: NameId,
},
// Query the names by the registrator
ByOwner {
owner: String,
},
ByName {
name: NymName,
},
ByAddress {
address: Address,
},
All {
limit: Option<u32>,
start_after: Option<NameId>,
},
Config {},
GetContractVersion {},
#[serde(rename = "get_cw2_contract_version")]
GetCW2ContractVersion {},
}
impl QueryMsg {
pub fn all() -> QueryMsg {
QueryMsg::All {
limit: None,
start_after: None,
}
}
}
@@ -0,0 +1,79 @@
use crate::{msg::ExecuteMsg, NameEntry, NameId, RegisteredName};
use cosmwasm_std::Coin;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// Like [`NameEntry`] but since it's a response type the name is an option depending on if
/// the name exists or not.
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct NameEntryResponse {
pub name_id: NameId,
pub name: Option<RegisteredName>,
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct NamesListResponse {
pub names: Vec<NameEntry>,
}
impl NamesListResponse {
pub fn new(names: Vec<(NameId, RegisteredName)>) -> NamesListResponse {
NamesListResponse {
names: names
.into_iter()
.map(|(name_id, name)| NameEntry::new(name_id, name))
.collect(),
}
}
}
impl From<&[NameEntry]> for NamesListResponse {
fn from(names: &[NameEntry]) -> Self {
NamesListResponse {
names: names.to_vec(),
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct PagedNamesListResponse {
pub names: Vec<NameEntry>,
pub per_page: usize,
pub start_next_after: Option<NameId>,
}
impl PagedNamesListResponse {
pub fn new(
names: Vec<(NameId, RegisteredName)>,
per_page: usize,
start_next_after: Option<NameId>,
) -> PagedNamesListResponse {
let names = names
.into_iter()
.map(|(name_id, name)| NameEntry::new(name_id, name))
.collect();
PagedNamesListResponse {
names,
per_page,
start_next_after,
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct ConfigResponse {
pub deposit_required: Coin,
}
impl From<RegisteredName> for ExecuteMsg {
fn from(name: RegisteredName) -> Self {
ExecuteMsg::Register {
name: name.name,
address: name.address,
}
}
}
@@ -0,0 +1,165 @@
use std::fmt::{Display, Formatter};
use cosmwasm_std::{Addr, Coin};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// The directory of services are indexed by [`ServiceId`].
pub type NameId = u32;
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)]
pub struct RegisteredName {
/// The name pointing to the nym address
pub name: NymName,
/// The address of the service.
pub address: Address,
/// Service owner.
pub owner: Addr,
/// Block height at which the service was added.
pub block_height: u64,
/// The deposit used to announce the service.
pub deposit: Coin,
}
/// String representation of a nym address, which is of the form
/// client_id.client_enc@gateway_id.
/// NOTE: entirely unvalidated.
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Address {
NymAddress(String),
// Possible extension:
//Gateway(String)
}
impl Address {
/// Create a new nym address.
pub fn new(address: &str) -> Self {
Self::NymAddress(address.to_string())
}
pub fn as_str(&self) -> &str {
match self {
Address::NymAddress(address) => address,
}
}
pub fn event_tag(&self) -> &str {
match self {
Address::NymAddress(_) => "nym_address",
//Address::Gateway(_) => "gatway_address",
}
}
}
impl Display for Address {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
/// Name stored and pointing a to a nym-address
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct NymName(String);
#[derive(Debug)]
pub enum NymNameError {
InvalidName,
}
fn is_valid_name_char(c: char) -> bool {
// Normal lowercase letters
(c.is_alphabetic() && c.is_lowercase())
// or numbers
|| c.is_numeric()
// special case hyphen or underscore
|| c == '-' || c == '_'
}
impl NymName {
pub fn new(name: &str) -> Result<NymName, NymNameError> {
// We are a bit restrictive in which names we allow, to start out with. Consider relaxing
// this in the future.
if !name.chars().all(is_valid_name_char) {
return Err(NymNameError::InvalidName);
}
Ok(Self(name.to_string()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Display for NymName {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
/// [`RegisterdName`] together with the assigned [`NameId`].
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct NameEntry {
pub name_id: NameId,
pub name: RegisteredName,
}
impl NameEntry {
pub fn new(name_id: NameId, name: RegisteredName) -> Self {
Self { name_id, name }
}
}
#[cfg(test)]
mod tests {
use super::NymName;
#[test]
fn parse_nym_name() {
// Test some valid cases
assert!(NymName::new("foo").is_ok());
assert!(NymName::new("foo-bar").is_ok());
assert!(NymName::new("foo-bar-123").is_ok());
assert!(NymName::new("foo_bar").is_ok());
assert!(NymName::new("foo_bar_123").is_ok());
// And now test all some invalid ones
assert!(NymName::new("Foo").is_err());
assert!(NymName::new("foo bar").is_err());
assert!(NymName::new("foo!bar").is_err());
assert!(NymName::new("foo#bar").is_err());
assert!(NymName::new("foo$bar").is_err());
assert!(NymName::new("foo%bar").is_err());
assert!(NymName::new("foo&bar").is_err());
assert!(NymName::new("foo'bar").is_err());
assert!(NymName::new("foo(bar").is_err());
assert!(NymName::new("foo)bar").is_err());
assert!(NymName::new("foo*bar").is_err());
assert!(NymName::new("foo+bar").is_err());
assert!(NymName::new("foo,bar").is_err());
assert!(NymName::new("foo.bar").is_err());
assert!(NymName::new("foo.bar").is_err());
assert!(NymName::new("foo/bar").is_err());
assert!(NymName::new("foo/bar").is_err());
assert!(NymName::new("foo:bar").is_err());
assert!(NymName::new("foo;bar").is_err());
assert!(NymName::new("foo<bar").is_err());
assert!(NymName::new("foo=bar").is_err());
assert!(NymName::new("foo>bar").is_err());
assert!(NymName::new("foo?bar").is_err());
assert!(NymName::new("foo@bar").is_err());
assert!(NymName::new("fooBar").is_err());
assert!(NymName::new("foo[bar").is_err());
assert!(NymName::new("foo\"bar").is_err());
assert!(NymName::new("foo\\bar").is_err());
assert!(NymName::new("foo]bar").is_err());
assert!(NymName::new("foo^bar").is_err());
assert!(NymName::new("foo`bar").is_err());
assert!(NymName::new("foo{bar").is_err());
assert!(NymName::new("foo|bar").is_err());
assert!(NymName::new("foo}bar").is_err());
assert!(NymName::new("foo~bar").is_err());
}
}
@@ -7,4 +7,5 @@ edition = "2021"
[dependencies]
cosmwasm-std = { workspace = true }
serde = { workspace = true, default-features = false, features = ["derive"] }
schemars = "0.8"
serde = { workspace = true, features = ["derive"] }
@@ -5,3 +5,5 @@ pub mod types;
// Re-export all types at the top-level
pub use types::*;
pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction};
@@ -40,6 +40,24 @@ impl ExecuteMsg {
pub fn delete_id(service_id: ServiceId) -> Self {
ExecuteMsg::DeleteId { service_id }
}
pub fn default_memo(&self) -> String {
match self {
ExecuteMsg::Announce {
nym_address,
service_type,
} => format!("announcing {nym_address} as type {service_type}"),
ExecuteMsg::DeleteId { service_id } => {
format!("deleting service with service id {service_id}")
}
ExecuteMsg::DeleteNymAddress { nym_address } => {
format!("deleting service with nym address {nym_address}")
}
ExecuteMsg::UpdateDepositRequired { deposit_required } => {
format!("updating the deposit required to {deposit_required}")
}
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
@@ -1,5 +1,6 @@
use crate::{msg::ExecuteMsg, Service, ServiceId, ServiceInfo};
use cosmwasm_std::Coin;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
@@ -9,7 +10,7 @@ pub struct ServiceInfoResponse {
pub service: Option<Service>,
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct ServicesListResponse {
pub services: Vec<ServiceInfo>,
@@ -26,6 +27,14 @@ impl ServicesListResponse {
}
}
impl From<&[ServiceInfo]> for ServicesListResponse {
fn from(services: &[ServiceInfo]) -> Self {
Self {
services: services.to_vec(),
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct PagedServicesListResponse {
@@ -1,12 +1,13 @@
use std::fmt::{Display, Formatter};
use cosmwasm_std::{Addr, Coin};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// The directory of services are indexed by [`ServiceId`].
pub type ServiceId = u32;
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)]
pub struct Service {
/// The address of the service.
pub nym_address: NymAddress,
@@ -21,7 +22,7 @@ pub struct Service {
}
/// The types of addresses supported.
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum NymAddress {
/// String representation of a nym address, which is of the form
@@ -51,7 +52,7 @@ impl Display for NymAddress {
}
/// The type of services provider supported
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug)]
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ServiceType {
NetworkRequester,
@@ -66,7 +67,7 @@ impl std::fmt::Display for ServiceType {
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct ServiceInfo {
pub service_id: ServiceId,
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-crypto"
version = "0.2.0"
version = "0.3.0"
description = "Crypto library for the nym mixnet"
edition = { workspace = true }
authors = { workspace = true }
@@ -24,6 +24,7 @@ serde_bytes = { version = "0.11.6", optional = true }
serde_crate = { version = "1.0", optional = true, default_features = false, package = "serde" }
subtle-encoding = { version = "0.5", features = ["bech32-preview"]}
thiserror = "1.0.37"
zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] }
# internal
nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0" }
@@ -0,0 +1,38 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use nym_sphinx_types::{
SharedSecret, RoutingKeys
};
#[derive(Clone, Debug)]
pub struct KeyStorage(Arc<Mutex<HashMap<SharedSecret, (RoutingKeys, Option<SharedSecret>)>>>);
impl KeyStorage {
pub fn new() -> Self {
KeyStorage(Arc::new(Mutex::new(
HashMap::new()
)))
}
pub fn lookup(&self, key : SharedSecret) -> Option<(RoutingKeys, Option<SharedSecret>)> {
match self.0.lock() {
Ok(map) => map.get(&key).cloned(),
Err(_) => None,
}
}
pub fn store(&self, key : SharedSecret, routing_key : RoutingKeys, blinded_shared_secret : Option<SharedSecret>) {
match self.0.lock() {
Ok(mut map) => map.insert(key, (routing_key, blinded_shared_secret)),
Err(_) => return,
};
}
}
@@ -3,3 +3,4 @@
pub mod error;
pub mod processor;
mod key_storage;
@@ -3,6 +3,7 @@
use crate::measure;
use crate::packet_processor::error::MixProcessingError;
use crate::packet_processor::key_storage::KeyStorage;
use log::*;
use nym_sphinx_acknowledgements::surb_ack::SurbAck;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
@@ -11,7 +12,7 @@ use nym_sphinx_framing::packet::FramedSphinxPacket;
use nym_sphinx_params::{PacketMode, PacketSize};
use nym_sphinx_types::{
Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, Payload, PrivateKey,
ProcessedPacket, SphinxPacket,
ProcessedPacket, RoutingKeys, SharedSecret, SphinxPacket,
};
use std::convert::TryFrom;
use std::sync::Arc;
@@ -38,6 +39,7 @@ pub enum MixProcessingResult {
pub struct SphinxPacketProcessor {
/// Private sphinx key of this node required to unwrap received sphinx packet.
sphinx_key: Arc<PrivateKey>,
key_storage: KeyStorage,
}
impl SphinxPacketProcessor {
@@ -45,6 +47,7 @@ impl SphinxPacketProcessor {
pub fn new(sphinx_key: PrivateKey) -> Self {
SphinxPacketProcessor {
sphinx_key: Arc::new(sphinx_key),
key_storage: KeyStorage::new(),
}
}
@@ -65,6 +68,27 @@ impl SphinxPacketProcessor {
})
}
/// Performs a sphinx unwrapping using given keys.
#[cfg_attr(
feature = "cpucycles",
instrument(skip(self, packet), fields(cpucycles))
)]
fn perform_initial_sphinx_packet_processing_with_keys(
&self,
packet: SphinxPacket,
new_blinded_secret: &Option<SharedSecret>,
routing_keys: RoutingKeys,
) -> Result<ProcessedPacket, MixProcessingError> {
measure!({
packet
.process_with_derived_keys(new_blinded_secret, routing_keys)
.map_err(|err| {
debug!("Failed to unwrap Sphinx packet: {err}");
MixProcessingError::SphinxProcessingError(err)
})
})
}
/// Takes the received framed packet and tries to unwrap it from the sphinx encryption.
#[cfg_attr(
feature = "cpucycles",
@@ -81,8 +105,28 @@ impl SphinxPacketProcessor {
if packet_mode.is_old_vpn() {
return Err(MixProcessingError::ReceivedOldTypeVpnPacket);
}
self.perform_initial_sphinx_packet_processing(sphinx_packet)
//here be shared secret retrieval and hashmap lookup
if let Some((routing_keys, blinded_shared_secret)) =
self.key_storage.lookup(sphinx_packet.shared_secret())
{
trace!("Packet already seen, reusing keys");
self.perform_initial_sphinx_packet_processing_with_keys(
sphinx_packet,
&blinded_shared_secret,
routing_keys,
)
} else {
trace!("New packet, deriving keys and storing them");
let key_secret = sphinx_packet.shared_secret();
let processed_packet =
self.perform_initial_sphinx_packet_processing(sphinx_packet)?;
self.key_storage.store(
key_secret,
processed_packet.routing_keys(),
processed_packet.shared_secret(),
);
Ok(processed_packet)
}
})
}
@@ -175,12 +219,12 @@ impl SphinxPacketProcessor {
packet_mode: PacketMode,
) -> Result<MixProcessingResult, MixProcessingError> {
match packet {
ProcessedPacket::ForwardHop(packet, address, delay) => {
ProcessedPacket::ForwardHop(packet, address, delay, _) => {
self.process_forward_hop(*packet, address, delay, packet_mode)
}
// right now there's no use for the surb_id included in the header - probably it should get removed from the
// sphinx all together?
ProcessedPacket::FinalHop(destination, _, payload) => {
ProcessedPacket::FinalHop(destination, _, payload, _) => {
self.process_final_hop(destination, payload, packet_size, packet_mode)
}
}
+37 -4
View File
@@ -3,15 +3,17 @@
use crate::var_names::{DEPRECATED_API_VALIDATOR, DEPRECATED_NYMD_VALIDATOR, NYM_API, NYXD};
use serde::{Deserialize, Serialize};
use std::{env::var, ops::Not, path::PathBuf};
use std::{
env::{var, VarError},
ffi::OsStr,
ops::Not,
path::PathBuf,
};
use url::Url;
pub mod mainnet;
pub mod var_names;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS;
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_ERC20_CONTRACT_ADDRESS;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ChainDetails {
pub bech32_account_prefix: String,
@@ -28,6 +30,8 @@ pub struct NymContracts {
pub group_contract_address: Option<String>,
pub multisig_contract_address: Option<String>,
pub coconut_dkg_contract_address: Option<String>,
pub service_provider_directory_contract_address: Option<String>,
pub name_service_contract_address: Option<String>,
}
// I wanted to use the simpler `NetworkDetails` name, but there's a clash
@@ -68,6 +72,14 @@ impl NymNetworkDetails {
}
pub fn new_from_env() -> Self {
fn get_optional_env<K: AsRef<OsStr>>(env: K) -> Option<String> {
match var(env) {
Ok(var) => Some(var),
Err(VarError::NotPresent) => None,
err => panic!("Unable to set: {:?}", err),
}
}
NymNetworkDetails::new_empty()
.with_bech32_account_prefix(
var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"),
@@ -117,6 +129,10 @@ impl NymNetworkDetails {
.with_coconut_dkg_contract(Some(
var(var_names::COCONUT_DKG_CONTRACT_ADDRESS).expect("coconut dkg contract not set"),
))
.with_service_provider_directory_contract(get_optional_env(
var_names::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
))
.with_name_service_contract(get_optional_env(var_names::NAME_SERVICE_CONTRACT_ADDRESS))
}
pub fn new_mainnet() -> Self {
@@ -146,6 +162,8 @@ impl NymNetworkDetails {
coconut_dkg_contract_address: parse_optional_str(
mainnet::COCONUT_DKG_CONTRACT_ADDRESS,
),
service_provider_directory_contract_address: None,
name_service_contract_address: None,
},
}
}
@@ -227,6 +245,21 @@ impl NymNetworkDetails {
self.contracts.coconut_dkg_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_service_provider_directory_contract<S: Into<String>>(
mut self,
contract: Option<S>,
) -> Self {
self.contracts.service_provider_directory_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_name_service_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.name_service_contract_address = contract.map(Into::into);
self
}
}
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
-4
View File
@@ -20,10 +20,6 @@ pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
+3
View File
@@ -19,6 +19,9 @@ pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS";
pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
"SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS";
pub const NAME_SERVICE_CONTRACT_ADDRESS: &str = "NAME_SERVICE_CONTRACT_ADDRESS";
pub const NYXD: &str = "NYXD";
pub const NYM_API: &str = "NYM_API";
+3
View File
@@ -46,4 +46,7 @@ pub enum NetworkTestingError {
#[error("received a packet that could not be reconstructed into a full message with a single fragment")]
NonReconstructablePacket,
#[error("the recipient of the test packet was never specified")]
UnknownPacketRecipient,
}
+2
View File
@@ -3,6 +3,8 @@
pub mod error;
pub mod message;
pub mod node;
pub mod processor;
pub mod receiver;
pub mod tester;
+60 -32
View File
@@ -2,27 +2,18 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::NetworkTestingError;
use crate::MixId;
use crate::node::TestableNode;
use nym_sphinx::message::NymMessage;
use nym_topology::{gateway, mix};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};
#[derive(Serialize, Deserialize, Hash, Clone, Copy)]
pub enum NodeType {
Mixnode(MixId),
Gateway,
}
#[derive(Serialize, Deserialize, Hash, Clone, Copy)]
#[derive(Serialize, Deserialize, Clone, Copy)]
pub struct Empty;
#[derive(Serialize, Deserialize, Clone)]
pub struct TestMessage<T = Empty> {
pub encoded_node_identity: String,
pub node_owner: String,
pub node_type: NodeType,
pub tested_node: TestableNode,
pub msg_id: u32,
pub total_msgs: u32,
@@ -34,26 +25,72 @@ pub struct TestMessage<T = Empty> {
}
impl<T> TestMessage<T> {
pub fn new_mix(node: &mix::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
pub fn new<N: Into<TestableNode>>(node: N, msg_id: u32, total_msgs: u32, ext: T) -> Self {
TestMessage {
encoded_node_identity: node.identity_key.to_base58_string(),
node_owner: node.owner.clone(),
node_type: NodeType::Mixnode(node.mix_id),
tested_node: node.into(),
msg_id,
total_msgs,
ext,
}
}
pub fn new_mix(node: &mix::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
Self::new(node, msg_id, total_msgs, ext)
}
pub fn new_gateway(node: &gateway::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
TestMessage {
encoded_node_identity: node.identity_key.to_base58_string(),
node_owner: node.owner.clone(),
node_type: NodeType::Gateway,
msg_id,
total_msgs,
ext,
Self::new(node, msg_id, total_msgs, ext)
}
pub fn new_serialized<N>(
node: N,
msg_id: u32,
total_msgs: u32,
ext: T,
) -> Result<Vec<u8>, NetworkTestingError>
where
N: Into<TestableNode>,
T: Serialize,
{
Self::new(node, msg_id, total_msgs, ext).as_bytes()
}
pub fn new_plaintexts<N>(
node: &N,
total_msgs: u32,
ext: T,
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
where
for<'a> &'a N: Into<TestableNode>,
T: Serialize + Clone,
{
let mut msgs = Vec::with_capacity(total_msgs as usize);
for msg_id in 1..=total_msgs {
msgs.push(Self::new(node, msg_id, total_msgs, ext.clone()).as_bytes()?)
}
Ok(msgs)
}
pub fn mix_plaintexts(
node: &mix::Node,
total_msgs: u32,
ext: T,
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
where
T: Serialize + Clone,
{
Self::new_plaintexts(node, total_msgs, ext)
}
pub fn gateway_plaintexts(
node: &gateway::Node,
total_msgs: u32,
ext: T,
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
where
T: Serialize + Clone,
{
Self::new_plaintexts(node, total_msgs, ext)
}
pub fn as_json_string(&self) -> Result<String, NetworkTestingError>
@@ -88,12 +125,3 @@ impl<T> TestMessage<T> {
.map_err(|source| NetworkTestingError::MalformedTestMessageReceived { source })
}
}
impl<T: Hash> Hash for TestMessage<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.encoded_node_identity.hash(state);
self.node_owner.hash(state);
self.node_type.hash(state);
self.ext.hash(state)
}
}
+92
View File
@@ -0,0 +1,92 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::MixId;
use nym_topology::{gateway, mix};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
pub struct TestableNode {
pub encoded_identity: String,
pub owner: String,
#[serde(rename = "type")]
pub typ: NodeType,
}
impl TestableNode {
pub fn new(encoded_identity: String, owner: String, typ: NodeType) -> Self {
TestableNode {
encoded_identity,
owner,
typ,
}
}
pub fn new_mixnode(encoded_identity: String, owner: String, mix_id: MixId) -> Self {
TestableNode::new(encoded_identity, owner, NodeType::Mixnode { mix_id })
}
pub fn new_gateway(encoded_identity: String, owner: String) -> Self {
TestableNode::new(encoded_identity, owner, NodeType::Gateway)
}
pub fn is_mixnode(&self) -> bool {
self.typ.is_mixnode()
}
}
impl<'a> From<&'a mix::Node> for TestableNode {
fn from(value: &'a mix::Node) -> Self {
TestableNode {
encoded_identity: value.identity_key.to_base58_string(),
owner: value.owner.clone(),
typ: NodeType::Mixnode {
mix_id: value.mix_id,
},
}
}
}
impl<'a> From<&'a gateway::Node> for TestableNode {
fn from(value: &'a gateway::Node) -> Self {
TestableNode {
encoded_identity: value.identity_key.to_base58_string(),
owner: value.owner.clone(),
typ: NodeType::Gateway,
}
}
}
impl Display for TestableNode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} {} owned by {}",
self.typ, self.encoded_identity, self.owner
)
}
}
#[derive(Serialize, Deserialize, Hash, Clone, Copy, Debug, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum NodeType {
Mixnode { mix_id: MixId },
Gateway,
}
impl NodeType {
pub fn is_mixnode(&self) -> bool {
matches!(self, NodeType::Mixnode { .. })
}
}
impl Display for NodeType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
NodeType::Mixnode { mix_id } => write!(f, "mixnode (mix_id {mix_id})"),
NodeType::Gateway => write!(f, "gateway"),
}
}
}
+99
View File
@@ -0,0 +1,99 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NetworkTestingError;
use crate::TestMessage;
use nym_crypto::asymmetric::encryption;
use nym_sphinx::acknowledgements::identifier::recover_identifier;
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::chunking::fragment::FragmentIdentifier;
use nym_sphinx::receiver::{MessageReceiver, SphinxMessageReceiver};
use serde::de::DeserializeOwned;
use std::marker::PhantomData;
use std::sync::Arc;
// simple enum containing aggregated processed results
pub enum Received<T> {
Message(TestMessage<T>),
Ack(FragmentIdentifier),
}
impl<T> From<TestMessage<T>> for Received<T> {
fn from(value: TestMessage<T>) -> Self {
Received::Message(value)
}
}
impl<T> From<FragmentIdentifier> for Received<T> {
fn from(value: FragmentIdentifier) -> Self {
Received::Ack(value)
}
}
pub struct TestPacketProcessor<T, R: MessageReceiver = SphinxMessageReceiver> {
local_encryption_keypair: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
/// Structure responsible for decrypting and recovering plaintext message from received ciphertexts.
message_receiver: R,
_ext_phantom: PhantomData<T>,
}
impl<T> TestPacketProcessor<T, SphinxMessageReceiver> {
pub fn new_sphinx_processor(
local_encryption_keypair: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
) -> Self {
Self::new(local_encryption_keypair, ack_key)
}
}
impl<T, R> TestPacketProcessor<T, R>
where
R: MessageReceiver,
{
pub fn new(local_encryption_keypair: Arc<encryption::KeyPair>, ack_key: Arc<AckKey>) -> Self {
TestPacketProcessor {
local_encryption_keypair,
ack_key,
message_receiver: R::new(),
_ext_phantom: PhantomData,
}
}
pub fn process_mixnet_message(
&mut self,
mut raw_message: Vec<u8>,
) -> Result<TestMessage<T>, NetworkTestingError>
where
T: DeserializeOwned,
{
let plaintext = self
.message_receiver
.recover_plaintext_from_regular_packet(
self.local_encryption_keypair.private_key(),
&mut raw_message,
)?;
let fragment = self.message_receiver.recover_fragment(plaintext)?;
// test messages must consist of a single fragment
let (serialized, _) = self
.message_receiver
.insert_new_fragment(fragment)?
.ok_or(NetworkTestingError::NonReconstructablePacket)?;
TestMessage::try_recover(serialized)
}
pub fn process_ack(
&mut self,
raw_ack: Vec<u8>,
) -> Result<FragmentIdentifier, NetworkTestingError> {
let serialized_ack = recover_identifier(&self.ack_key, &raw_ack)
.ok_or(NetworkTestingError::UnrecoverableAck)?;
FragmentIdentifier::try_from_bytes(serialized_ack)
.map_err(|source| NetworkTestingError::MalformedAckIdentifier { source })
}
}
+42 -61
View File
@@ -2,114 +2,95 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::NetworkTestingError;
use crate::processor::{Received, TestPacketProcessor};
use crate::{log_err, log_info, log_warn};
use futures::channel::mpsc;
use futures::StreamExt;
use nym_crypto::asymmetric::encryption;
use nym_sphinx::message::NymMessage;
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::receiver::{MessageReceiver, SphinxMessageReceiver};
use nym_sphinx::{
acknowledgements::{identifier::recover_identifier, AckKey},
chunking::fragment::FragmentIdentifier,
};
use nym_task::TaskClient;
use serde::de::DeserializeOwned;
use std::sync::Arc;
pub type ReceivedSender = mpsc::UnboundedSender<Received>;
pub type ReceivedReceiver = mpsc::UnboundedReceiver<Received>;
// simple enum containing aggregated processed results
pub enum Received {
Message(NymMessage),
Ack(FragmentIdentifier),
}
impl From<NymMessage> for Received {
fn from(value: NymMessage) -> Self {
Received::Message(value)
}
}
impl From<FragmentIdentifier> for Received {
fn from(value: FragmentIdentifier) -> Self {
Received::Ack(value)
}
}
pub type ReceivedSender<T> = mpsc::UnboundedSender<Received<T>>;
pub type ReceivedReceiver<T> = mpsc::UnboundedReceiver<Received<T>>;
// the 'Simple' bit comes from the fact that it expects all received messages to consist of a single `Fragment`
pub struct SimpleMessageReceiver<R: MessageReceiver = SphinxMessageReceiver> {
local_encryption_keypair: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
/// Structure responsible for decrypting and recovering plaintext message from received ciphertexts.
message_receiver: R,
pub struct SimpleMessageReceiver<T, R: MessageReceiver = SphinxMessageReceiver> {
message_processor: TestPacketProcessor<T, R>,
mixnet_message_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
acks_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
received_sender: ReceivedSender,
received_sender: ReceivedSender<T>,
shutdown: TaskClient,
}
impl SimpleMessageReceiver<SphinxMessageReceiver> {
impl<T> SimpleMessageReceiver<T, SphinxMessageReceiver> {
pub fn new_sphinx_receiver(
local_encryption_keypair: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
mixnet_message_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
acks_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
received_sender: ReceivedSender,
received_sender: ReceivedSender<T>,
shutdown: TaskClient,
) -> Self {
Self::new(
local_encryption_keypair,
ack_key,
mixnet_message_receiver,
acks_receiver,
received_sender,
shutdown,
)
}
}
impl<T, R: MessageReceiver> SimpleMessageReceiver<T, R> {
pub fn new(
local_encryption_keypair: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
mixnet_message_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
acks_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
received_sender: ReceivedSender<T>,
shutdown: TaskClient,
) -> Self {
SimpleMessageReceiver {
local_encryption_keypair,
ack_key,
message_receiver: SphinxMessageReceiver::new(),
message_processor: TestPacketProcessor::new(local_encryption_keypair, ack_key),
mixnet_message_receiver,
acks_receiver,
received_sender,
shutdown,
}
}
}
impl<R: MessageReceiver> SimpleMessageReceiver<R> {
fn forward_received<T: Into<Received>>(&self, received: T) {
fn forward_received<U: Into<Received<T>>>(&self, received: U) {
// TODO: remove the unwrap once/if we do graceful shutdowns here
self.received_sender
.unbounded_send(received.into())
.expect("ReceivedReceiver has stopped receiving");
}
fn on_mixnet_message(&mut self, mut raw_message: Vec<u8>) -> Result<(), NetworkTestingError> {
let plaintext = self
.message_receiver
.recover_plaintext_from_regular_packet(
self.local_encryption_keypair.private_key(),
&mut raw_message,
)?;
let fragment = self.message_receiver.recover_fragment(plaintext)?;
let (recovered, _) = self
.message_receiver
.insert_new_fragment(fragment)?
.ok_or(NetworkTestingError::NonReconstructablePacket)?; // by definition of this receiver, the message must consist of a single fragment
fn on_mixnet_message(&mut self, raw_message: Vec<u8>) -> Result<(), NetworkTestingError>
where
T: DeserializeOwned,
{
let recovered = self.message_processor.process_mixnet_message(raw_message)?;
self.forward_received(recovered);
Ok(())
}
fn on_ack(&mut self, raw_ack: Vec<u8>) -> Result<(), NetworkTestingError> {
let serialized_ack = recover_identifier(&self.ack_key, &raw_ack)
.ok_or(NetworkTestingError::UnrecoverableAck)?;
let frag_id = FragmentIdentifier::try_from_bytes(serialized_ack)
.map_err(|source| NetworkTestingError::MalformedAckIdentifier { source })?;
let frag_id = self.message_processor.process_ack(raw_ack)?;
self.forward_received(frag_id);
Ok(())
}
pub async fn run(&mut self) {
pub async fn run(&mut self)
where
T: DeserializeOwned,
{
while !self.shutdown.is_shutdown() {
tokio::select! {
biased;
+104 -18
View File
@@ -21,7 +21,10 @@ pub struct NodeTester<R> {
base_topology: NymTopology,
recipient: Recipient,
/// Generally test packets are designed to be sent from ourselves to ourselves,
/// However, one might want to customise this behaviour.
/// In that case an explicit `Recipient` has to be provided when constructing test packets.
self_address: Option<Recipient>,
packet_size: PacketSize,
@@ -47,7 +50,7 @@ where
pub fn new(
rng: R,
base_topology: NymTopology,
recipient: Recipient,
self_address: Option<Recipient>,
packet_size: PacketSize,
average_packet_delay: Duration,
average_ack_delay: Duration,
@@ -56,7 +59,7 @@ where
Self {
rng,
base_topology,
recipient,
self_address,
packet_size,
average_packet_delay,
average_ack_delay,
@@ -89,7 +92,7 @@ where
mix: &mix::Node,
test_packets: u32,
) -> Result<Vec<PreparedFragment>, NetworkTestingError> {
self.mixnode_test_packets(mix, Empty, test_packets)
self.mixnode_test_packets(mix, Empty, test_packets, None)
}
pub fn mixnode_test_packets<T>(
@@ -97,6 +100,7 @@ where
mix: &mix::Node,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
where
T: Serialize + Clone,
@@ -104,9 +108,35 @@ where
let ephemeral_topology = self.testable_mix_topology(mix);
let mut packets = Vec::with_capacity(test_packets as usize);
for i in 1..=test_packets {
let msg = TestMessage::new_mix(mix, i, test_packets, msg_ext.clone());
packets.push(self.create_test_packet(&msg, &ephemeral_topology)?);
for plaintext in TestMessage::mix_plaintexts(mix, test_packets, msg_ext)? {
packets.push(self.wrap_plaintext_data(
plaintext,
&ephemeral_topology,
custom_recipient,
)?);
}
Ok(packets)
}
pub fn mixnodes_test_packets<T>(
&mut self,
nodes: &[mix::Node],
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
where
T: Serialize + Clone,
{
let mut packets = Vec::new();
for node in nodes {
packets.append(&mut self.mixnode_test_packets(
node,
msg_ext.clone(),
test_packets,
custom_recipient,
)?)
}
Ok(packets)
@@ -117,6 +147,7 @@ where
mix_id: MixId,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
where
T: Serialize + Clone,
@@ -125,7 +156,7 @@ where
return Err(NetworkTestingError::NonExistentMixnode {mix_id})
};
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets)
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient)
}
pub fn existing_identity_mixnode_test_packets<T>(
@@ -133,6 +164,7 @@ where
encoded_mix_identity: String,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
where
T: Serialize + Clone,
@@ -141,19 +173,57 @@ where
return Err(NetworkTestingError::NonExistentMixnodeIdentity { mix_identity: encoded_mix_identity })
};
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets)
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient)
}
pub fn create_test_packet<T>(
pub fn gateway_test_packets<T>(
&mut self,
message: &TestMessage<T>,
topology: &NymTopology,
) -> Result<PreparedFragment, NetworkTestingError>
gateway: &gateway::Node,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
where
T: Serialize,
T: Serialize + Clone,
{
let serialized = message.as_bytes()?;
let message = NymMessage::new_plain(serialized);
let ephemeral_topology = self.testable_gateway_topology(gateway);
let mut packets = Vec::with_capacity(test_packets as usize);
for plaintext in TestMessage::gateway_plaintexts(gateway, test_packets, msg_ext)? {
packets.push(self.wrap_plaintext_data(
plaintext,
&ephemeral_topology,
custom_recipient,
)?);
}
Ok(packets)
}
pub fn existing_gateway_test_packets<T>(
&mut self,
encoded_gateway_identity: String,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
where
T: Serialize + Clone,
{
let Some(node) = self.base_topology.find_gateway(&encoded_gateway_identity) else {
return Err(NetworkTestingError::NonExistentGateway { gateway_identity: encoded_gateway_identity })
};
self.gateway_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient)
}
pub fn wrap_plaintext_data(
&mut self,
plaintext: Vec<u8>,
topology: &NymTopology,
custom_recipient: Option<Recipient>,
) -> Result<PreparedFragment, NetworkTestingError> {
let message = NymMessage::new_plain(plaintext);
let mut fragments = self.pad_and_split_message(message, self.packet_size);
@@ -165,13 +235,29 @@ where
// we would have returned the error when checking for its length
let fragment = fragments.pop().unwrap();
// the packet is designed to be sent from ourselves to ourselves
let address = self.recipient;
// either `self_address` or `custom_recipient` has to be specified.
let address = custom_recipient.unwrap_or(
self.self_address
.ok_or(NetworkTestingError::UnknownPacketRecipient)?,
);
// TODO: can we avoid this arc clone?
let ack_key = Arc::clone(&self.ack_key);
Ok(self.prepare_chunk_for_sending(fragment, topology, &ack_key, &address, &address)?)
}
pub fn create_test_packet<T>(
&mut self,
message: &TestMessage<T>,
topology: &NymTopology,
custom_recipient: Option<Recipient>,
) -> Result<PreparedFragment, NetworkTestingError>
where
T: Serialize,
{
let serialized = message.as_bytes()?;
self.wrap_plaintext_data(serialized, topology, custom_recipient)
}
}
impl<R: CryptoRng + Rng> FragmentPreparer for NodeTester<R> {
+3 -2
View File
@@ -20,11 +20,12 @@ nym-sphinx-chunking = { path = "chunking" }
nym-sphinx-cover = { path = "cover" }
nym-sphinx-forwarding = { path = "forwarding" }
nym-sphinx-params = { path = "params" }
nym-sphinx-routing = { path = "routing" }
nym-sphinx-types = { path = "types" }
# those dependencies are due to intriducing preparer and receiver. Perpaphs that indicates they should be moved
# to separate crate?
nym-crypto = { path = "../crypto", version = "0.2.0" }
nym-crypto = { path = "../crypto", version = "0.3.0" }
nym-topology = { path = "../topology" }
# outfox
@@ -32,7 +33,7 @@ nym-outfox = { path = "../../nym-outfox" }
[dev-dependencies]
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
nym-crypto = { path = "../crypto", version = "0.2.0", features = ["asymmetric"] }
nym-crypto = { path = "../crypto", version = "0.3.0", features = ["asymmetric"] }
# do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require
# net2 via tokio-util -> tokio -> mio -> net2
@@ -60,6 +60,7 @@ impl SurbAck {
let surb_ack_packet = SphinxPacketBuilder::new()
.with_payload_size(PacketSize::AckPacket.payload_size())
.with_initial_secret(&[42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42].into())
.build_packet(surb_ack_payload, &route, &destination, &delays)
.unwrap();
+1
View File
@@ -113,6 +113,7 @@ where
// once merged, that's an easy rng injection point for sphinx packets : )
let packet = SphinxPacketBuilder::new()
.with_payload_size(packet_size.payload_size())
.with_initial_secret(&[42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42].into())
.build_packet(packet_payload, &route, &destination, &delays)
.unwrap();
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "nym-sphinx-routing"
version = "0.1.0"
description = "Sphinx packet routing as Nym mix packets"
edition = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
[dependencies]
thiserror = { workspace = true }
nym-sphinx-addressing = { path = "../addressing" }
nym-sphinx-types = { path = "../types" }
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_types::Node;
use thiserror::Error;
pub trait SphinxRouteMaker {
type Error;
fn sphinx_route(&mut self, hops: u8, destination: &Recipient)
-> Result<Vec<Node>, Self::Error>;
}
#[derive(Debug, Error, Clone, Copy)]
#[error("the route vector contains {available} nodes while {requested} hops are required")]
pub struct InvalidNumberOfHops {
available: usize,
requested: u8,
}
// if one wants to provide a hardcoded route, they can
impl SphinxRouteMaker for Vec<Node> {
type Error = InvalidNumberOfHops;
fn sphinx_route(
&mut self,
hops: u8,
_destination: &Recipient,
) -> Result<Vec<Node>, InvalidNumberOfHops> {
// it's the responsibility of the caller to ensure the hardcoded route has correct number of hops
// and that it's final hop include the recipient's gateway.
if self.len() != hops as usize {
Err(InvalidNumberOfHops {
available: self.len(),
requested: hops,
})
} else {
Ok(self.clone())
}
}
}
+4 -2
View File
@@ -13,10 +13,12 @@ pub use nym_sphinx_anonymous_replies as anonymous_replies;
pub use nym_sphinx_chunking as chunking;
pub use nym_sphinx_cover as cover;
pub use nym_sphinx_forwarding as forwarding;
pub use nym_sphinx_params as params;
pub use nym_sphinx_routing as routing;
pub use nym_sphinx_types::*;
#[cfg(not(target_arch = "wasm32"))]
pub use nym_sphinx_framing as framing;
pub use nym_sphinx_params as params;
pub use nym_sphinx_types::*;
// TEMP UNTIL FURTHER REFACTORING
pub use preparer::payload::NymsphinxPayloadBuilder;
+7
View File
@@ -38,6 +38,12 @@ pub struct PreparedFragment {
pub fragment_identifier: FragmentIdentifier,
}
impl From<PreparedFragment> for MixPacket {
fn from(value: PreparedFragment) -> Self {
value.mix_packet
}
}
// this is extracted into a trait with default implementation to remove duplicate code
// (which we REALLY want to avoid with crypto)
pub trait FragmentPreparer {
@@ -202,6 +208,7 @@ pub trait FragmentPreparer {
// there's absolutely no reason for this call to fail.
let sphinx_packet = SphinxPacketBuilder::new()
.with_payload_size(packet_size.payload_size())
.with_initial_secret(&[42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42].into())
.build_packet(packet_payload, &route, &destination, &delays)
.unwrap();
+2 -1
View File
@@ -8,7 +8,8 @@ license = { workspace = true }
repository = { workspace = true }
[dependencies]
sphinx-packet = { version = "0.1.0" }
#sphinx-packet = { version = "0.1.0" }
sphinx-packet = { git = "https://github.com/nymtech/sphinx.git", branch = "simon/key-reuse"}
#[patch.crates-io]
#sphinx-packet = { path = "../../../../sphinx" }
+1 -1
View File
@@ -8,7 +8,7 @@ pub use sphinx_packet::{
PAYLOAD_KEY_SIZE,
},
crypto::{self, EphemeralSecret, PrivateKey, PublicKey, SharedSecret},
header::{self, delays, delays::Delay, ProcessedHeader, SphinxHeader, HEADER_SIZE},
header::{self, delays, delays::Delay, ProcessedHeader, SphinxHeader, HEADER_SIZE, keys::RoutingKeys},
packet::builder::{self, DEFAULT_PAYLOAD_SIZE},
payload::{Payload, PAYLOAD_OVERHEAD_SIZE},
route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier},
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "nym-store-cipher"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
aes-gcm = { version = "0.10.1" }
argon2 = { version = "0.5.0" }
rand = "0.8.5"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, optional = true }
thiserror = { workspace = true }
zeroize = { version = "1.6.0", features = ["zeroize_derive"] }
[target.'cfg(target_env = "wasm32-unknown-unknown")'.dependencies]
getrandom = { version = "0.2", features = ["js"] }
[features]
default = []
json = ["serde_json"]
+279
View File
@@ -0,0 +1,279 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use aes_gcm::aead::{Aead, Nonce};
use aes_gcm::{AeadCore, AeadInPlace, Key, KeyInit, KeySizeUser};
use rand::{thread_rng, CryptoRng, Fill, RngCore};
use serde::{Deserialize, Serialize};
use serde_helpers::{argon2_algorithm_helper, argon2_params_helper, argon2_version_helper};
use thiserror::Error;
use zeroize::{Zeroize, ZeroizeOnDrop};
pub use aes_gcm::Aes256Gcm;
pub use argon2::{Algorithm, Argon2, Params, Version};
mod serde_helpers;
pub const CURRENT_VERSION: u8 = 1;
pub const ARGON2_SALT_SIZE: usize = 16;
pub const AES256GCM_NONCE_SIZE: usize = 12;
#[derive(Debug, Error)]
pub enum Error {
#[error("failed to encrypt/decrypt provided data: {cause}")]
AesFailure { cause: aes_gcm::Error },
#[error("failed to expand the passphrase: {cause}")]
Argon2Failure { cause: argon2::Error },
#[cfg(feature = "json")]
#[error("failed to serialize/deserialize JSON: {source}")]
SerdeJsonFailure {
#[from]
source: serde_json::Error,
},
#[error("failed to generate random bytes: {source}")]
RandomError {
#[from]
source: rand::Error,
},
#[error("the received ciphertext was encrypted with different store version ({received}). The current version is {CURRENT_VERSION}")]
VersionMismatch { received: u8 },
}
// it's weird that this couldn't be auto-derived with a `#[from]`...
impl From<aes_gcm::Error> for Error {
fn from(cause: aes_gcm::Error) -> Self {
Error::AesFailure { cause }
}
}
impl From<argon2::Error> for Error {
fn from(cause: argon2::Error) -> Self {
Error::Argon2Failure { cause }
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum KdfInfo {
Argon2 {
/// The Argon2 parameters that were used when deriving the store key.
#[serde(with = "argon2_params_helper")]
params: Params,
/// The specific Argon2 algorithm variant used when deriving the store key.
#[serde(with = "argon2_algorithm_helper")]
algorithm: Algorithm,
/// The specific version of the Argon2 algorithm used when deriving the store key.
#[serde(with = "argon2_version_helper")]
version: Version,
/// The salt that was used when the passphrase was expanded into a store key.
kdf_salt: [u8; ARGON2_SALT_SIZE],
},
}
impl KdfInfo {
pub fn expand_key<C>(&self, passphrase: &[u8]) -> Result<Key<C>, Error>
where
C: KeySizeUser,
{
match self {
KdfInfo::Argon2 {
params,
algorithm,
version,
kdf_salt,
} => argon2_derive_cipher_key::<C>(
passphrase,
kdf_salt,
&[],
params.clone(),
*algorithm,
*version,
),
}
}
pub fn new_with_default_settings() -> Result<Self, Error> {
let kdf_salt = Self::random_salt()?;
Ok(KdfInfo::Argon2 {
params: Default::default(),
algorithm: Default::default(),
version: Default::default(),
kdf_salt,
})
}
pub fn random_salt() -> Result<[u8; ARGON2_SALT_SIZE], Error> {
let mut rng = thread_rng();
Self::random_salt_with_rng(&mut rng)
}
pub fn random_salt_with_rng<R: RngCore + CryptoRng>(
rng: &mut R,
) -> Result<[u8; ARGON2_SALT_SIZE], Error> {
let mut salt = [0u8; ARGON2_SALT_SIZE];
salt.try_fill(rng)?;
Ok(salt)
}
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct StoreCipher<C = Aes256Gcm>
where
C: KeySizeUser,
{
key: Key<C>,
}
impl<C: KeySizeUser + KeyInit> StoreCipher<C>
where
C: KeySizeUser + KeyInit,
{
pub fn new(passphrase: &[u8], kdf_info: KdfInfo) -> Result<Self, Error> {
let key = kdf_info.expand_key::<C>(passphrase)?;
Ok(StoreCipher { key })
}
pub fn new_with_default_kdf(passphrase: &[u8]) -> Result<Self, Error> {
let kdf_info = KdfInfo::new_with_default_settings()?;
Self::new(passphrase, kdf_info)
}
#[cfg(feature = "json")]
pub fn encrypt_json_value<T: Serialize>(&self, data: &T) -> Result<EncryptedData, Error>
where
C: AeadInPlace,
{
let raw = serde_json::to_vec(data)?;
self.encrypt_data(raw)
}
// Unless you know what you're doing, use `Self::encrypt_data` instead.
// As the caller of this method needs to make sure to correctly dispose of the original plaintext.
pub fn encrypt_data_ref(&self, data: &[u8]) -> Result<EncryptedData, Error>
where
C: Aead,
{
let nonce = Self::random_nonce()?;
let cipher = C::new(&self.key);
let ciphertext = cipher.encrypt(&nonce, data)?;
Ok(EncryptedData {
version: CURRENT_VERSION,
ciphertext,
nonce: nonce.to_vec(),
})
}
pub fn encrypt_data(&self, mut data: Vec<u8>) -> Result<EncryptedData, Error>
where
C: AeadInPlace,
{
let nonce = Self::random_nonce()?;
let cipher = C::new(&self.key);
cipher.encrypt_in_place(&nonce, &[], &mut data)?;
Ok(EncryptedData {
version: CURRENT_VERSION,
ciphertext: data,
nonce: nonce.to_vec(),
})
}
#[cfg(feature = "json")]
pub fn decrypt_json_value<T: serde::de::DeserializeOwned>(
&self,
data: EncryptedData,
) -> Result<T, Error>
where
C: AeadInPlace,
{
let mut plaintext = self.decrypt_data(data)?;
// DO NOT USE `?` here!!
// We need to make sure to zeroize the plaintext even if deserialization fails!!
let value = serde_json::from_slice(&plaintext);
plaintext.zeroize();
Ok(value?)
}
pub fn decrypt_data_unchecked(&self, data: EncryptedData) -> Result<Vec<u8>, Error>
where
C: Aead,
{
let cipher = C::new(&self.key);
let plaintext = cipher.decrypt(
Nonce::<C>::from_slice(&data.nonce),
data.ciphertext.as_ref(),
)?;
Ok(plaintext)
}
pub fn decrypt_data(&self, data: EncryptedData) -> Result<Vec<u8>, Error>
where
C: Aead,
{
if data.version != CURRENT_VERSION {
return Err(Error::VersionMismatch {
received: data.version,
});
}
self.decrypt_data_unchecked(data)
}
pub fn random_nonce() -> Result<Nonce<C>, Error>
where
C: AeadCore,
{
let mut rng = thread_rng();
Self::random_nonce_with_rng(&mut rng)
}
pub fn random_nonce_with_rng<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Nonce<C>, Error>
where
C: AeadCore,
{
let mut nonce = Nonce::<C>::default();
nonce.try_fill(rng)?;
Ok(nonce)
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct EncryptedData {
pub version: u8,
pub ciphertext: Vec<u8>,
pub nonce: Vec<u8>,
}
pub fn argon2_derive_cipher_key<C>(
passphrase: &[u8],
salt: &[u8],
pepper: &[u8],
params: Params,
algorithm: Algorithm,
version: Version,
) -> Result<Key<C>, Error>
where
C: KeySizeUser,
{
let argon2 = if pepper.is_empty() {
Argon2::new(algorithm, version, params)
} else {
Argon2::new_with_secret(pepper, algorithm, version, params)?
};
let mut key = Key::<C>::default();
argon2.hash_password_into(passphrase, salt, &mut key)?;
Ok(key)
}
+132
View File
@@ -0,0 +1,132 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod argon2_params_helper {
use argon2::Params;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// Refer to [argon2::Params] for details.
#[derive(Serialize, Deserialize)]
struct Argon2Params {
m_cost: u32,
t_cost: u32,
p_cost: u32,
output_len: Option<usize>,
// Note: `keyid` and `data` are not longer part of the argon2 standard
// (see: <https://github.com/P-H-C/phc-winner-argon2/pull/173>), and should
// not be used for any non-legacy work.
// So we're explicitly skipping them for serialization
}
impl From<&Params> for Argon2Params {
fn from(value: &Params) -> Self {
Argon2Params {
m_cost: value.m_cost(),
t_cost: value.t_cost(),
p_cost: value.p_cost(),
output_len: value.output_len(),
}
}
}
impl TryFrom<Argon2Params> for Params {
type Error = argon2::Error;
fn try_from(value: Argon2Params) -> Result<Self, Self::Error> {
Params::new(value.m_cost, value.t_cost, value.p_cost, value.output_len)
}
}
pub fn serialize<S: Serializer>(params: &Params, serializer: S) -> Result<S::Ok, S::Error> {
Argon2Params::from(params).serialize(serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Params, D::Error> {
<Argon2Params>::deserialize(deserializer)?
.try_into()
.map_err(serde::de::Error::custom)
}
}
pub(crate) mod argon2_algorithm_helper {
use argon2::Algorithm;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// Refer to [argon2::Algorithm] for details.
#[derive(Serialize, Deserialize)]
enum Argon2Algorithm {
Argon2d = 0,
Argon2i = 1,
Argon2id = 2,
}
impl From<Algorithm> for Argon2Algorithm {
fn from(value: Algorithm) -> Self {
match value {
Algorithm::Argon2d => Argon2Algorithm::Argon2d,
Algorithm::Argon2i => Argon2Algorithm::Argon2i,
Algorithm::Argon2id => Argon2Algorithm::Argon2id,
}
}
}
impl From<Argon2Algorithm> for Algorithm {
fn from(value: Argon2Algorithm) -> Self {
match value {
Argon2Algorithm::Argon2d => Algorithm::Argon2d,
Argon2Algorithm::Argon2i => Algorithm::Argon2i,
Argon2Algorithm::Argon2id => Algorithm::Argon2id,
}
}
}
pub fn serialize<S: Serializer>(
algorithm: &Algorithm,
serializer: S,
) -> Result<S::Ok, S::Error> {
Argon2Algorithm::from(*algorithm).serialize(serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Algorithm, D::Error> {
<Argon2Algorithm>::deserialize(deserializer).map(From::from)
}
}
pub(crate) mod argon2_version_helper {
use argon2::Version;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// Refer to [argon2::Version] for details.
#[derive(Serialize, Deserialize)]
#[repr(u32)]
enum Argon2Version {
V0x10 = 0x10,
V0x13 = 0x13,
}
impl From<Version> for Argon2Version {
fn from(value: Version) -> Self {
match value {
Version::V0x10 => Argon2Version::V0x10,
Version::V0x13 => Argon2Version::V0x13,
}
}
}
impl From<Argon2Version> for Version {
fn from(value: Argon2Version) -> Self {
match value {
Argon2Version::V0x10 => Version::V0x10,
Argon2Version::V0x13 => Version::V0x13,
}
}
}
pub fn serialize<S: Serializer>(algorithm: &Version, serializer: S) -> Result<S::Ok, S::Error> {
Argon2Version::from(*algorithm).serialize(serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Version, D::Error> {
<Argon2Version>::deserialize(deserializer).map(From::from)
}
}
+1
View File
@@ -23,6 +23,7 @@ nym-crypto = { path = "../crypto" }
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
nym-sphinx-addressing = { path = "../nymsphinx/addressing" }
nym-sphinx-types = { path = "../nymsphinx/types" }
nym-sphinx-routing = { path = "../nymsphinx/routing" }
nym-bin-common = { path = "../bin-common" }
[features]
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::MixLayer;
use thiserror::Error;
#[derive(Debug, Clone, Error)]
pub enum NymTopologyError {
#[error("The provided network topology is empty - there are no mixnodes and no gateways on it - the network request(s) probably failed")]
EmptyNetworkTopology,
#[error("The provided network topology has no gateways available")]
NoGatewaysAvailable,
#[error("The provided network topology has no mixnodes available")]
NoMixnodesAvailable,
#[error("Gateway with identity key {identity_key} doesn't exist")]
NonExistentGatewayError { identity_key: String },
#[error("Wanted to create a mix route with {requested} hops, while only {available} layers are available")]
InvalidNumberOfHopsError { available: usize, requested: usize },
#[error("No mixnodes available on layer {layer}")]
EmptyMixLayer { layer: MixLayer },
#[error("Uneven layer distribution. Layer {layer} has {nodes} on it, while we expected a value between {lower_bound} and {upper_bound} as we have {total_nodes} nodes in total. Full breakdown: {layer_distribution:?}")]
UnevenLayerDistribution {
layer: MixLayer,
nodes: usize,
lower_bound: usize,
upper_bound: usize,
total_nodes: usize,
layer_distribution: Vec<(MixLayer, usize)>,
},
}
+10 -33
View File
@@ -14,45 +14,17 @@ use std::fmt::{self, Display, Formatter};
use std::io;
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
use std::str::FromStr;
use thiserror::Error;
pub mod error;
pub mod filter;
pub mod gateway;
pub mod mix;
pub mod random_route_provider;
#[cfg(feature = "provider-trait")]
pub mod provider_trait;
#[derive(Debug, Clone, Error)]
pub enum NymTopologyError {
#[error("The provided network topology is empty - there are no mixnodes and no gateways on it - the network request(s) probably failed")]
EmptyNetworkTopology,
#[error("The provided network topology has no gateways available")]
NoGatewaysAvailable,
#[error("The provided network topology has no mixnodes available")]
NoMixnodesAvailable,
#[error("Gateway with identity key {identity_key} doesn't exist")]
NonExistentGatewayError { identity_key: String },
#[error("Wanted to create a mix route with {requested} hops, while only {available} layers are available")]
InvalidNumberOfHopsError { available: usize, requested: usize },
#[error("No mixnodes available on layer {layer}")]
EmptyMixLayer { layer: MixLayer },
#[error("Uneven layer distribution. Layer {layer} has {nodes} on it, while we expected a value between {lower_bound} and {upper_bound} as we have {total_nodes} nodes in total. Full breakdown: {layer_distribution:?}")]
UnevenLayerDistribution {
layer: MixLayer,
nodes: usize,
lower_bound: usize,
upper_bound: usize,
total_nodes: usize,
layer_distribution: Vec<(MixLayer, usize)>,
},
}
pub use error::NymTopologyError;
#[derive(Debug, Clone)]
pub enum NetworkAddress {
@@ -134,6 +106,12 @@ impl NymTopology {
None
}
pub fn find_gateway(&self, gateway_identity: IdentityKeyRef) -> Option<&gateway::Node> {
self.gateways
.iter()
.find(|&gateway| gateway.identity_key.to_base58_string() == gateway_identity)
}
pub fn mixes(&self) -> &BTreeMap<MixLayer, Vec<mix::Node>> {
&self.mixes
}
@@ -182,8 +160,7 @@ impl NymTopology {
num_mix_hops: u8,
) -> Result<Vec<SphinxNode>, NymTopologyError>
where
// I don't think there's a need for this RNG to be crypto-secure
R: Rng + ?Sized,
R: Rng + CryptoRng + ?Sized,
{
use rand::seq::SliceRandom;
@@ -0,0 +1,29 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{NymTopology, NymTopologyError};
use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_routing::SphinxRouteMaker;
use nym_sphinx_types::Node;
use rand::{CryptoRng, Rng};
pub struct NymTopologyRouteProvider<R> {
rng: R,
inner: NymTopology,
}
impl<R> SphinxRouteMaker for NymTopologyRouteProvider<R>
where
R: Rng + CryptoRng,
{
type Error = NymTopologyError;
fn sphinx_route(
&mut self,
hops: u8,
destination: &Recipient,
) -> Result<Vec<Node>, NymTopologyError> {
self.inner
.random_route_to_gateway(&mut self.rng, hops, destination.gateway())
}
}
+202 -12
View File
@@ -536,7 +536,7 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
dependencies = [
"curve25519-dalek",
"ed25519",
"rand",
"rand 0.7.3",
"serde",
"sha2",
"zeroize",
@@ -662,6 +662,101 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
[[package]]
name = "futures"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-executor"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-macro"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.12",
]
[[package]]
name = "futures-sink"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
[[package]]
name = "futures-task"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]]
name = "futures-timer"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
[[package]]
name = "futures-util"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "generic-array"
version = "0.12.4"
@@ -949,6 +1044,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mixnet-vesting-integration-tests"
version = "0.1.0"
@@ -1045,13 +1146,13 @@ dependencies = [
[[package]]
name = "nym-crypto"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"bs58",
"ed25519-dalek",
"nym-pemstore",
"nym-sphinx-types",
"rand",
"rand 0.7.3",
"subtle-encoding",
"thiserror",
"x25519-dalek",
@@ -1121,6 +1222,36 @@ dependencies = [
"thiserror",
]
[[package]]
name = "nym-name-service"
version = "0.1.0"
dependencies = [
"anyhow",
"cosmwasm-std",
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"cw-utils",
"cw2",
"nym-contracts-common",
"nym-name-service-common",
"rand 0.8.5",
"rstest",
"semver",
"serde",
"thiserror",
"vergen",
]
[[package]]
name = "nym-name-service-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "nym-pemstore"
version = "0.2.0"
@@ -1152,6 +1283,7 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
@@ -1231,6 +1363,18 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs8"
version = "0.8.0"
@@ -1338,6 +1482,17 @@ dependencies = [
"rand_hc",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
@@ -1383,7 +1538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56"
dependencies = [
"num-traits",
"rand",
"rand 0.7.3",
]
[[package]]
@@ -1430,6 +1585,32 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rstest"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962"
dependencies = [
"futures",
"futures-timer",
"rstest_macros",
"rustc_version",
]
[[package]]
name = "rstest_macros"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290ca1a1c8ca7edb7c3283bd44dc35dd54fdec6253a3912e201ba1072018fca8"
dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"rustc_version",
"syn 1.0.109",
"unicode-ident",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
@@ -1546,7 +1727,7 @@ checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.3",
"syn 2.0.12",
]
[[package]]
@@ -1562,9 +1743,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.94"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
dependencies = [
"itoa",
"ryu",
@@ -1579,7 +1760,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.3",
"syn 2.0.12",
]
[[package]]
@@ -1605,6 +1786,15 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "slab"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
"autocfg",
]
[[package]]
name = "sphinx-packet"
version = "0.1.0"
@@ -1623,7 +1813,7 @@ dependencies = [
"hmac",
"lioness",
"log",
"rand",
"rand 0.7.3",
"rand_distr",
"sha2",
"subtle 2.4.1",
@@ -1679,9 +1869,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.3"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8234ae35e70582bfa0f1fedffa6daa248e41dd045310b19800c4a36382c8f60"
checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927"
dependencies = [
"proc-macro2",
"quote",
@@ -1730,7 +1920,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.3",
"syn 2.0.12",
]
[[package]]
+1
View File
@@ -7,6 +7,7 @@ members = [
"mixnet-vesting-integration-tests",
"multisig/cw3-flex-multisig",
"multisig/cw4-group",
"name-service",
"service-provider-directory",
"vesting",
]
+28
View File
@@ -0,0 +1,28 @@
[package]
name = "nym-name-service"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
cosmwasm-std = { workspace = true }
cw-controllers = { workspace = true }
cw-storage-plus = { workspace = true }
cw-utils = { workspace = true }
cw2 = { workspace = true }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.4.0" }
nym-name-service-common = { path = "../../common/cosmwasm-smart-contracts/name-service" }
semver = { version = "1.0.16", default-features = false }
serde = { version = "1.0.155", default-features = false, features = ["derive"] }
thiserror = "1.0.39"
[build-dependencies]
vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc"] }
[dev-dependencies]
anyhow = "1.0.40"
cw-multi-test = { workspace = true }
rand = "0.8.5"
rstest = "0.17.0"
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use vergen::{vergen, Config};
fn main() {
let mut config = Config::default();
if std::env::var("DOCS_RS").is_ok() {
// If we don't have access to git information, such as in a docs.rs build, don't error
*config.git_mut().skip_if_error_mut() = true;
}
vergen(config).expect("failed to extract build metadata");
}
+16
View File
@@ -0,0 +1,16 @@
// We limit the these for simplicity and to avoid having to deal with paging.
pub const MAX_NUMBER_OF_NAMES_PER_OWNER: u32 = 100;
pub const MAX_NUMBER_OF_NAMES_FOR_ADDRESS: u32 = 100;
pub const NAME_DEFAULT_RETRIEVAL_LIMIT: u32 = 100;
pub const NAME_MAX_RETRIEVAL_LIMIT: u32 = 150;
// Storage keys
pub const CONFIG_KEY: &str = "config";
pub const ADMIN_KEY: &str = "admin";
pub const NAME_ID_COUNTER_KEY: &str = "nidc";
pub const NAMES_PK_NAMESPACE: &str = "nanames";
pub const NAMES_OWNER_IDX_NAMESPACE: &str = "naowner";
pub const NAMES_ADDRESS_IDX_NAMESPACE: &str = "naaddress";
pub const NAMES_NAME_IDX_NAMESPACE: &str = "naname";
+272
View File
@@ -0,0 +1,272 @@
use crate::{
error::{NameServiceError, Result},
state::{self, Config},
};
use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response};
use nym_name_service_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
use semver::Version;
mod execute;
mod query;
// version info for migration info
const CONTRACT_NAME: &str = "crate:nym-name-service";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn instantiate(
mut deps: DepsMut<'_>,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response> {
state::set_admin(deps.branch(), info.sender.clone())?;
let config = Config {
deposit_required: msg.deposit_required,
};
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
state::save_config(deps.storage, &config)?;
Ok(Response::new()
.add_attribute("method", "instantiate")
.add_attribute("admin", info.sender))
}
pub fn migrate(
deps: DepsMut<'_>,
_env: Env,
_msg: MigrateMsg,
) -> Result<Response, NameServiceError> {
// Note: don't remove this particular bit of code as we have to ALWAYS check whether we have to
// update the stored version
let version: Version = CONTRACT_VERSION.parse().map_err(|error: semver::Error| {
NameServiceError::SemVerFailure {
value: CONTRACT_VERSION.to_string(),
error_message: error.to_string(),
}
})?;
let storage_version_raw = cw2::get_contract_version(deps.storage)?.version;
let storage_version: Version =
storage_version_raw
.parse()
.map_err(|error: semver::Error| NameServiceError::SemVerFailure {
value: storage_version_raw,
error_message: error.to_string(),
})?;
if storage_version < version {
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
// If state structure changed in any contract version in the way migration is needed, it
// should occur here, for example anything from `crate::queued_migrations::`
}
Ok(Response::new())
}
pub fn execute(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, NameServiceError> {
match msg {
ExecuteMsg::Register { name, address } => execute::register(deps, env, info, name, address),
ExecuteMsg::DeleteId { name_id } => execute::delete_id(deps, info, name_id),
ExecuteMsg::DeleteName { name } => execute::delete_name(deps, info, name),
ExecuteMsg::UpdateDepositRequired { deposit_required } => {
execute::update_deposit_required(deps, info, deposit_required)
}
}
}
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<Binary> {
let response = match msg {
QueryMsg::NameId { name_id } => to_binary(&query::query_id(deps, name_id)?),
QueryMsg::ByOwner { owner } => to_binary(&query::query_owner(deps, owner)?),
QueryMsg::ByAddress { address } => to_binary(&query::query_address(deps, address)?),
QueryMsg::ByName { name } => to_binary(&query::query_name(deps, name)?),
QueryMsg::All { limit, start_after } => {
to_binary(&query::query_all_paged(deps, limit, start_after)?)
}
QueryMsg::Config {} => to_binary(&query::query_config(deps)?),
QueryMsg::GetContractVersion {} => to_binary(&query::query_contract_version()),
QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?),
};
Ok(response?)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helpers::{
assert::{assert_config, assert_empty, assert_name, assert_names, assert_not_found},
fixture::name_fixture,
helpers::{get_attribute, nyms},
};
use cosmwasm_std::{
testing::{mock_dependencies, mock_env, mock_info},
Addr, Coin,
};
use nym_name_service_common::{msg::ExecuteMsg, NameEntry, NameId};
const DENOM: &str = "unym";
#[test]
fn instantiate_contract() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
deposit_required: Coin::new(100u128, DENOM),
};
let info = mock_info("creator", &[]);
let admin = info.sender.clone();
// Instantiate contract
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
// Check that it worked by querying the config, and checking that the list of names is
// empty
assert_config(deps.as_ref(), &admin, Coin::new(100u128, DENOM));
assert_empty(deps.as_ref());
}
#[test]
fn register_fails_incorrect_deposit() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg::new(nyms(100));
let info = mock_info("creator", &[]);
let admin = info.sender.clone();
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
// Register
let msg: ExecuteMsg = name_fixture().into();
let owner = name_fixture().owner.to_string();
assert_eq!(
execute(
deps.as_mut(),
mock_env(),
mock_info(&owner, &[nyms(99)]),
msg.clone()
)
.unwrap_err(),
NameServiceError::InsufficientDeposit {
funds: 99u128.into(),
deposit_required: 100u128.into(),
}
);
assert_eq!(
execute(
deps.as_mut(),
mock_env(),
mock_info(&owner, &[nyms(101)]),
msg
)
.unwrap_err(),
NameServiceError::TooLargeDeposit {
funds: 101u128.into(),
deposit_required: 100u128.into(),
}
);
assert_config(deps.as_ref(), &admin, Coin::new(100, DENOM));
assert_empty(deps.as_ref());
}
#[test]
fn register_success() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg::new(nyms(100));
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
// Register
let msg: ExecuteMsg = name_fixture().into();
let info = mock_info("steve", &[nyms(100)]);
let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
// Check that the name has had name id assigned to it
let expected_id = 1;
let id: NameId = get_attribute(&res, "register", "name_id").parse().unwrap();
assert_eq!(id, expected_id);
assert_eq!(
get_attribute(&res, "register", "name"),
"my-service".to_string()
);
assert_eq!(
get_attribute(&res, "register", "nym_address"),
"client_id.client_key@gateway_id".to_string()
);
// The expected registered name
let expected_name = NameEntry {
name_id: expected_id,
name: name_fixture(),
};
assert_names(deps.as_ref(), &[expected_name.clone()]);
assert_name(deps.as_ref(), &expected_name);
}
#[test]
fn delete() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg::new(Coin::new(100, "unym"));
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
// Register
let msg: ExecuteMsg = name_fixture().into();
let info_steve = mock_info("steve", &[nyms(100)]);
assert_eq!(info_steve.sender, name_fixture().owner);
execute(deps.as_mut(), mock_env(), info_steve, msg).unwrap();
// The expected registerd name
let expected_id = 1;
let expected_name = NameEntry {
name_id: expected_id,
name: name_fixture(),
};
assert_names(deps.as_ref(), &[expected_name]);
// Removing someone else's name will fail
let msg = ExecuteMsg::delete_id(expected_id);
let info_timmy = mock_info("timmy", &[]);
assert_eq!(
execute(deps.as_mut(), mock_env(), info_timmy, msg).unwrap_err(),
NameServiceError::Unauthorized {
sender: Addr::unchecked("timmy")
}
);
// Removing an non-existent name will fail
let msg = ExecuteMsg::delete_id(expected_id + 1);
let info_owner = MessageInfo {
sender: name_fixture().owner,
funds: vec![],
};
assert_eq!(
execute(deps.as_mut(), mock_env(), info_owner.clone(), msg).unwrap_err(),
NameServiceError::NotFound {
name_id: expected_id + 1
}
);
// Remove as correct owner succeeds
let msg = ExecuteMsg::delete_id(expected_id);
let res = execute(deps.as_mut(), mock_env(), info_owner, msg).unwrap();
assert_eq!(
get_attribute(&res, "delete_id", "name_id"),
expected_id.to_string()
);
assert_names(deps.as_ref(), &[]);
assert_not_found(deps.as_ref(), expected_id);
}
}
@@ -0,0 +1,162 @@
use crate::{
constants::{MAX_NUMBER_OF_NAMES_FOR_ADDRESS, MAX_NUMBER_OF_NAMES_PER_OWNER},
error::{NameServiceError, Result},
state,
};
use cosmwasm_std::{Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Response, Uint128};
use nym_name_service_common::{
events::{
new_delete_id_event, new_delete_name_event, new_register_event,
new_update_deposit_required_event,
},
Address, NameId, NymName, RegisteredName,
};
use super::query;
fn ensure_correct_deposit(will_deposit: Uint128, deposit_required: Uint128) -> Result<()> {
match will_deposit.cmp(&deposit_required) {
std::cmp::Ordering::Less => Err(NameServiceError::InsufficientDeposit {
funds: will_deposit,
deposit_required,
}),
std::cmp::Ordering::Equal => Ok(()),
std::cmp::Ordering::Greater => Err(NameServiceError::TooLargeDeposit {
funds: will_deposit,
deposit_required,
}),
}
}
fn ensure_max_names_per_owner(deps: Deps, owner: Addr) -> Result<()> {
let current_entries = query::query_owner(deps, owner.to_string())?;
if current_entries.names.len() < MAX_NUMBER_OF_NAMES_PER_OWNER as usize {
Ok(())
} else {
Err(NameServiceError::ReachedMaxNamesForOwner {
max_names: MAX_NUMBER_OF_NAMES_PER_OWNER,
owner,
})
}
}
fn ensure_max_names_per_address(deps: Deps, address: Address) -> Result<()> {
let current_entries = query::query_address(deps, address.clone())?;
if current_entries.names.len() < MAX_NUMBER_OF_NAMES_FOR_ADDRESS as usize {
Ok(())
} else {
Err(NameServiceError::ReachedMaxNamesForAddress {
max_names: MAX_NUMBER_OF_NAMES_FOR_ADDRESS,
address,
})
}
}
fn ensure_name_exists(deps: Deps, name_id: NameId) -> Result<()> {
if state::names::has_name_id(deps.storage, name_id) {
Ok(())
} else {
Err(NameServiceError::NotFound { name_id })
}
}
fn ensure_name_not_exists(deps: Deps, name: &NymName) -> Result<()> {
if state::names::has_name(deps.storage, name) {
println!("name already exists");
Err(NameServiceError::NameAlreadyRegistered { name: name.clone() })
} else {
Ok(())
}
}
fn ensure_sender_authorized(info: MessageInfo, names: &RegisteredName) -> Result<()> {
if info.sender == names.owner {
Ok(())
} else {
Err(NameServiceError::Unauthorized {
sender: info.sender,
})
}
}
fn return_deposit(name_to_delete: &RegisteredName) -> BankMsg {
BankMsg::Send {
to_address: name_to_delete.owner.to_string(),
amount: vec![name_to_delete.deposit.clone()],
}
}
/// Register a new name. It will be assigned a new name id.
pub fn register(
deps: DepsMut,
env: Env,
info: MessageInfo,
name: NymName,
address: Address,
) -> Result<Response> {
ensure_name_not_exists(deps.as_ref(), &name)?;
ensure_max_names_per_owner(deps.as_ref(), info.sender.clone())?;
ensure_max_names_per_address(deps.as_ref(), address.clone())?;
let deposit_required = state::deposit_required(deps.storage)?;
let denom = deposit_required.denom.clone();
let will_deposit = cw_utils::must_pay(&info, &denom)
.map_err(|err| NameServiceError::DepositRequired { source: err })?;
ensure_correct_deposit(will_deposit, deposit_required.amount)?;
let new_name = RegisteredName {
address,
name,
owner: info.sender,
block_height: env.block.height,
deposit: Coin::new(will_deposit.u128(), denom),
};
let name_id = state::names::save(deps.storage, &new_name)?;
Ok(Response::new().add_event(new_register_event(name_id, new_name)))
}
/// Delete an exsisting name.
pub fn delete_id(deps: DepsMut, info: MessageInfo, name_id: NameId) -> Result<Response> {
ensure_name_exists(deps.as_ref(), name_id)?;
let name_to_delete = state::names::load_id(deps.storage, name_id)?;
ensure_sender_authorized(info, &name_to_delete)?;
state::names::remove_id(deps.storage, name_id)?;
let return_deposit_msg = return_deposit(&name_to_delete);
Ok(Response::new()
.add_message(return_deposit_msg)
.add_event(new_delete_id_event(name_id, name_to_delete)))
}
/// Delete an existing name by name.
pub(crate) fn delete_name(deps: DepsMut, info: MessageInfo, name: NymName) -> Result<Response> {
let name_to_delete = query::query_name(deps.as_ref(), name)?;
ensure_sender_authorized(info, &name_to_delete.name)?;
state::names::remove_id(deps.storage, name_to_delete.name_id)?;
let return_deposit_msg = return_deposit(&name_to_delete.name);
Ok(Response::new()
.add_message(return_deposit_msg)
.add_event(new_delete_name_event(
name_to_delete.name_id,
name_to_delete.name,
)))
}
/// Update the deposit required to register new names
pub(crate) fn update_deposit_required(
deps: DepsMut,
info: MessageInfo,
deposit_required: Coin,
) -> Result<Response> {
state::assert_admin(deps.as_ref(), &info.sender)?;
let mut config = state::load_config(deps.storage)?;
config.deposit_required = deposit_required.clone();
state::save_config(deps.storage, &config)?;
Ok(Response::new().add_event(new_update_deposit_required_event(deposit_required)))
}
@@ -0,0 +1,68 @@
use cosmwasm_std::Deps;
use nym_contracts_common::ContractBuildInformation;
use nym_name_service_common::{
response::{ConfigResponse, NamesListResponse, PagedNamesListResponse},
Address, NameEntry, NameId, NymName,
};
use crate::{
error::Result,
state::{self, names::PagedLoad},
};
pub fn query_id(deps: Deps, name_id: NameId) -> Result<NameEntry> {
let name = state::names::load_id(deps.storage, name_id)?;
Ok(NameEntry { name_id, name })
}
pub fn query_owner(deps: Deps, owner: String) -> Result<NamesListResponse> {
let owner = deps.api.addr_validate(&owner)?;
let names = state::names::load_owner(deps.storage, owner)?;
Ok(NamesListResponse::new(names))
}
pub fn query_address(deps: Deps, address: Address) -> Result<NamesListResponse> {
let names = state::names::load_address(deps.storage, &address)?;
Ok(NamesListResponse::new(names))
}
pub fn query_name(deps: Deps, name: NymName) -> Result<NameEntry> {
state::names::load_name_entry(deps.storage, &name)
.map(|(name_id, name)| NameEntry::new(name_id, name))
}
pub fn query_all_paged(
deps: Deps,
limit: Option<u32>,
start_after: Option<NameId>,
) -> Result<PagedNamesListResponse> {
let PagedLoad {
names,
limit,
start_next_after,
} = state::names::load_all_paged(deps.storage, limit, start_after)?;
Ok(PagedNamesListResponse::new(names, limit, start_next_after))
}
pub fn query_config(deps: Deps) -> Result<ConfigResponse> {
let config = state::load_config(deps.storage)?;
Ok(config.into())
}
pub fn query_contract_version() -> ContractBuildInformation {
// as per docs
// env! macro will expand to the value of the named environment variable at
// compile time, yielding an expression of type `&'static str`
ContractBuildInformation {
build_timestamp: env!("VERGEN_BUILD_TIMESTAMP").to_string(),
build_version: env!("VERGEN_BUILD_SEMVER").to_string(),
commit_sha: option_env!("VERGEN_GIT_SHA").unwrap_or("NONE").to_string(),
commit_timestamp: option_env!("VERGEN_GIT_COMMIT_TIMESTAMP")
.unwrap_or("NONE")
.to_string(),
commit_branch: option_env!("VERGEN_GIT_BRANCH")
.unwrap_or("NONE")
.to_string(),
rustc_version: env!("VERGEN_RUSTC_SEMVER").to_string(),
}
}
+57
View File
@@ -0,0 +1,57 @@
use cosmwasm_std::{Addr, StdError};
use cw_controllers::AdminError;
use nym_name_service_common::{Address, NameId, NymName};
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum NameServiceError {
#[error("{0}")]
Std(#[from] StdError),
#[error("{0}")]
AdminError(#[from] AdminError),
#[error("name id entry not found: {name_id}")]
NotFound { name_id: NameId },
#[error("name entry not found: {name}")]
NameNotFound { name: NymName },
#[error("{sender} is not registrator of name")]
Unauthorized { sender: Addr },
#[error("deposit required to register a name")]
DepositRequired { source: cw_utils::PaymentError },
#[error("insufficiant deposit: {funds}, required: {deposit_required}")]
InsufficientDeposit {
funds: cosmwasm_std::Uint128,
deposit_required: cosmwasm_std::Uint128,
},
#[error("deposit too large: {funds}, required: {deposit_required}")]
TooLargeDeposit {
funds: cosmwasm_std::Uint128,
deposit_required: cosmwasm_std::Uint128,
},
#[error("reached the max number of names ({max_names}) for owner {owner}")]
ReachedMaxNamesForOwner { max_names: u32, owner: Addr },
#[error("reached the max number of names ({max_names}) for address {0}", address.to_string())]
ReachedMaxNamesForAddress { max_names: u32, address: Address },
#[error("failed to parse {value} into a valid SemVer version: {error_message}")]
SemVerFailure {
value: String,
error_message: String,
},
#[error("duplicate entries detected for name: {name}")]
DuplicateNames { name: NymName },
#[error("name already registered: {name}")]
NameAlreadyRegistered { name: NymName },
}
pub(crate) type Result<T, E = NameServiceError> = std::result::Result<T, E>;
@@ -0,0 +1,445 @@
//! Integration tests using cw-multi-test.
use cosmwasm_std::Addr;
use nym_name_service_common::{
response::{ConfigResponse, PagedNamesListResponse},
Address, NameEntry, NymName, RegisteredName,
};
use crate::{
constants::NAME_DEFAULT_RETRIEVAL_LIMIT,
error::NameServiceError,
test_helpers::{fixture::name_entry, helpers::nyms, test_setup::TestSetup},
};
#[test]
fn instantiate_contract() {
TestSetup::new();
}
#[test]
fn query_config() {
assert_eq!(
TestSetup::new().query_config(),
ConfigResponse {
deposit_required: nyms(100),
}
);
}
#[test]
fn register_and_query_name() {
let mut setup = TestSetup::new();
assert_eq!(
setup.query_all(),
PagedNamesListResponse {
names: vec![],
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: None,
}
);
// Register a first name
let owner = Addr::unchecked("owner");
let name = NymName::new("steves-server").unwrap();
let nym_address = Address::new("nym-address");
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&owner), nyms(250));
setup.register(name.clone(), nym_address.clone(), owner.clone());
// Deposit is deposited to contract and deducted from owners's balance
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance(&owner), nyms(150));
// We can query the full name list
assert_eq!(
setup.query_all(),
PagedNamesListResponse {
names: vec![NameEntry {
name_id: 1,
name: RegisteredName {
address: nym_address.clone(),
name: name.clone(),
owner: owner.clone(),
block_height: 12345,
deposit: nyms(100),
},
}],
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(1),
}
);
// ... and we can query by id
assert_eq!(
setup.query_id(1),
NameEntry {
name_id: 1,
name: RegisteredName {
address: nym_address.clone(),
name: name.clone(),
owner: owner.clone(),
block_height: 12345,
deposit: nyms(100),
},
}
);
// Register a second name
let owner2 = Addr::unchecked("owner2");
let name2 = NymName::new("another_server").unwrap();
let nym_address2 = Address::new("nymAddress2");
setup.register(name2.clone(), nym_address2.clone(), owner2.clone());
assert_eq!(setup.contract_balance(), nyms(200));
assert_eq!(
setup.query_all(),
PagedNamesListResponse {
names: vec![
name_entry(1, name, nym_address, owner),
name_entry(2, name2, nym_address2, owner2)
],
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(2),
}
);
}
#[test]
fn cant_register_a_name_without_funds() {
let mut setup = TestSetup::new();
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance("owner"), nyms(250));
setup.register(
NymName::new("my_name").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance("owner"), nyms(150));
setup.register(
NymName::new("my_name2").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
);
assert_eq!(setup.contract_balance(), nyms(200));
assert_eq!(setup.balance("owner"), nyms(50));
let res = setup
.try_register(
NymName::new("my_name3").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
)
.unwrap_err();
assert_eq!(
res.downcast::<cosmwasm_std::StdError>().unwrap(),
cosmwasm_std::StdError::Overflow {
source: cosmwasm_std::OverflowError::new(
cosmwasm_std::OverflowOperation::Sub,
"50",
"100"
)
}
);
}
#[test]
fn delete_name() {
let mut setup = TestSetup::new();
setup.register(
NymName::new("my_name").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance("owner"), nyms(150));
assert!(!setup.query_all().names.is_empty());
setup.delete(1, Addr::unchecked("owner"));
// Deleting the name returns the deposit to the owner
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance("owner"), nyms(250));
assert!(setup.query_all().names.is_empty());
}
#[test]
fn only_owner_can_delete_name() {
let mut setup = TestSetup::new();
assert_eq!(setup.contract_balance(), nyms(0));
setup.register(
NymName::new("name").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert!(!setup.query_all().names.is_empty());
let delete_resp = setup
.try_delete(1, Addr::unchecked("not_owner"))
.unwrap_err();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(
delete_resp.downcast::<NameServiceError>().unwrap(),
NameServiceError::Unauthorized {
sender: Addr::unchecked("not_owner")
}
);
}
#[test]
fn cant_delete_name_that_does_not_exist() {
let mut setup = TestSetup::new();
setup.register(
NymName::new("foo").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert!(!setup.query_all().names.is_empty());
let delete_resp = setup.try_delete(0, Addr::unchecked("owner")).unwrap_err();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(
delete_resp.downcast::<NameServiceError>().unwrap(),
NameServiceError::NotFound { name_id: 0 }
);
let delete_resp = setup.try_delete(2, Addr::unchecked("owner")).unwrap_err();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(
delete_resp.downcast::<NameServiceError>().unwrap(),
NameServiceError::NotFound { name_id: 2 }
);
assert!(!setup.query_all().names.is_empty());
setup.delete(1, Addr::unchecked("owner"));
assert_eq!(setup.contract_balance(), nyms(0));
assert!(setup.query_all().names.is_empty());
}
#[test]
fn cant_register_the_same_name_multiple_times() {
let mut setup = TestSetup::new();
setup.register(
NymName::new("name").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
);
let resp = setup
.try_register(
NymName::new("name").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
)
.unwrap_err();
assert_eq!(
resp.downcast::<NameServiceError>().unwrap(),
NameServiceError::NameAlreadyRegistered {
name: NymName::new("name").unwrap()
}
);
}
#[test]
fn can_register_multiple_names_for_the_same_nym_address() {
let mut setup = TestSetup::new();
let name1 = NymName::new("name1").unwrap();
let name2 = NymName::new("name2").unwrap();
let address = Address::new("nymaddress");
let owner = Addr::unchecked("owner");
setup.register(name1.clone(), address.clone(), owner.clone());
setup.register(name2.clone(), address.clone(), owner.clone());
assert_eq!(
setup.query_all().names,
vec![
name_entry(1, name1, address.clone(), owner.clone()),
name_entry(2, name2, address, owner)
],
);
}
#[test]
fn register_multiple_names_and_deleting_by_name() {
let mut setup = TestSetup::new();
let owner1 = Addr::unchecked("wealthy_owner_1");
let owner2 = Addr::unchecked("wealthy_owner_2");
let nym_address1 = Address::new("nymaddress1");
let nym_address2 = Address::new("nymaddress2");
let name1 = NymName::new("name1").unwrap();
let name2 = NymName::new("name2").unwrap();
let name3 = NymName::new("name3").unwrap();
let name4 = NymName::new("name4").unwrap();
let name5 = NymName::new("name5").unwrap();
// We register the same address three times, but with different owners
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&owner1), nyms(1000));
setup.register(name1.clone(), nym_address1.clone(), owner1.clone());
setup.register(name2.clone(), nym_address1.clone(), owner1.clone());
setup.register(name3.clone(), nym_address2.clone(), owner1.clone());
setup.register(name4.clone(), nym_address1.clone(), owner2.clone());
setup.register(name5.clone(), nym_address2.clone(), owner2.clone());
assert_eq!(setup.contract_balance(), nyms(500));
assert_eq!(setup.balance(&owner1), nyms(700));
assert_eq!(
setup.query_all(),
PagedNamesListResponse {
names: vec![
name_entry(1, name1.clone(), nym_address1.clone(), owner1.clone()),
name_entry(2, name2.clone(), nym_address1.clone(), owner1.clone()),
name_entry(3, name3.clone(), nym_address2.clone(), owner1.clone()),
name_entry(4, name4.clone(), nym_address1.clone(), owner2.clone()),
name_entry(5, name5.clone(), nym_address2.clone(), owner2.clone()),
],
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
setup.delete_name(name1, owner1.clone());
assert_eq!(setup.contract_balance(), nyms(400));
assert_eq!(setup.balance(&owner1), nyms(800));
assert_eq!(
setup.query_all(),
PagedNamesListResponse {
names: vec![
name_entry(2, name2, nym_address1.clone(), owner1.clone()),
name_entry(3, name3, nym_address2.clone(), owner1),
name_entry(4, name4, nym_address1, owner2.clone()),
name_entry(5, name5, nym_address2, owner2),
],
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
}
#[test]
fn check_paging() {
let mut setup = TestSetup::new();
let owner1 = Addr::unchecked("wealthy_owner_1");
let owner2 = Addr::unchecked("wealthy_owner_2");
let nym_address1 = Address::new("nymAddress1");
let nym_address2 = Address::new("nymAddress2");
let name1 = NymName::new("name1").unwrap();
let name2 = NymName::new("name2").unwrap();
let name3 = NymName::new("name3").unwrap();
let name4 = NymName::new("name4").unwrap();
let name5 = NymName::new("name5").unwrap();
// We register the same address three times, but with different owners
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&owner1), nyms(1000));
setup.register(name1.clone(), nym_address1.clone(), owner1.clone());
setup.register(name2.clone(), nym_address1.clone(), owner1.clone());
setup.register(name3.clone(), nym_address2.clone(), owner1.clone());
setup.register(name4.clone(), nym_address1.clone(), owner2.clone());
setup.register(name5.clone(), nym_address2.clone(), owner2.clone());
assert_eq!(setup.contract_balance(), nyms(500));
assert_eq!(setup.balance(&owner1), nyms(700));
assert_eq!(
setup.query_all(),
PagedNamesListResponse {
names: vec![
name_entry(1, name1.clone(), nym_address1.clone(), owner1.clone()),
name_entry(2, name2.clone(), nym_address1.clone(), owner1.clone()),
name_entry(3, name3.clone(), nym_address2.clone(), owner1.clone()),
name_entry(4, name4.clone(), nym_address1.clone(), owner2.clone()),
name_entry(5, name5, nym_address2.clone(), owner2.clone()),
],
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
setup.delete_name(name1, owner1.clone());
assert_eq!(
setup.query_all_with_limit(Some(2), None),
PagedNamesListResponse {
names: vec![
name_entry(2, name2, nym_address1.clone(), owner1.clone()),
name_entry(3, name3.clone(), nym_address2.clone(), owner1.clone()),
],
per_page: 2,
start_next_after: Some(3),
}
);
assert_eq!(
setup.query_all_with_limit(Some(2), Some(2)),
PagedNamesListResponse {
names: vec![
name_entry(3, name3, nym_address2, owner1),
name_entry(4, name4, nym_address1, owner2),
],
per_page: 2,
start_next_after: Some(4),
}
);
}
#[test]
fn name_id_is_not_resused_when_deleting_and_then_adding_a_new_names() {
let mut setup = TestSetup::new();
setup.register(
NymName::new("myname1").unwrap(),
Address::new("nymAddress1"),
Addr::unchecked("owner1"),
);
setup.register(
NymName::new("myname2").unwrap(),
Address::new("nymAddress2"),
Addr::unchecked("owner2"),
);
setup.register(
NymName::new("myname3").unwrap(),
Address::new("nymAddress3"),
Addr::unchecked("owner3"),
);
setup.delete(1, Addr::unchecked("owner1"));
setup.delete(3, Addr::unchecked("owner3"));
assert_eq!(
setup.query_all().names,
vec![name_entry(
2,
NymName::new("myname2").unwrap(),
Address::new("nymAddress2"),
Addr::unchecked("owner2")
)]
);
setup.register(
NymName::new("myname4").unwrap(),
Address::new("nymAddress4"),
Addr::unchecked("owner4"),
);
assert_eq!(
setup.query_all().names,
vec![
name_entry(
2,
NymName::new("myname2").unwrap(),
Address::new("nymAddress2"),
Addr::unchecked("owner2")
),
name_entry(
4,
NymName::new("myname4").unwrap(),
Address::new("nymAddress4"),
Addr::unchecked("owner4")
)
]
);
}
+57
View File
@@ -0,0 +1,57 @@
//! The nym name service contract is for users to register names for the nym addresses.
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
use crate::error::Result;
use nym_name_service_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response};
use error::NameServiceError;
mod contract;
mod error;
mod state;
pub mod constants;
#[cfg(test)]
mod integration_tests;
#[cfg(test)]
mod test_helpers;
/// Contract entry point for instantiation.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response> {
contract::instantiate(deps, env, info, msg)
}
/// Contract entry point for migrations.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, NameServiceError> {
contract::migrate(deps, env, msg)
}
/// Contract entry point for execution
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, NameServiceError> {
contract::execute(deps, env, info, msg)
}
/// Contract entry point for queries
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<Binary> {
contract::query(deps, env, msg)
}
+14
View File
@@ -0,0 +1,14 @@
use cosmwasm_std::{Addr, Deps, DepsMut};
use cw_controllers::Admin;
use crate::{constants::ADMIN_KEY, error::Result};
const ADMIN: Admin = Admin::new(ADMIN_KEY);
pub(crate) fn set_admin(deps: DepsMut<'_>, admin: Addr) -> Result<()> {
Ok(ADMIN.set(deps, Some(admin))?)
}
pub(crate) fn assert_admin(deps: Deps, sender: &Addr) -> Result<()> {
Ok(ADMIN.assert_admin(deps, sender)?)
}
@@ -0,0 +1,34 @@
use cosmwasm_std::{Coin, Storage};
use cw_storage_plus::Item;
use nym_name_service_common::response::ConfigResponse;
use serde::{Deserialize, Serialize};
use crate::{constants::CONFIG_KEY, error::Result};
const CONFIG: Item<Config> = Item::new(CONFIG_KEY);
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub(crate) struct Config {
pub deposit_required: Coin,
}
impl From<Config> for ConfigResponse {
fn from(config: Config) -> Self {
ConfigResponse {
deposit_required: config.deposit_required,
}
}
}
pub(crate) fn save_config(store: &mut dyn Storage, config: &Config) -> Result<()> {
Ok(CONFIG.save(store, config)?)
}
pub(crate) fn load_config(store: &dyn Storage) -> Result<Config> {
Ok(CONFIG.load(store)?)
}
/// Return the deposit required to register a name.
pub(crate) fn deposit_required(store: &dyn Storage) -> Result<Coin> {
Ok(CONFIG.load(store).map(|config| config.deposit_required)?)
}
+8
View File
@@ -0,0 +1,8 @@
pub mod admin;
pub mod config;
pub mod name_id_counter;
pub mod names;
pub(crate) use admin::{assert_admin, set_admin};
pub(crate) use config::{deposit_required, load_config, save_config, Config};
pub(crate) use name_id_counter::next_name_id_counter;
@@ -0,0 +1,83 @@
use cosmwasm_std::Storage;
use cw_storage_plus::Item;
use nym_name_service_common::NameId;
use crate::{constants::NAME_ID_COUNTER_KEY, error::Result};
const NAME_ID_COUNTER: Item<NameId> = Item::new(NAME_ID_COUNTER_KEY);
/// Generate the next name id, store it and return it
pub(crate) fn next_name_id_counter(store: &mut dyn Storage) -> Result<NameId> {
// The first id is 1.
let id = NAME_ID_COUNTER.may_load(store)?.unwrap_or_default() + 1;
NAME_ID_COUNTER.save(store, &id)?;
Ok(id)
}
#[cfg(test)]
mod tests {
use nym_name_service_common::NameEntry;
use crate::test_helpers::{
assert::assert_names,
fixture::name_fixture_name,
helpers::{delete_name_id, instantiate_test_contract, register_name},
};
#[test]
fn get_next_name_id() {
let mut deps = instantiate_test_contract();
assert_eq!(register_name(deps.as_mut(), &name_fixture_name("foo")), 1);
assert_names(
deps.as_ref(),
&[NameEntry::new(1, name_fixture_name("foo"))],
);
assert_eq!(register_name(deps.as_mut(), &name_fixture_name("bar")), 2);
assert_eq!(register_name(deps.as_mut(), &name_fixture_name("baz")), 3);
assert_names(
deps.as_ref(),
&[
NameEntry::new(1, name_fixture_name("foo")),
NameEntry::new(2, name_fixture_name("bar")),
NameEntry::new(3, name_fixture_name("baz")),
],
);
}
#[test]
fn deleted_name_id_is_not_reused() {
let mut deps = instantiate_test_contract();
// Register two names
assert_eq!(register_name(deps.as_mut(), &name_fixture_name("one")), 1);
assert_eq!(register_name(deps.as_mut(), &name_fixture_name("two")), 2);
assert_names(
deps.as_ref(),
&[
NameEntry::new(1, name_fixture_name("one")),
NameEntry::new(2, name_fixture_name("two")),
],
);
// Delete the last entry
delete_name_id(deps.as_mut(), 2, "steve");
assert_names(
deps.as_ref(),
&[NameEntry::new(1, name_fixture_name("one"))],
);
// Create a third entry. The index should not reuse the previous entry that we just
// deleted.
assert_eq!(register_name(deps.as_mut(), &name_fixture_name("two")), 3);
assert_names(
deps.as_ref(),
&[
NameEntry::new(1, name_fixture_name("one")),
NameEntry::new(3, name_fixture_name("two")),
],
);
}
}
+697
View File
@@ -0,0 +1,697 @@
use cosmwasm_std::{Addr, Order, StdError, StdResult, Storage};
use cw_storage_plus::{Bound, Index, IndexList, IndexedMap, MultiIndex, UniqueIndex};
use nym_name_service_common::{Address, NameId, NymName, RegisteredName};
use crate::{
constants::{
MAX_NUMBER_OF_NAMES_FOR_ADDRESS, MAX_NUMBER_OF_NAMES_PER_OWNER,
NAMES_ADDRESS_IDX_NAMESPACE, NAMES_NAME_IDX_NAMESPACE, NAMES_OWNER_IDX_NAMESPACE,
NAMES_PK_NAMESPACE, NAME_DEFAULT_RETRIEVAL_LIMIT, NAME_MAX_RETRIEVAL_LIMIT,
},
error::{NameServiceError, Result},
};
struct NameIndex<'a> {
// A name can only point to a single address
pub(crate) name: UniqueIndex<'a, String, RegisteredName, NameId>,
// An addresses can be pointed to by multiple names.
pub(crate) address: MultiIndex<'a, String, RegisteredName, NameId>,
pub(crate) owner: MultiIndex<'a, Addr, RegisteredName, NameId>,
}
impl<'a> IndexList<RegisteredName> for NameIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<RegisteredName>> + '_> {
let v: Vec<&dyn Index<RegisteredName>> = vec![&self.name, &self.address, &self.owner];
Box::new(v.into_iter())
}
}
fn names<'a>() -> IndexedMap<'a, NameId, RegisteredName, NameIndex<'a>> {
let indexes = NameIndex {
name: UniqueIndex::new(|d| d.name.to_string(), NAMES_NAME_IDX_NAMESPACE),
address: MultiIndex::new(
|d| d.address.to_string(),
NAMES_PK_NAMESPACE,
NAMES_ADDRESS_IDX_NAMESPACE,
),
owner: MultiIndex::new(
|d| d.owner.clone(),
NAMES_PK_NAMESPACE,
NAMES_OWNER_IDX_NAMESPACE,
),
};
IndexedMap::new(NAMES_PK_NAMESPACE, indexes)
}
pub fn save(store: &mut dyn Storage, new_name: &RegisteredName) -> Result<NameId> {
let name_id = super::next_name_id_counter(store)?;
names().save(store, name_id, new_name)?;
Ok(name_id)
}
#[cfg(test)]
pub fn save_all(state: &mut dyn Storage, names: &[RegisteredName]) -> Result<Vec<NameId>> {
let mut ids = vec![];
for name in names {
ids.push(save(state, name)?);
}
Ok(ids)
}
pub fn has_name_id(store: &dyn Storage, name_id: NameId) -> bool {
names().has(store, name_id)
}
pub fn has_name(store: &dyn Storage, name: &NymName) -> bool {
load_name(store, name).is_ok()
}
// Get the (key, name) entry for a given name
pub fn load_name_entry(store: &dyn Storage, name: &NymName) -> Result<(NameId, RegisteredName)> {
names()
.idx
.name
.range(store, None, None, Order::Ascending)
.find(|entry| {
if let Ok(entry) = entry {
&entry.1.name == name
} else {
false
}
})
.ok_or(NameServiceError::NameNotFound { name: name.clone() })?
.map_err(NameServiceError::from)
}
pub fn load_id(store: &dyn Storage, name_id: NameId) -> Result<RegisteredName> {
names().load(store, name_id).map_err(|err| match err {
StdError::NotFound { .. } => NameServiceError::NotFound { name_id },
err => err.into(),
})
}
pub fn load_name(store: &dyn Storage, name: &NymName) -> Result<RegisteredName> {
names()
.idx
.name
.item(store, name.to_string())?
.map(|record| record.1)
.ok_or(NameServiceError::NameNotFound { name: name.clone() })
}
pub fn load_address(
store: &dyn Storage,
address: &Address,
) -> Result<Vec<(NameId, RegisteredName)>> {
names()
.idx
.address
.prefix(address.to_string())
.range(store, None, None, Order::Ascending)
.take(MAX_NUMBER_OF_NAMES_FOR_ADDRESS as usize)
.collect::<StdResult<Vec<_>>>()
.map_err(NameServiceError::from)
}
pub fn load_owner(store: &dyn Storage, owner: Addr) -> Result<Vec<(NameId, RegisteredName)>> {
names()
.idx
.owner
.prefix(owner)
.range(store, None, None, Order::Ascending)
.take(MAX_NUMBER_OF_NAMES_PER_OWNER as usize)
.collect::<StdResult<Vec<_>>>()
.map_err(NameServiceError::from)
}
pub fn remove_id(store: &mut dyn Storage, name_id: NameId) -> Result<()> {
Ok(names().remove(store, name_id)?)
}
#[cfg(test)]
pub fn remove_name(store: &mut dyn Storage, name: NymName) -> Result<NameId> {
let name_info = load_name_entry(store, &name)?;
remove_id(store, name_info.0)?;
Ok(name_info.0)
}
#[derive(Debug, PartialEq)]
pub struct PagedLoad {
pub names: Vec<(NameId, RegisteredName)>,
pub limit: usize,
pub start_next_after: Option<NameId>,
}
pub fn load_all_paged(
store: &dyn Storage,
limit: Option<u32>,
start_after: Option<NameId>,
) -> Result<PagedLoad> {
let limit = limit
.unwrap_or(NAME_DEFAULT_RETRIEVAL_LIMIT)
.min(NAME_MAX_RETRIEVAL_LIMIT) as usize;
let start = start_after.map(Bound::exclusive);
let names = names()
.range(store, start, None, Order::Ascending)
.take(limit)
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = names.last().map(|name| name.0);
Ok(PagedLoad {
names,
limit,
start_next_after,
})
}
#[cfg(test)]
mod tests {
use std::iter::zip;
use cosmwasm_std::{
testing::{MockApi, MockQuerier},
MemoryStorage, OwnedDeps,
};
use rstest::rstest;
use crate::{
error::NameServiceError,
test_helpers::{
fixture::{name_fixture, name_fixture_full},
helpers::instantiate_test_contract,
},
};
use super::*;
type TestDeps = OwnedDeps<MemoryStorage, MockApi, MockQuerier>;
#[rstest::fixture]
fn deps() -> TestDeps {
instantiate_test_contract()
}
#[rstest::fixture]
fn uniq_names() -> Vec<RegisteredName> {
vec![
name_fixture_full("one", "address_one", "owner_one"),
name_fixture_full("two", "address_two", "owner_two"),
name_fixture_full("three", "address_three", "owner_three"),
]
}
#[rstest::fixture]
fn overlapping_addresses() -> Vec<RegisteredName> {
vec![
name_fixture_full("one", "address_one", "owner_one"),
name_fixture_full("two", "address_two", "owner_two"),
name_fixture_full("three", "address_two", "owner_three"),
]
}
#[rstest::fixture]
fn overlapping_owners() -> Vec<RegisteredName> {
vec![
name_fixture_full("one", "address_one", "owner_one"),
name_fixture_full("two", "address_two", "owner_two"),
name_fixture_full("three", "address_three", "owner_two"),
]
}
fn assert_not_registered(store: &dyn Storage, names: Vec<RegisteredName>, ids: Vec<NameId>) {
let names: Vec<(NameId, RegisteredName)> = zip(ids, names).collect();
let loaded = load_all_paged(store, None, None).unwrap();
for (id, name) in &names {
assert!(!has_name_id(store, *id));
assert!(!has_name(store, &name.name));
assert!(!loaded.names.iter().any(|(i, _)| i == id));
assert!(!loaded.names.iter().any(|(_, n)| n == name));
}
}
fn assert_registered(store: &dyn Storage, names: Vec<RegisteredName>, ids: Vec<NameId>) {
assert!(names.len() == ids.len());
let names: Vec<(NameId, RegisteredName)> = zip(ids, names).collect();
let loaded = load_all_paged(store, None, None).unwrap();
for (id, name) in &names {
assert!(has_name_id(store, *id));
assert!(has_name(store, &name.name));
assert!(loaded.names.iter().filter(|(i, _)| i == id).count() == 1);
assert!(loaded.names.iter().filter(|(_, n)| n == name).count() == 1);
}
}
fn assert_only_these_registered(
store: &dyn Storage,
names: Vec<RegisteredName>,
ids: Vec<NameId>,
) {
let last_id = *ids.last().unwrap();
let names: Vec<(NameId, RegisteredName)> = zip(ids, names).collect();
for (id, name) in &names {
assert!(has_name_id(store, *id));
assert!(has_name(store, &name.name));
}
assert_eq!(
load_all_paged(store, None, None).unwrap(),
PagedLoad {
names,
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(last_id),
}
)
}
#[rstest]
fn single_basic_save_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &name_fixture()).unwrap();
}
#[rstest]
fn save_same_name_twice_fails(mut deps: TestDeps) {
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert!(matches!(
save(deps.as_mut().storage, &name_fixture()).unwrap_err(),
NameServiceError::Std(StdError::GenericErr { .. })
));
}
#[rstest]
fn has_name_works(mut deps: TestDeps) {
assert!(!has_name(&deps.storage, &name_fixture().name));
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert!(has_name(&deps.storage, &name_fixture().name));
}
#[rstest]
fn has_name_id_works(mut deps: TestDeps) {
assert!(!has_name_id(&deps.storage, 1));
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert!(has_name_id(&deps.storage, 1));
}
#[rstest]
fn has_name_id_with_incorrect_id_fails(mut deps: TestDeps) {
assert!(!has_name_id(&deps.storage, 2));
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert!(!has_name_id(&deps.storage, 2));
}
#[rstest]
fn load_name_entry_works(mut deps: TestDeps) {
assert_eq!(
load_name_entry(deps.as_ref().storage, &name_fixture().name).unwrap_err(),
NameServiceError::NameNotFound {
name: name_fixture().name
}
);
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert_eq!(
load_name_entry(deps.as_ref().storage, &name_fixture().name).unwrap(),
(1, name_fixture())
);
}
#[rstest]
fn load_id_works(mut deps: TestDeps) {
assert_eq!(
load_id(deps.as_ref().storage, 1).unwrap_err(),
NameServiceError::NotFound { name_id: 1 }
);
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert_eq!(load_id(deps.as_ref().storage, 1).unwrap(), name_fixture(),);
}
#[rstest]
fn load_name_works(mut deps: TestDeps) {
assert_eq!(
load_name(deps.as_ref().storage, &name_fixture().name).unwrap_err(),
NameServiceError::NameNotFound {
name: name_fixture().name
}
);
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert_eq!(
load_name(deps.as_ref().storage, &name_fixture().name).unwrap(),
name_fixture(),
);
}
#[rstest]
fn load_address_works(mut deps: TestDeps) {
assert_eq!(
load_address(deps.as_ref().storage, &name_fixture().address).unwrap(),
vec![],
);
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert_eq!(
load_address(deps.as_ref().storage, &name_fixture().address).unwrap(),
vec![(1, name_fixture())],
);
}
#[rstest]
fn load_owner_works(mut deps: TestDeps) {
assert_eq!(
load_owner(deps.as_ref().storage, name_fixture().owner).unwrap(),
vec![],
);
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert_eq!(
load_owner(deps.as_ref().storage, name_fixture().owner).unwrap(),
vec![(1, name_fixture())],
);
}
#[rstest]
fn load_all_paged_works(mut deps: TestDeps, uniq_names: Vec<RegisteredName>) {
assert_eq!(
load_all_paged(&deps.storage, None, None).unwrap(),
PagedLoad {
names: vec![],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: None
}
);
save(deps.as_mut().storage, &uniq_names[0]).unwrap();
assert_eq!(
load_all_paged(&deps.storage, None, None).unwrap(),
PagedLoad {
names: vec![(1, uniq_names[0].clone())],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(1),
}
);
}
#[rstest]
fn remove_id_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert!(has_name_id(&deps.storage, 1));
assert!(has_name(&deps.storage, &name_fixture().name));
remove_id(deps.as_mut().storage, 1).unwrap();
assert!(!has_name_id(&deps.storage, 1));
assert!(!has_name(&deps.storage, &name_fixture().name));
}
#[rstest]
fn remove_name_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert!(has_name_id(&deps.storage, 1));
assert!(has_name(&deps.storage, &name_fixture().name));
remove_name(deps.as_mut().storage, name_fixture().name).unwrap();
assert!(!has_name_id(&deps.storage, 1));
assert!(!has_name(&deps.storage, &name_fixture().name));
}
#[rstest]
fn save_set_of_unique_names_works(mut deps: TestDeps, uniq_names: Vec<RegisteredName>) {
let num = uniq_names.len() as NameId;
let ids = (1..=num).collect::<Vec<NameId>>();
assert_not_registered(&deps.storage, uniq_names.clone(), ids.clone());
let saved_ids = save_all(deps.as_mut().storage, &uniq_names).unwrap();
assert_eq!(saved_ids, ids);
assert_registered(&deps.storage, uniq_names.clone(), ids.clone());
assert_only_these_registered(&deps.storage, uniq_names, ids);
}
#[rstest]
fn save_set_of_unique_names_generates_ids(mut deps: TestDeps, uniq_names: Vec<RegisteredName>) {
let num = uniq_names.len() as NameId;
let ids = save_all(deps.as_mut().storage, &uniq_names).unwrap();
assert_eq!(ids, (1..=num).collect::<Vec<NameId>>());
}
#[rstest]
fn load_name_entry_for_unique_set_works(mut deps: TestDeps, uniq_names: Vec<RegisteredName>) {
save_all(deps.as_mut().storage, &uniq_names).unwrap();
for (id, name) in uniq_names.iter().enumerate() {
assert_eq!(
load_name_entry(deps.as_ref().storage, &name.name).unwrap(),
(id as NameId + 1, name.clone()),
);
}
}
#[rstest]
fn load_name_for_unique_set_works(mut deps: TestDeps, uniq_names: Vec<RegisteredName>) {
save_all(deps.as_mut().storage, &uniq_names).unwrap();
for name in uniq_names {
assert_eq!(
load_name(deps.as_ref().storage, &name.name).unwrap(),
name.clone(),
);
}
}
#[rstest]
fn save_and_remove_name_id_from_set_of_unique_set_works(
mut deps: TestDeps,
uniq_names: Vec<RegisteredName>,
) {
let ids = save_all(deps.as_mut().storage, &uniq_names).unwrap();
remove_id(deps.as_mut().storage, ids[1]).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(
3,
name_fixture_full("three", "address_three", "owner_three")
),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
}
#[rstest]
fn save_and_remove_name_from_set_of_unique_set_works(
mut deps: TestDeps,
uniq_names: Vec<RegisteredName>,
) {
save_all(deps.as_mut().storage, &uniq_names).unwrap();
remove_name(deps.as_mut().storage, uniq_names[1].name.clone()).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(
3,
name_fixture_full("three", "address_three", "owner_three")
),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
}
#[rstest]
fn save_set_of_overlapping_addressed_works(
mut deps: TestDeps,
overlapping_addresses: Vec<RegisteredName>,
) {
let ids = save_all(deps.as_mut().storage, &overlapping_addresses).unwrap();
assert_registered(&deps.storage, overlapping_addresses.clone(), ids.clone());
assert_only_these_registered(&deps.storage, overlapping_addresses, ids);
}
#[rstest]
fn load_address_with_overlapping_addresses_works(
mut deps: TestDeps,
overlapping_addresses: Vec<RegisteredName>,
) {
save_all(deps.as_mut().storage, &overlapping_addresses).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(2, name_fixture_full("two", "address_two", "owner_two")),
(3, name_fixture_full("three", "address_two", "owner_three")),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
assert_eq!(
load_address(deps.as_ref().storage, &Address::new("address_two")).unwrap(),
vec![
(2, name_fixture_full("two", "address_two", "owner_two")),
(3, name_fixture_full("three", "address_two", "owner_three")),
]
);
}
#[rstest]
fn save_and_remove_name_id_from_set_of_overlapping_addresses_works(
mut deps: TestDeps,
overlapping_addresses: Vec<RegisteredName>,
) {
let ids = save_all(deps.as_mut().storage, &overlapping_addresses).unwrap();
remove_id(deps.as_mut().storage, ids[1]).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(3, name_fixture_full("three", "address_two", "owner_three")),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
}
#[rstest]
fn save_and_remove_name_from_set_of_overlapping_addresses_works(
mut deps: TestDeps,
overlapping_addresses: Vec<RegisteredName>,
) {
save_all(deps.as_mut().storage, &overlapping_addresses).unwrap();
remove_name(deps.as_mut().storage, overlapping_addresses[1].name.clone()).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(3, name_fixture_full("three", "address_two", "owner_three")),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
}
#[rstest]
fn save_set_of_overlapping_owners_works(
mut deps: TestDeps,
overlapping_owners: Vec<RegisteredName>,
) {
let ids = save_all(deps.as_mut().storage, &overlapping_owners).unwrap();
assert_registered(&deps.storage, overlapping_owners.clone(), ids.clone());
assert_only_these_registered(&deps.storage, overlapping_owners, ids);
}
#[rstest]
fn load_owner_with_overlapping_owners_works(
mut deps: TestDeps,
overlapping_owners: Vec<RegisteredName>,
) {
save_all(deps.as_mut().storage, &overlapping_owners).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(2, name_fixture_full("two", "address_two", "owner_two")),
(3, name_fixture_full("three", "address_three", "owner_two")),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
assert_eq!(
load_owner(deps.as_ref().storage, Addr::unchecked("owner_two")).unwrap(),
vec![
(2, name_fixture_full("two", "address_two", "owner_two")),
(3, name_fixture_full("three", "address_three", "owner_two")),
]
);
}
#[rstest]
fn save_and_remove_name_id_from_set_of_overlapping_owners_works(
mut deps: TestDeps,
overlapping_owners: Vec<RegisteredName>,
) {
let ids = save_all(deps.as_mut().storage, &overlapping_owners).unwrap();
assert_registered(&deps.storage, overlapping_owners.clone(), ids.clone());
assert_only_these_registered(&deps.storage, overlapping_owners, ids);
remove_id(deps.as_mut().storage, 2).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(3, name_fixture_full("three", "address_three", "owner_two")),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
}
#[rstest]
fn save_and_remove_name_from_set_of_overlapping_owners_works(
mut deps: TestDeps,
overlapping_owners: Vec<RegisteredName>,
) {
let ids = save_all(deps.as_mut().storage, &overlapping_owners).unwrap();
assert_registered(&deps.storage, overlapping_owners.clone(), ids.clone());
assert_only_these_registered(&deps.storage, overlapping_owners.clone(), ids);
remove_name(deps.as_mut().storage, overlapping_owners[1].name.clone()).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(3, name_fixture_full("three", "address_three", "owner_two")),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
}
#[rstest]
fn load_all_paged_with_limit_works(mut deps: TestDeps, uniq_names: Vec<RegisteredName>) {
save_all(deps.as_mut().storage, &uniq_names).unwrap();
assert_eq!(
load_all_paged(&deps.storage, Some(2), None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(2, name_fixture_full("two", "address_two", "owner_two")),
],
limit: 2,
start_next_after: Some(2),
}
);
assert_eq!(
load_all_paged(&deps.storage, Some(1), Some(2)).unwrap(),
PagedLoad {
names: vec![(
3,
name_fixture_full("three", "address_three", "owner_three")
),],
limit: 1,
start_next_after: Some(3),
}
);
assert_eq!(
load_all_paged(&deps.storage, Some(2), Some(2)).unwrap(),
PagedLoad {
names: vec![(
3,
name_fixture_full("three", "address_three", "owner_three")
),],
limit: 2,
start_next_after: Some(3),
}
);
}
#[test]
#[ignore]
fn max_page_limit_is_applied() {
// WIP(JON)
todo!();
}
}
@@ -0,0 +1,65 @@
use cosmwasm_std::{from_binary, testing::mock_env, Addr, Coin, Deps};
use nym_name_service_common::{
msg::QueryMsg,
response::{ConfigResponse, PagedNamesListResponse},
NameEntry, NameId,
};
use crate::{constants::NAME_DEFAULT_RETRIEVAL_LIMIT, error::NameServiceError};
pub fn assert_config(deps: Deps, admin: &Addr, deposit_required: Coin) {
crate::state::assert_admin(deps, admin).unwrap();
let res = crate::contract::query(deps, mock_env(), QueryMsg::Config {}).unwrap();
let config: ConfigResponse = from_binary(&res).unwrap();
assert_eq!(config, ConfigResponse { deposit_required });
}
pub fn assert_names(deps: Deps, expected_names: &[NameEntry]) {
let res = crate::contract::query(deps, mock_env(), QueryMsg::all()).unwrap();
let names: PagedNamesListResponse = from_binary(&res).unwrap();
let start_next_after = expected_names.iter().last().map(|s| s.name_id);
assert_eq!(
names,
PagedNamesListResponse {
names: expected_names.to_vec(),
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after,
}
);
}
pub fn assert_name(deps: Deps, expected_name: &NameEntry) {
let res = crate::contract::query(
deps,
mock_env(),
QueryMsg::NameId {
name_id: expected_name.name_id,
},
)
.unwrap();
let names: NameEntry = from_binary(&res).unwrap();
assert_eq!(&names, expected_name);
}
pub fn assert_empty(deps: Deps) {
let res = crate::contract::query(deps, mock_env(), QueryMsg::all()).unwrap();
let names: PagedNamesListResponse = from_binary(&res).unwrap();
assert!(names.names.is_empty());
}
pub fn assert_not_found(deps: Deps, expected_id: NameId) {
let res = crate::contract::query(
deps,
mock_env(),
QueryMsg::NameId {
name_id: expected_id,
},
)
.unwrap_err();
assert!(matches!(
res,
NameServiceError::NotFound {
name_id: _expected_id
}
));
}
@@ -0,0 +1,29 @@
use cosmwasm_std::Addr;
use nym_name_service_common::{Address, NameEntry, NameId, NymName, RegisteredName};
use super::helpers::nyms;
pub fn name_fixture_full(name: &str, nym_address: &str, owner: &str) -> RegisteredName {
RegisteredName {
name: NymName::new(name).unwrap(),
address: Address::new(nym_address),
owner: Addr::unchecked(owner),
block_height: 12345,
deposit: nyms(100),
}
}
pub fn name_fixture() -> RegisteredName {
name_fixture_full("my-service", "client_id.client_key@gateway_id", "steve")
}
pub fn name_fixture_name(name: &str) -> RegisteredName {
name_fixture_full(name, "client_id.client_key@gateway_id", "steve")
}
pub fn name_entry(name_id: NameId, name: NymName, address: Address, owner: Addr) -> NameEntry {
NameEntry {
name_id,
name: name_fixture_full(name.as_str(), address.as_str(), owner.as_str()),
}
}
@@ -0,0 +1,107 @@
use cosmwasm_std::{
coin, coins,
testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier},
Coin, DepsMut, Event, MemoryStorage, OwnedDeps, Response,
};
use cw_multi_test::AppResponse;
use nym_name_service_common::{
events::{NameEventType, NAME_ID},
msg::{ExecuteMsg, InstantiateMsg},
NameId, NymName, RegisteredName,
};
pub fn nyms(amount: u64) -> Coin {
Coin::new(amount.into(), "unym")
}
pub fn get_event_types(response: &Response, event_type: &str) -> Vec<Event> {
response
.events
.iter()
.filter(|ev| ev.ty == event_type)
.cloned()
.collect()
}
pub fn get_attribute(response: &Response, event_type: &str, key: &str) -> String {
get_event_types(response, event_type)
.first()
.unwrap()
.attributes
.iter()
.find(|attr| attr.key == key)
.unwrap()
.value
.clone()
}
pub fn get_app_event_types(response: &AppResponse, event_type: &str) -> Vec<Event> {
response
.events
.iter()
.filter(|ev| ev.ty == event_type)
.cloned()
.collect()
}
pub fn get_app_attribute(response: &AppResponse, event_type: &str, key: &str) -> String {
get_app_event_types(response, event_type)
.first()
.unwrap()
.attributes
.iter()
.find(|attr| attr.key == key)
.unwrap()
.value
.clone()
}
#[allow(unused)]
pub fn get_app_attributes(response: &AppResponse, event_type: &str, key: &str) -> Vec<String> {
get_app_event_types(response, event_type)
.iter()
.map(|ev| {
ev.attributes
.iter()
.find(|attr| attr.key == key)
.unwrap()
.value
.clone()
})
.collect::<Vec<_>>()
}
pub fn instantiate_test_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
deposit_required: coin(100, "unym"),
};
let env = mock_env();
let info = mock_info("creator", &[]);
let res = crate::instantiate(deps.as_mut(), env, info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
deps
}
pub fn register_name(deps: DepsMut<'_>, name: &RegisteredName) -> NameId {
let msg: ExecuteMsg = name.clone().into();
let info = mock_info(name.owner.as_str(), &coins(100, "unym"));
let res = crate::execute(deps, mock_env(), info, msg).unwrap();
let name_id: NameId = get_attribute(&res, &NameEventType::Register.to_string(), NAME_ID)
.parse()
.unwrap();
name_id
}
pub fn delete_name_id(deps: DepsMut<'_>, name_id: NameId, owner: &str) {
let msg = ExecuteMsg::DeleteId { name_id };
let info = mock_info(owner, &[]);
crate::execute(deps, mock_env(), info, msg).unwrap();
}
#[allow(unused)]
pub fn delete_name(deps: DepsMut<'_>, name: NymName, owner: &str) {
let msg = ExecuteMsg::DeleteName { name };
let info = mock_info(owner, &[]);
crate::execute(deps, mock_env(), info, msg).unwrap();
}
@@ -0,0 +1,4 @@
pub mod assert;
pub mod fixture;
pub mod helpers;
pub mod test_setup;
@@ -0,0 +1,153 @@
use cosmwasm_std::{coins, Addr, Coin, Uint128};
use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor};
use nym_name_service_common::{
msg::{ExecuteMsg, InstantiateMsg, QueryMsg},
response::{ConfigResponse, PagedNamesListResponse},
Address, NameEntry, NameId, NymName,
};
use serde::de::DeserializeOwned;
use crate::test_helpers::helpers::get_app_attribute;
const DENOM: &str = "unym";
const ADDRESSES: &[&str] = &[
"user", "admin", "owner", "owner1", "owner2", "owner3", "owner4",
];
const WEALTHY_ADDRESSES: &[&str] = &["wealthy_owner_1", "wealthy_owner_2"];
/// Helper for being able to systematic integration tests
pub struct TestSetup {
app: App,
addr: Addr,
}
impl Default for TestSetup {
fn default() -> Self {
TestSetup::new()
}
}
impl TestSetup {
pub fn new() -> Self {
let mut app = AppBuilder::new().build(|router, _, storage| {
let mut init_balance = |account: &str, amount: u128| {
router
.bank
.init_balance(storage, &Addr::unchecked(account), coins(amount, DENOM))
.unwrap();
};
ADDRESSES.iter().for_each(|addr| init_balance(addr, 250));
WEALTHY_ADDRESSES
.iter()
.for_each(|addr| init_balance(addr, 1000));
});
let code = ContractWrapper::new(crate::execute, crate::instantiate, crate::query);
let code_id = app.store_code(Box::new(code));
let addr = Self::instantiate(&mut app, code_id);
TestSetup { app, addr }
}
fn instantiate(app: &mut App, code_id: u64) -> Addr {
app.instantiate_contract(
code_id,
Addr::unchecked("admin"),
&InstantiateMsg {
deposit_required: Coin::new(100, DENOM),
},
&[],
"contract_label",
None,
)
.unwrap()
}
pub fn contract_balance(&self) -> Coin {
self.app.wrap().query_balance(&self.addr, DENOM).unwrap()
}
pub fn query<T: DeserializeOwned>(&self, query_msg: &QueryMsg) -> T {
self.app
.wrap()
.query_wasm_smart(&self.addr, query_msg)
.unwrap()
}
pub fn query_config(&self) -> ConfigResponse {
self.query(&QueryMsg::Config {})
}
pub fn query_id(&self, name_id: NameId) -> NameEntry {
self.query(&QueryMsg::NameId { name_id })
}
pub fn query_all(&self) -> PagedNamesListResponse {
self.query(&QueryMsg::all())
}
pub fn query_all_with_limit(
&self,
limit: Option<u32>,
start_after: Option<u32>,
) -> PagedNamesListResponse {
self.query(&QueryMsg::All { limit, start_after })
}
pub fn try_register(
&mut self,
name: NymName,
address: Address,
owner: Addr,
) -> anyhow::Result<AppResponse> {
self.app.execute_contract(
owner,
self.addr.clone(),
&ExecuteMsg::Register { name, address },
&[Coin {
denom: DENOM.to_string(),
amount: Uint128::new(100),
}],
)
}
pub fn register(&mut self, name: NymName, address: Address, owner: Addr) -> AppResponse {
let resp = self.try_register(name, address, owner).unwrap();
assert_eq!(
get_app_attribute(&resp, "wasm-register", "action"),
"register"
);
resp
}
pub fn try_delete(&mut self, name_id: NameId, owner: Addr) -> anyhow::Result<AppResponse> {
self.app.execute_contract(
owner,
self.addr.clone(),
&ExecuteMsg::DeleteId { name_id },
&[],
)
}
pub fn delete(&mut self, name_id: NameId, owner: Addr) -> AppResponse {
let delete_resp = self.try_delete(name_id, owner).unwrap();
assert_eq!(
get_app_attribute(&delete_resp, "wasm-delete_id", "action"),
"delete_id"
);
delete_resp
}
pub fn delete_name(&mut self, name: NymName, owner: Addr) -> AppResponse {
self.app
.execute_contract(
owner,
self.addr.clone(),
&ExecuteMsg::DeleteName { name },
&[],
)
.unwrap()
}
pub fn balance(&self, address: impl Into<String>) -> Coin {
self.app.wrap().query_balance(address, DENOM).unwrap()
}
}

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