Compare commits
275 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b66701c5d | |||
| 3a8b99db10 | |||
| 6e7cf42831 | |||
| 0e42f977ee | |||
| ef379cdeb3 | |||
| 4886312e43 | |||
| 3a152366d2 | |||
| d920bcb2f4 | |||
| 268a1817ab | |||
| f5c493500f | |||
| 7533f304ff | |||
| 755d4f2388 | |||
| 6884395daa | |||
| 5299b94f9e | |||
| f701787802 | |||
| 0b58e0ae24 | |||
| 03226d1487 | |||
| 534f8c6b99 | |||
| e544a6d2fa | |||
| d696535da5 | |||
| 7e444b7c42 | |||
| 0d377d98a7 | |||
| 5bdbee6dae | |||
| 014374e33e | |||
| 9adb4fbbbc | |||
| 5293766856 | |||
| a40bb3ae4f | |||
| 226c37cfea | |||
| ea8f36dfd2 | |||
| b466cb6b3d | |||
| bb0328137a | |||
| e07974ad2b | |||
| e8bb96b7d0 | |||
| 9bd06dbfcc | |||
| 055b10f7f2 | |||
| 44ca339ee5 | |||
| b6a43787b3 | |||
| 9cb22d9bde | |||
| 68485f8998 | |||
| 9c22c082c0 | |||
| 28d7400304 | |||
| 056d189e0e | |||
| b76eab2363 | |||
| b8a2ac5719 | |||
| c861e40047 | |||
| 20426d001e | |||
| 2eed9fd247 | |||
| a1887356dc | |||
| ef2420f847 | |||
| 6f26475055 | |||
| b9bf6e4521 | |||
| 7e392ff6c3 | |||
| 9e834ebaef | |||
| f96a4ffed3 | |||
| 8e9c0d2b0d | |||
| 33c356b3ec | |||
| c902e8eaa5 | |||
| 1dc8b5894f | |||
| ad285af0fa | |||
| 6621e5c72f | |||
| 969a93cf80 | |||
| f52ee0431c | |||
| a41cfd47a4 | |||
| 0b098687e1 | |||
| 09709dd8e4 | |||
| ef0867a7e7 | |||
| bfa7754ea6 | |||
| a4377bf1a6 | |||
| 9e30f9a346 | |||
| 7878895651 | |||
| fcc940a4d6 | |||
| 036092bb9c | |||
| dcde42c45d | |||
| 5b5bbc2a3d | |||
| 0c1cdc4b63 | |||
| 953c1eddd9 | |||
| 6ae0913aa1 | |||
| 92834ff9b8 | |||
| bfba92de97 | |||
| d37b63f788 | |||
| 63dd26ca1b | |||
| f24d6e224d | |||
| f9a154b36c | |||
| 72d994880b | |||
| fe02bc4631 | |||
| 1fb8e1bbaf | |||
| 96aa355db3 | |||
| 8dfb9c8173 | |||
| 382d3e130e | |||
| a8246621e1 | |||
| 66b6a8aeef | |||
| 93165ad699 | |||
| 2ebb498589 | |||
| 929a780315 | |||
| c7cdd1e1b4 | |||
| 8575a72a22 | |||
| 9d03bec14b | |||
| 7c826ef881 | |||
| ac666b7a1d | |||
| 83a9b993a1 | |||
| fe4fcc7fdf | |||
| a6e40443a4 | |||
| 0a583bd48a | |||
| 96ccce1049 | |||
| 89d0ff5624 | |||
| 2391d11758 | |||
| 902c674ff1 | |||
| 160328a08e | |||
| 9fea869bbc | |||
| c66d7ed489 | |||
| 19d603ba1a | |||
| b30f680549 | |||
| 379dd1f02b | |||
| 38804279e8 | |||
| 3907cd17fe | |||
| 67c69cbec9 | |||
| abc5dd8b92 | |||
| c791de426a | |||
| 4c5d4ac4a4 | |||
| 48c1fcaf93 | |||
| 31594c7a79 | |||
| 8ec3c04a39 | |||
| ba7fecde6f | |||
| 835a915f25 | |||
| 9c16e5ebfa | |||
| 5dc57d2a3e | |||
| fd619cad51 | |||
| 953c0915d1 | |||
| acb1aa8df0 | |||
| b9ef848523 | |||
| b7bc713cd4 | |||
| b966f962c8 | |||
| 698cdc524d | |||
| 448aba0917 | |||
| b813e1fee0 | |||
| 23de430f93 | |||
| 9462bc726d | |||
| 3d2eaeeabb | |||
| a732a676e0 | |||
| d6a8fcda9c | |||
| 3628cd92c9 | |||
| 8d26acbc7e | |||
| 07b971fe92 | |||
| 2a539dc3cc | |||
| 562eb639a2 | |||
| 91dd830a5e | |||
| 18837220f4 | |||
| 3fb9737db4 | |||
| 4d0b5b34ec | |||
| 6af0dbd1da | |||
| 2004148710 | |||
| 1e805be5ad | |||
| 7a25e600b3 | |||
| 1fd7d54715 | |||
| 0575f01f9b | |||
| 43a6e62050 | |||
| 39fb131d95 | |||
| 9533c2440a | |||
| aab2be1fbd | |||
| 448dcbeaa8 | |||
| 29b07b4904 | |||
| cceb7a40bd | |||
| fb4b32359d | |||
| 7ca92cc487 | |||
| 15d612eff6 | |||
| 021c2d14f2 | |||
| 3b85927a90 | |||
| b386388b82 | |||
| 7941b7099f | |||
| 6604d927c5 | |||
| 4fedf05b64 | |||
| fa00f85a31 | |||
| abf5a2d307 | |||
| 8e0000cb0b | |||
| cc27911671 | |||
| b598f1d1db | |||
| d4f10c1d68 | |||
| 1d7ff40d02 | |||
| 72703b36e1 | |||
| ecf7a40010 | |||
| 272e9187d2 | |||
| 69a1f3773c | |||
| 15c97b1cae | |||
| 2660ef59f7 | |||
| 12a06d9dfe | |||
| 6399cd24c8 | |||
| f30b5162e8 | |||
| 229329ae4c | |||
| a3bb5e466d | |||
| 65d893a70b | |||
| e0944e5238 | |||
| b451d4b709 | |||
| 1507e48158 | |||
| 6e7a7ed4a6 | |||
| af4801ac47 | |||
| b748c55d4e | |||
| 84e5bfc286 | |||
| 96bd9ecf49 | |||
| 092851976f | |||
| 9da1f82cca | |||
| 8c60927fe2 | |||
| ec5ef1db56 | |||
| 5dee6dd44f | |||
| aa6f5ad664 | |||
| 201b5cc56f | |||
| 510553a697 | |||
| b81967c9f5 | |||
| fadff7888b | |||
| 24854f9254 | |||
| 8ea7726e81 | |||
| ea32a897b2 | |||
| 5d7a14c5ab | |||
| cad68b8d6c | |||
| 1d125ed97f | |||
| 36d4ec5a72 | |||
| 44981b8621 | |||
| bd68797432 | |||
| 636759789c | |||
| c03a859223 | |||
| b269cdae31 | |||
| 2177a396b9 | |||
| 4c50dd64f4 | |||
| be9d3a48bb | |||
| 37d890c634 | |||
| 6287583f4b | |||
| c7760c6c10 | |||
| 2c0058c477 | |||
| 02fb2e56ca | |||
| 8b2c72eb91 | |||
| 805eb31728 | |||
| 22d4c89e48 | |||
| 28d5ac92b3 | |||
| a2e678587c | |||
| 0ac7bb5f03 | |||
| 7f45cbd5df | |||
| 8ce709bbb4 | |||
| 46db139f79 | |||
| 069c5bf332 | |||
| 54a4653e7d | |||
| ea1f689c71 | |||
| 3d60299b22 | |||
| 4d9dfac812 | |||
| 0012294dc0 | |||
| 5b5063676f | |||
| 833556020d | |||
| 0cdb68bcf3 | |||
| aa00eb70d5 | |||
| a882d10848 | |||
| c03a58ae9a | |||
| 2caf79fba0 | |||
| eda1822653 | |||
| 0f1ff5ffd2 | |||
| 9512fc34f0 | |||
| 75348922fc | |||
| 938eb2d0f0 | |||
| ede51fa589 | |||
| 7e0711ce12 | |||
| 822f40239f | |||
| 31149e6ba1 | |||
| ba233b6016 | |||
| 3d17ab6a4b | |||
| 492f1dd199 | |||
| 172399f792 | |||
| 22407c2911 | |||
| bdace0f9cb | |||
| 15bbba590b | |||
| a72264c685 | |||
| 2612cca708 | |||
| fc16d1c839 | |||
| f22bb282ab | |||
| d907b6b81a | |||
| 1903938035 | |||
| bb646c4f6a | |||
| 4c65ef3a2f | |||
| e2302466cc |
@@ -0,0 +1,63 @@
|
||||
name: CI for ts-packages
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'ts-packages/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: custom-runner-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
- name: Build
|
||||
run: yarn && yarn build && yarn build:ci
|
||||
- name: Deploy branch to CI www (storybook)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "ts-packages/dist/storybook/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Deploy branch to CI www (example)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "ts-packages/dist/example/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}-example
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Keybase - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Keybase - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: ts-packages
|
||||
NYM_PROJECT_NAME: "ts-packages"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
||||
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
|
||||
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "ci-ts-packages"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
@@ -9,28 +9,14 @@ on:
|
||||
- 'explorer/**'
|
||||
|
||||
jobs:
|
||||
matrix_prep:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
# creates the matrix strategy from build_matrix_includes.json
|
||||
- uses: actions/checkout@v2
|
||||
- id: set-matrix
|
||||
uses: JoshuaTheMiller/conditional-build-matrix@main
|
||||
with:
|
||||
inputFile: '.github/workflows/build_matrix_includes.json'
|
||||
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
|
||||
build:
|
||||
needs: matrix_prep
|
||||
strategy:
|
||||
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.os == 'windows-latest' }}
|
||||
runs-on: [ self-hosted, custom-linux-exoscale ]
|
||||
# Enable sccache via environment variable
|
||||
env:
|
||||
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
|
||||
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
@@ -39,7 +25,7 @@ jobs:
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
@@ -47,13 +33,13 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all
|
||||
args: --workspace
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all
|
||||
args: --workspace --all-features
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
@@ -63,40 +49,30 @@ jobs:
|
||||
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
name: Clippy checks
|
||||
# if: matrix.os == 'ubuntu-latest' && matrix.rust == 'stable'
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features
|
||||
|
||||
- name: Run clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
||||
# COCONUT stuff
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
args: --workspace -- -D warnings
|
||||
|
||||
- name: Build all binaries with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all --features=coconut
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run all tests with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all --features=coconut
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run clippy with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --features=coconut -- -D warnings
|
||||
args: --features=coconut -- -D warnings
|
||||
|
||||
@@ -10,41 +10,10 @@
|
||||
"rust":"stable",
|
||||
"runOnEvent":"pull_request"
|
||||
},
|
||||
|
||||
{
|
||||
"os":"macos-latest",
|
||||
"rust":"stable",
|
||||
"runOnEvent":"pull_request"
|
||||
},
|
||||
|
||||
{
|
||||
"os":"ubuntu-latest",
|
||||
"rust":"beta",
|
||||
"runOnEvent":"pull_request"
|
||||
},
|
||||
{
|
||||
"os":"windows-latest",
|
||||
"rust":"beta",
|
||||
"runOnEvent":"pull_request"
|
||||
},
|
||||
{
|
||||
"os":"macos-latest",
|
||||
"rust":"beta",
|
||||
"runOnEvent":"pull_request"
|
||||
},
|
||||
|
||||
{
|
||||
"os":"ubuntu-latest",
|
||||
"rust":"nightly",
|
||||
"runOnEvent":"pull_request"
|
||||
},
|
||||
{
|
||||
"os":"windows-latest",
|
||||
"rust":"nightly",
|
||||
"runOnEvent":"pull_request"
|
||||
},
|
||||
{
|
||||
"os":"macos-latest",
|
||||
"rust":"nightly",
|
||||
"runOnEvent":"pull_request"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
RUSTFLAGS: '-C link-arg=-s'
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path contracts/Cargo.toml --all --target wasm32-unknown-unknown
|
||||
args: --manifest-path contracts/Cargo.toml --workspace --target wasm32-unknown-unknown
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
@@ -61,4 +61,4 @@ jobs:
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path contracts/Cargo.toml --all -- -D warnings
|
||||
args: --manifest-path contracts/Cargo.toml --workspace -- -D warnings
|
||||
|
||||
@@ -16,8 +16,9 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- run: npm install
|
||||
node-version: '16'
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
- name: Run ESLint
|
||||
# GitHub should automatically annotate the PR
|
||||
run: npm run lint
|
||||
run: yarn && yarn lint
|
||||
@@ -19,14 +19,17 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- run: npm install
|
||||
node-version: '16'
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
continue-on-error: true
|
||||
- name: Build shared packages
|
||||
run: cd .. && yarn && yarn build
|
||||
- name: Set environment from the example
|
||||
run: cp .env.prod .env
|
||||
- run: npm run test
|
||||
continue-on-error: true
|
||||
- run: npm run build
|
||||
# - run: yarn test
|
||||
# continue-on-error: true
|
||||
- run: yarn && yarn build
|
||||
continue-on-error: true
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
@@ -41,9 +44,10 @@ jobs:
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Keybase - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files/messages
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Keybase - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: network-explorer
|
||||
NYM_PROJECT_NAME: "Network Explorer"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "network-explorer-${{ env.GITHUB_REF_SLUG }}"
|
||||
@@ -56,4 +60,4 @@ jobs:
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/messages/entry_point_notifications.sh
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
name: Nightly builds
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '14 4 * * *'
|
||||
jobs:
|
||||
matrix_prep:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
# creates the matrix strategy from nightly_build_matrix_includes.json
|
||||
- uses: actions/checkout@v2
|
||||
- id: set-matrix
|
||||
uses: JoshuaTheMiller/conditional-build-matrix@main
|
||||
with:
|
||||
inputFile: '.github/workflows/nightly_build_matrix_includes.json'
|
||||
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
|
||||
build:
|
||||
needs: matrix_prep
|
||||
strategy:
|
||||
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
name: Clippy checks
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features
|
||||
|
||||
- name: Run clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets -- -D warnings
|
||||
|
||||
|
||||
# COCONUT stuff
|
||||
- name: Build all binaries with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run all tests with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run clippy with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets --features=coconut -- -D warnings
|
||||
notification:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Collect jobs status
|
||||
uses: technote-space/workflow-conclusion-action@v2
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
- name: Keybase - Node Install
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Keybase - Send Notification
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: nightly
|
||||
NYM_PROJECT_NAME: "Nym nightly build"
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
||||
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
|
||||
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "ci-nightly"
|
||||
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
@@ -0,0 +1,50 @@
|
||||
[
|
||||
{
|
||||
"os":"ubuntu-latest",
|
||||
"rust":"stable",
|
||||
"runOnEvent":"schedule"
|
||||
},
|
||||
|
||||
{
|
||||
"os":"windows-latest",
|
||||
"rust":"stable",
|
||||
"runOnEvent":"schedule"
|
||||
},
|
||||
{
|
||||
"os":"macos-latest",
|
||||
"rust":"stable",
|
||||
"runOnEvent":"schedule"
|
||||
},
|
||||
|
||||
{
|
||||
"os":"ubuntu-latest",
|
||||
"rust":"beta",
|
||||
"runOnEvent":"schedule"
|
||||
},
|
||||
{
|
||||
"os":"windows-latest",
|
||||
"rust":"beta",
|
||||
"runOnEvent":"schedule"
|
||||
},
|
||||
{
|
||||
"os":"macos-latest",
|
||||
"rust":"beta",
|
||||
"runOnEvent":"schedule"
|
||||
},
|
||||
|
||||
{
|
||||
"os":"ubuntu-latest",
|
||||
"rust":"nightly",
|
||||
"runOnEvent":"schedule"
|
||||
},
|
||||
{
|
||||
"os":"windows-latest",
|
||||
"rust":"nightly",
|
||||
"runOnEvent":"schedule"
|
||||
},
|
||||
{
|
||||
"os":"macos-latest",
|
||||
"rust":"nightly",
|
||||
"runOnEvent":"schedule"
|
||||
}
|
||||
]
|
||||
@@ -1,29 +0,0 @@
|
||||
name: Generate TS types
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- "explorer/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "explorer/**"
|
||||
|
||||
jobs:
|
||||
nym-wallet-types:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: sudo apt-get update && sudo apt-get install -y libpango1.0-dev libatk1.0-dev libgdk-pixbuf2.0-dev libsoup2.4-dev librust-gdk-dev libwebkit2gtk-4.0-dev
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Generate TS
|
||||
run: cd nym-wallet/src-tauri && cargo test
|
||||
- uses: EndBug/add-and-commit@v7.2.1 # https://github.com/marketplace/actions/add-commit
|
||||
with:
|
||||
add: '["nym-wallet"]'
|
||||
message: "[ci skip] Generate TS types"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,35 @@
|
||||
KEYBASE_NYM_CHANNEL=
|
||||
KEYBASE_NYMBOT_USERNAME=
|
||||
KEYBASE_NYMBOT_PAPERKEY=
|
||||
|
||||
NYM_NOTIFICATION_KIND=nightly
|
||||
NYM_PROJECT_NAME=Nightly Build
|
||||
|
||||
#----------------------------------------------------------------
|
||||
# Custom GitHub Actions mock env vars
|
||||
IS_SUCCESS=true
|
||||
|
||||
#----------------------------------------------------------------
|
||||
# GitHub Actions context mock env vars
|
||||
GITHUB_SHA=abcdef
|
||||
GITHUB_RUN_ID=123456
|
||||
GITHUB_REPOSITORY=nymtech/nym
|
||||
GITHUB_SERVER_URL=https://github.com
|
||||
GIT_BRANCH_NAME=feature/testing-support-files
|
||||
GIT_BRANCH=feature/testing-support-files
|
||||
GIT_COMMIT_MESSAGE=This is the commit message
|
||||
GITHUB_ACTOR=octocat
|
||||
|
||||
# add a Personal Access Token (PAT) generated from GitHub here for use in testing
|
||||
GITHUB_TOKEN=
|
||||
|
||||
#----------------------------------------------------------------
|
||||
# Network Explorer
|
||||
NYM_CI_WWW_LOCATION=some-branch
|
||||
NYM_CI_WWW_BASE=example.com
|
||||
|
||||
#----------------------------------------------------------------
|
||||
# Nightly builds
|
||||
WORKFLOW_CONCLUSION=success
|
||||
|
||||
SHOW_DEBUG=true
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
.idea
|
||||
|
||||
# don't commit the lock file to avoid cross-platform issues
|
||||
package-lock.json
|
||||
@@ -0,0 +1 @@
|
||||
16
|
||||
@@ -0,0 +1,58 @@
|
||||
# GitHub Actions Support Files
|
||||
|
||||
This is a collection of scripts and files to support GitHub Actions.
|
||||
|
||||
## Sending Notifications
|
||||
|
||||
These scripts send CI notifications to Keybase by creating messages from templates and env vars passed from GitHub Actions.
|
||||
|
||||
### Adding notifications to a GitHub Action
|
||||
|
||||
```
|
||||
jobs:
|
||||
build:
|
||||
...
|
||||
- name: Notifications - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files/notifications
|
||||
- name: Notifications - Send
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: "my-component"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
||||
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
|
||||
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "ci-network-explorer"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
```
|
||||
|
||||
Notifications are run by adding the snippet above to a GitHub Action, and:
|
||||
|
||||
1. Installing node packages needed at run time
|
||||
2. Set the env vars as required:
|
||||
- `NYM_NOTIFICATION_KIND` matches the directory in `.github/workflows/support-files/${NYM_NOTIFICATION_KIND}` to provide the templates and extra scripting in `index.js`
|
||||
- Keybase credentials, channel and other env vars for the status of the build and repo
|
||||
3. Replacing the default entry point shell script on the `keybaseio/client:stable-node` docker image to run `.github/workflows/support-files/notifications/entry_point.sh`
|
||||
|
||||
### Running locally
|
||||
|
||||
You will need:
|
||||
- Node 16 LTS
|
||||
- npm
|
||||
|
||||
Copy `.github/workflows/support-files/.env.example` to `.github/workflows/support-files/.env` and valid Keybase credentials.
|
||||
|
||||
Then run `npm install` to get dependencies.
|
||||
|
||||
Start development mode for the notification type you want either by passing the value as an env var called `NYM_NOTIFICATION_KIND` or set the `.env` file values correctly.
|
||||
|
||||
```bash
|
||||
cd .github/workflows/support-files
|
||||
npm install
|
||||
cp .env.example .env
|
||||
vi .env
|
||||
npm run dev
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
require('./notifications/send_message');
|
||||
@@ -1,2 +0,0 @@
|
||||
node_modules
|
||||
.idea
|
||||
@@ -1,69 +0,0 @@
|
||||
const Bot = require('keybase-bot');
|
||||
const Handlebars = require('handlebars');
|
||||
const fs = require('fs');
|
||||
|
||||
async function main() {
|
||||
const data = { env: process.env };
|
||||
// const data = { ...PASTE TEST DATA HERE ... }; // -- DEV: uncomment to set test data
|
||||
|
||||
// validation of environment
|
||||
if(!(process.env.NYM_PROJECT_NAME || data.env.NYM_PROJECT_NAME)) {
|
||||
throw new Error('Please set env var NYM_PROJECT_NAME with the project name for displaying in notification messages');
|
||||
}
|
||||
const keybaseChannel = process.env.KEYBASE_NYM_CHANNEL || data.env.KEYBASE_NYM_CHANNEL;
|
||||
if(!keybaseChannel) {
|
||||
throw new Error('Please set env var KEYBASE_NYM_CHANNEL with the channel name for the notification message');
|
||||
}
|
||||
|
||||
// extract the git branch name
|
||||
const GIT_BRANCH_NAME = (process.env.GITHUB_REF || data.env.GITHUB_REF).split('/').slice(2).join('/');
|
||||
|
||||
data.env.GIT_BRANCH_NAME = GIT_BRANCH_NAME;
|
||||
const source = fs
|
||||
.readFileSync(process.env.IS_SUCCESS === 'true' ? 'success' : 'failure')
|
||||
.toString();
|
||||
const template = Handlebars.compile(source);
|
||||
const result = template(data);
|
||||
|
||||
// -- DEV: uncomment to show what is available in the handlebars template / show the result
|
||||
// console.dir({ data }, { depth: null });
|
||||
// console.log(result);
|
||||
|
||||
const bot = new Bot();
|
||||
try {
|
||||
const username = process.env.KEYBASE_NYMBOT_USERNAME;
|
||||
const paperkey = process.env.KEYBASE_NYMBOT_PAPERKEY;
|
||||
|
||||
if(!username) {
|
||||
throw new Error('Username is not defined. Please set env var KEYBASE_NYMBOT_USERNAME');
|
||||
}
|
||||
if(!paperkey) {
|
||||
throw new Error('Paperkey is not defined. Please set env var KEYBASE_NYMBOT_PAPERKEY');
|
||||
}
|
||||
|
||||
console.log(`Initialising keybase with user "${username}" and key: "${'*'.repeat(paperkey.length)}"...`);
|
||||
await bot.init(username, paperkey, { verbose: false });
|
||||
|
||||
const channel = {
|
||||
name: 'nymtech_bot',
|
||||
membersType: 'team',
|
||||
topicName: keybaseChannel,
|
||||
topic_type: 'CHAT',
|
||||
};
|
||||
const message = {
|
||||
body: result,
|
||||
};
|
||||
|
||||
console.log(`Sending to ${channel.name}#${channel.topicName}...`);
|
||||
await bot.chat.send(channel, message);
|
||||
|
||||
console.log('Message sent!');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exitCode = -1;
|
||||
} finally {
|
||||
await bot.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,29 @@
|
||||
const Handlebars = require('handlebars');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
async function addToContextAndValidate(context) {
|
||||
if (!context.env.NYM_CI_WWW_LOCATION) {
|
||||
throw new Error('Please ensure the env var NYM_CI_WWW_LOCATION is set');
|
||||
}
|
||||
if (!context.env.NYM_CI_WWW_BASE) {
|
||||
throw new Error('Please ensure the env var NYM_CI_WWW_BASE is set');
|
||||
}
|
||||
}
|
||||
|
||||
async function getMessageBody(context) {
|
||||
const source = fs
|
||||
.readFileSync(
|
||||
context.env.IS_SUCCESS === 'true'
|
||||
? path.resolve(__dirname, 'templates', 'success')
|
||||
: path.resolve(__dirname, 'templates', 'failure'),
|
||||
)
|
||||
.toString();
|
||||
const template = Handlebars.compile(source);
|
||||
return template(context);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addToContextAndValidate,
|
||||
getMessageBody,
|
||||
};
|
||||
@@ -5,7 +5,7 @@
|
||||
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
|
||||
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
|
||||
|
||||
Commit message:
|
||||
Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}:
|
||||
```
|
||||
{{ env.GIT_COMMIT_MESSAGE }}
|
||||
```
|
||||
@@ -0,0 +1,162 @@
|
||||
const Handlebars = require('handlebars');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { Octokit, App } = require('octokit');
|
||||
|
||||
async function addToContextAndValidate(context) {
|
||||
if (!context.env.WORKFLOW_CONCLUSION) {
|
||||
throw new Error('Please ensure the env var WORKFLOW_CONCLUSION is set');
|
||||
}
|
||||
if (!context.env.GITHUB_TOKEN) {
|
||||
throw new Error('Please ensure the env var GITHUB_TOKEN is set');
|
||||
}
|
||||
if (!context.env.GITHUB_RUN_ID) {
|
||||
throw new Error('Please ensure the env var GITHUB_RUN_ID is set');
|
||||
}
|
||||
if (!context.env.GITHUB_REPOSITORY) {
|
||||
throw new Error('Please ensure the env var GITHUB_REPOSITORY is set');
|
||||
}
|
||||
}
|
||||
|
||||
async function getMessageBody(context) {
|
||||
const source = fs
|
||||
.readFileSync(
|
||||
context.env.WORKFLOW_CONCLUSION === 'success'
|
||||
? path.resolve(__dirname, 'templates', 'success')
|
||||
: path.resolve(__dirname, 'templates', 'failure'),
|
||||
)
|
||||
.toString();
|
||||
const template = Handlebars.compile(source);
|
||||
|
||||
// get job details from GitHub API
|
||||
const octokit = new Octokit({ auth: context.env.GITHUB_TOKEN });
|
||||
const [owner, repo] = context.env.GITHUB_REPOSITORY.split('/');
|
||||
const {
|
||||
data: { jobs },
|
||||
} = await octokit.rest.actions.listJobsForWorkflowRun({
|
||||
run_id: context.env.GITHUB_RUN_ID,
|
||||
owner,
|
||||
repo,
|
||||
});
|
||||
|
||||
// uncomment this to see what is available for each job
|
||||
if(process.env.SHOW_DEBUG) {
|
||||
console.dir(jobs, { depth: null });
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
a sample of the response is:
|
||||
|
||||
{
|
||||
total_count: 10,
|
||||
jobs: [
|
||||
{
|
||||
id: 5182940024,
|
||||
run_id: 1840752095,
|
||||
run_url: 'https://api.github.com/repos/nymtech/nym/actions/runs/1840752095',
|
||||
run_attempt: 1,
|
||||
node_id: 'CR_kwDODdjOis8AAAABNO1jeA',
|
||||
head_sha: 'aa00eb70d57751bfa556bd3602df87c7473367fc',
|
||||
url: 'https://api.github.com/repos/nymtech/nym/actions/jobs/5182940024',
|
||||
html_url: 'https://github.com/nymtech/nym/runs/5182940024?check_suite_focus=true',
|
||||
status: 'completed',
|
||||
conclusion: 'success',
|
||||
started_at: '2022-02-14T11:28:34Z',
|
||||
completed_at: '2022-02-14T11:28:38Z',
|
||||
name: 'matrix_prep',
|
||||
steps: [
|
||||
{
|
||||
name: 'Set up job',
|
||||
status: 'completed',
|
||||
conclusion: 'success',
|
||||
number: 1,
|
||||
started_at: '2022-02-14T13:28:34.000+02:00',
|
||||
completed_at: '2022-02-14T13:28:36.000+02:00'
|
||||
},
|
||||
{
|
||||
name: 'Run actions/checkout@v2',
|
||||
status: 'completed',
|
||||
conclusion: 'success',
|
||||
number: 2,
|
||||
started_at: '2022-02-14T13:28:36.000+02:00',
|
||||
completed_at: '2022-02-14T13:28:37.000+02:00'
|
||||
},
|
||||
...
|
||||
],
|
||||
check_run_url: 'https://api.github.com/repos/nymtech/nym/check-runs/5182940024',
|
||||
labels: [ 'ubuntu-latest' ],
|
||||
runner_id: 1,
|
||||
runner_name: 'Hosted Agent',
|
||||
runner_group_id: 2,
|
||||
runner_group_name: 'GitHub Actions'
|
||||
},
|
||||
{
|
||||
id: 5182943473,
|
||||
run_id: 1840752095,
|
||||
run_url: 'https://api.github.com/repos/nymtech/nym/actions/runs/1840752095',
|
||||
run_attempt: 1,
|
||||
node_id: 'CR_kwDODdjOis8AAAABNO1w8Q',
|
||||
head_sha: 'aa00eb70d57751bfa556bd3602df87c7473367fc',
|
||||
url: 'https://api.github.com/repos/nymtech/nym/actions/jobs/5182943473',
|
||||
html_url: 'https://github.com/nymtech/nym/runs/5182943473?check_suite_focus=true',
|
||||
status: 'completed',
|
||||
conclusion: 'failure',
|
||||
started_at: '2022-02-14T11:29:04Z',
|
||||
completed_at: '2022-02-14T11:55:45Z',
|
||||
name: 'build (macos-latest, stable, schedule)',
|
||||
steps: [
|
||||
{
|
||||
name: 'Set up job',
|
||||
status: 'completed',
|
||||
conclusion: 'success',
|
||||
number: 1,
|
||||
started_at: '2022-02-14T13:29:04.000+02:00',
|
||||
completed_at: '2022-02-14T13:29:26.000+02:00'
|
||||
},
|
||||
{
|
||||
name: 'Install Dependencies (Linux)',
|
||||
status: 'completed',
|
||||
conclusion: 'skipped',
|
||||
number: 2,
|
||||
started_at: '2022-02-14T13:29:26.000+02:00',
|
||||
completed_at: '2022-02-14T13:29:26.000+02:00'
|
||||
},
|
||||
{
|
||||
name: 'Keybase - Send Notification',
|
||||
status: 'completed',
|
||||
conclusion: 'failure',
|
||||
number: 15,
|
||||
started_at: '2022-02-14T13:55:44.000+02:00',
|
||||
completed_at: '2022-02-14T13:55:44.000+02:00'
|
||||
},
|
||||
],
|
||||
check_run_url: 'https://api.github.com/repos/nymtech/nym/check-runs/5182943473',
|
||||
labels: [ 'macos-latest' ],
|
||||
runner_id: 4,
|
||||
runner_name: 'GitHub Actions 4',
|
||||
runner_group_id: 2,
|
||||
runner_group_name: 'GitHub Actions'
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
const jobResults = jobs
|
||||
.map((job) => {
|
||||
const icon = job.conclusion === 'success' ? '🟩' : '🟥';
|
||||
|
||||
// each job is converted into formatted markdown text
|
||||
return `${icon} ${job.conclusion}: ${job.name} - ${job.html_url}`;
|
||||
})
|
||||
// and join with newlines for display in the template
|
||||
.join('\n');
|
||||
|
||||
return template({ ...context, jobResults });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addToContextAndValidate,
|
||||
getMessageBody,
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
|
||||
> :rocket: {{ env.NYM_PROJECT_NAME }}
|
||||
> 🔴 **FAILURE** :cry:
|
||||
> `when` {{ timestamp }}
|
||||
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
|
||||
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
|
||||
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
|
||||
|
||||
{{ jobResults }}
|
||||
@@ -0,0 +1,9 @@
|
||||
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
|
||||
> :rocket: {{ env.NYM_PROJECT_NAME }}
|
||||
> ✅ **SUCCESS**
|
||||
> `when` {{ timestamp }}
|
||||
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
|
||||
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
|
||||
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
|
||||
|
||||
{{ jobResults }}
|
||||
@@ -0,0 +1,153 @@
|
||||
require('dotenv').config();
|
||||
|
||||
const Bot = require('keybase-bot');
|
||||
|
||||
let context = {
|
||||
kinds: ['ts-packages', 'network-explorer', 'nightly'],
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate that all required env and context vars are available
|
||||
*/
|
||||
function validateContext() {
|
||||
if (!context.env.NYM_NOTIFICATION_KIND) {
|
||||
throw new Error(
|
||||
'Please set env var NYM_NOTIFICATION_KIND with the project kind that matches a directory in ".github/workflows/support-files"',
|
||||
);
|
||||
}
|
||||
if (!context.kinds.includes(context.env.NYM_NOTIFICATION_KIND)) {
|
||||
throw new Error(`Env var NYM_NOTIFICATION_KIND is not in ${context.kinds}`);
|
||||
}
|
||||
if (!context.env.NYM_PROJECT_NAME) {
|
||||
throw new Error(
|
||||
'Please set env var NYM_PROJECT_NAME with the project name for displaying in notification messages',
|
||||
);
|
||||
}
|
||||
if (!context.env.KEYBASE_NYM_CHANNEL) {
|
||||
throw new Error(
|
||||
'Please set env var KEYBASE_NYM_CHANNEL with the channel name for the notification message',
|
||||
);
|
||||
}
|
||||
if (!context.env.KEYBASE_NYMBOT_USERNAME) {
|
||||
throw new Error(
|
||||
'Username is not defined. Please set env var KEYBASE_NYMBOT_USERNAME',
|
||||
);
|
||||
}
|
||||
if (!context.env.KEYBASE_NYMBOT_PAPERKEY) {
|
||||
throw new Error(
|
||||
'Paperkey is not defined. Please set env var KEYBASE_NYMBOT_PAPERKEY',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a context that will be available in the templates for rendering notifications
|
||||
*/
|
||||
function createTemplateContext() {
|
||||
const options = { dateStyle: 'full', timeStyle: 'long' };
|
||||
context.timestamp = new Date().toLocaleString(undefined, options);
|
||||
|
||||
// add environment to template context and validate
|
||||
context.env = process.env;
|
||||
try {
|
||||
validateContext();
|
||||
} catch (e) {
|
||||
if(process.env.SHOW_DEBUG) {
|
||||
// recursively print the context for easy debugging and rethrow the error
|
||||
console.dir({ context }, { depth: null });
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
context.kind = context.env.NYM_NOTIFICATION_KIND;
|
||||
|
||||
context.keybase = {
|
||||
channel: context.env.KEYBASE_NYM_CHANNEL,
|
||||
username: context.env.KEYBASE_NYMBOT_USERNAME,
|
||||
paperkey: context.env.KEYBASE_NYMBOT_PAPERKEY,
|
||||
};
|
||||
|
||||
if (!context.env.GIT_BRANCH_NAME) {
|
||||
context.env.GIT_BRANCH_NAME = context.env.GITHUB_REF.split('/')
|
||||
.slice(2)
|
||||
.join('/');
|
||||
}
|
||||
|
||||
context.status = process.env.IS_SUCCESS === 'true' ? 'success' : 'failure';
|
||||
}
|
||||
|
||||
async function sendKeybaseMessage(messageBody) {
|
||||
const bot = new Bot();
|
||||
try {
|
||||
console.log(
|
||||
`Initialising keybase with user "${
|
||||
context.keybase.username
|
||||
}" and key: "${'*'.repeat(context.keybase.paperkey.length)}"...`,
|
||||
);
|
||||
await bot.init(context.keybase.username, context.keybase.paperkey, {
|
||||
verbose: false,
|
||||
});
|
||||
|
||||
const channel = {
|
||||
name: 'nymtech_bot',
|
||||
membersType: 'team',
|
||||
topicName: context.keybase.channel,
|
||||
topic_type: 'CHAT',
|
||||
};
|
||||
const message = {
|
||||
body: messageBody,
|
||||
};
|
||||
|
||||
console.log(`Sending to ${channel.name}#${channel.topicName}...`);
|
||||
await bot.chat.send(channel, message);
|
||||
|
||||
console.log('Message sent!');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exitCode = -1;
|
||||
} finally {
|
||||
await bot.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the `kind` set in the context to process the context and generate a notification message
|
||||
* @returns {Promise<string>} A string notification message body
|
||||
*/
|
||||
async function processKindScript() {
|
||||
const script = require(`../${context.kind}`);
|
||||
if (!script.addToContextAndValidate) {
|
||||
throw new Error(
|
||||
`"./${context.kind}/index.js" does not export a method called "async addToContextAndValidate(context)"`,
|
||||
);
|
||||
}
|
||||
if (!script.getMessageBody) {
|
||||
throw new Error(
|
||||
`"./${context.kind}/index.js" does not export a method called "async getMessageBody(context)"`,
|
||||
);
|
||||
}
|
||||
|
||||
// call the script to modify and validate the context
|
||||
await script.addToContextAndValidate(context);
|
||||
|
||||
// let the script create a message body and return the result as a string for sending
|
||||
return await script.getMessageBody(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* The main function, as async so that await syntax is available
|
||||
*/
|
||||
async function main() {
|
||||
createTemplateContext();
|
||||
console.log(`Sending notification for kind "${context.kind}"...`);
|
||||
const messageBody = await processKindScript();
|
||||
if(process.env.SHOW_DEBUG) {
|
||||
console.log('-----------------------------------------');
|
||||
console.log(messageBody);
|
||||
console.log('-----------------------------------------');
|
||||
}
|
||||
await sendKeybaseMessage(messageBody);
|
||||
}
|
||||
|
||||
// call main function and let NodeJS handle the promise
|
||||
main();
|
||||
@@ -4,11 +4,14 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"format": "prettier --write send_message.js"
|
||||
"dev": "node dev.js",
|
||||
"format": "prettier --write **/*.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^16.0.0",
|
||||
"handlebars": "^4.7.7",
|
||||
"keybase-bot": "^3.6.1"
|
||||
"keybase-bot": "^3.6.1",
|
||||
"octokit": "^1.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "2.3.2"
|
||||
@@ -0,0 +1,29 @@
|
||||
const Handlebars = require('handlebars');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
async function addToContextAndValidate(context) {
|
||||
if (!context.env.NYM_CI_WWW_LOCATION) {
|
||||
throw new Error('Please ensure the env var NYM_CI_WWW_LOCATION is set');
|
||||
}
|
||||
if (!context.env.NYM_CI_WWW_BASE) {
|
||||
throw new Error('Please ensure the env var NYM_CI_WWW_BASE is set');
|
||||
}
|
||||
}
|
||||
|
||||
async function getMessageBody(context) {
|
||||
const source = fs
|
||||
.readFileSync(
|
||||
context.env.IS_SUCCESS === 'true'
|
||||
? path.resolve(__dirname, 'templates', 'success')
|
||||
: path.resolve(__dirname, 'templates', 'failure'),
|
||||
)
|
||||
.toString();
|
||||
const template = Handlebars.compile(source);
|
||||
return template(context);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addToContextAndValidate,
|
||||
getMessageBody,
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
|
||||
> :rocket: {{ env.NYM_PROJECT_NAME }}
|
||||
> 🔴 **FAILURE** :cry:
|
||||
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
|
||||
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
|
||||
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
|
||||
|
||||
Commit message:
|
||||
```
|
||||
{{ env.GIT_COMMIT_MESSAGE }}
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
|
||||
> :rocket: {{ env.NYM_PROJECT_NAME }}
|
||||
> ✅ **SUCCESS**
|
||||
|
||||
> ➡️➡️➡️➡️➡️ **View output:**
|
||||
> `storybook`: https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}
|
||||
> `example`: https://{{ env.NYM_CI_WWW_LOCATION }}-example.{{ env.NYM_CI_WWW_BASE }}
|
||||
|
||||
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
|
||||
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
|
||||
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
|
||||
|
||||
Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}:
|
||||
```
|
||||
{{ env.GIT_COMMIT_MESSAGE }}
|
||||
```
|
||||
@@ -35,4 +35,6 @@ contracts/mixnet/Justfile
|
||||
contracts/mixnet/Makefile
|
||||
validator-config
|
||||
*.patch
|
||||
validator-api-config.toml
|
||||
validator-api-config.toml
|
||||
dist
|
||||
storybook-static
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"mainnet":[{
|
||||
"nymd_url":"https://rpc.nyx.nodes.guru/",
|
||||
"api_url":"https://api.nyx.nodes.guru/"
|
||||
}]
|
||||
}
|
||||
@@ -17,7 +17,7 @@ members = [
|
||||
"clients/native",
|
||||
"clients/native/websocket-requests",
|
||||
"clients/socks5",
|
||||
"clients/tauri-client/src-tauri",
|
||||
# "clients/tauri-client/src-tauri",
|
||||
"common/client-libs/gateway-client",
|
||||
"common/client-libs/mixnet-client",
|
||||
"common/client-libs/validator-client",
|
||||
@@ -33,6 +33,9 @@ members = [
|
||||
"common/network-defaults",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
"common/nymcoconut",
|
||||
"common/nym_offline_compact_ecash",
|
||||
"common/nym_offline_divisible_ecash",
|
||||
"common/nym_online_divisible_ecash",
|
||||
"common/nymsphinx",
|
||||
"common/nymsphinx/acknowledgements",
|
||||
"common/nymsphinx/addressing",
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
all: clippy-all test wasm fmt
|
||||
happy: clippy-happy test fmt
|
||||
test: build clippy-all cargo-test wasm fmt
|
||||
no-clippy: build cargo-test wasm fmt
|
||||
happy: fmt clippy-happy test
|
||||
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet
|
||||
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet
|
||||
test: test-main test-contracts test-wallet
|
||||
cargo-test: test-main test-contracts test-wallet
|
||||
build: build-contracts build-wallet build-main
|
||||
fmt: fmt-main fmt-contracts fmt-wallet
|
||||
|
||||
clippy-happy-main:
|
||||
@@ -11,20 +13,20 @@ clippy-happy-main:
|
||||
clippy-happy-contracts:
|
||||
cargo clippy --manifest-path contracts/Cargo.toml --target wasm32-unknown-unknown
|
||||
|
||||
clippy-happy-wallet:
|
||||
clippy-happy-wallet:
|
||||
cargo clippy --manifest-path nym-wallet/Cargo.toml
|
||||
|
||||
clippy-all-main:
|
||||
cargo clippy --all-features -- -D warnings
|
||||
cargo clippy --workspace --all-features -- -D warnings
|
||||
|
||||
clippy-all-contracts:
|
||||
cargo clippy --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
cargo clippy --workspace --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
|
||||
clippy-all-wallet:
|
||||
cargo clippy --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
|
||||
clippy-all-wallet:
|
||||
cargo clippy --workspace --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
|
||||
|
||||
test-main:
|
||||
cargo test --all-features
|
||||
cargo test --all-features --workspace
|
||||
|
||||
test-contracts:
|
||||
cargo test --manifest-path contracts/Cargo.toml --all-features
|
||||
@@ -32,6 +34,15 @@ test-contracts:
|
||||
test-wallet:
|
||||
cargo test --manifest-path nym-wallet/Cargo.toml --all-features
|
||||
|
||||
build-main:
|
||||
cargo build --workspace
|
||||
|
||||
build-contracts:
|
||||
cargo build --manifest-path contracts/Cargo.toml --workspace
|
||||
|
||||
build-wallet:
|
||||
cargo build --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
fmt-main:
|
||||
cargo fmt --all
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ Wallet build instructions are also available on [our docs site](https://nymtech.
|
||||
|
||||
There's a `.env.sample-dev` file provided which you can rename to `.env` if you want convenient logging, backtrace, or other environment variables pre-set. The `.env` file is ignored so you don't need to worry about checking it in.
|
||||
|
||||
For Typescript components, please see [ts-packages](./ts-packages).
|
||||
|
||||
### Developer chat
|
||||
|
||||
You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel.
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Shared assets
|
||||
|
||||
This directory contains asset files shared by many projects in this repo.
|
||||
|
||||
You will find:
|
||||
|
||||
- favicons
|
||||
- logos
|
||||
- shared fonts
|
||||
- shared icon SVGs
|
||||
|
||||
See [ts-packages/react-webpack-with-theme-example](../ts-packages/react-webpack-with-theme-example) for examples of usage.
|
||||
|
After Width: | Height: | Size: 545 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
|
||||
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.09375" stop-color="#FB6E4E"/>
|
||||
<stop offset="1" stop-color="#F51473"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 672 B |
@@ -0,0 +1,6 @@
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url('./OpenSans-VariableFont_wdth,wght.ttf') format('truetype-variations'),
|
||||
url('./OpenSans-Italic-VariableFont_wdth,wght.ttf') format('truetype-variations');
|
||||
font-weight: 100 1000;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
|
||||
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.09375" stop-color="#FB6E4E"/>
|
||||
<stop offset="1" stop-color="#F51473"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 714 B |
@@ -0,0 +1,13 @@
|
||||
<svg width="300" height="300" viewBox="0 0 296 296" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M148 296C229.738 296 296 229.738 296 148C296 66.2619 229.738 0 148 0C66.2619 0 0 66.2619 0 148C0 229.738 66.2619 296 148 296Z" fill="url(#paint0_linear_113_1244)"/>
|
||||
<path d="M148 285.875C224.147 285.875 285.875 224.146 285.875 148C285.875 71.8536 224.147 10.1248 148 10.1248C71.8538 10.1248 10.125 71.8536 10.125 148C10.125 224.146 71.8538 285.875 148 285.875Z" fill="#121725"/>
|
||||
<path d="M88.8829 120.143H88.7169V120.281V168.637L68.3289 120.226L68.3012 120.143H68.1905H56.6272H43.653H43.5146V120.281V175.719V175.857H43.653H56.6272H56.7655V175.719V127.28L77.2365 175.774L77.2642 175.857H77.3748H88.8829H101.829H101.968V175.719V120.281V120.143H101.829H88.8829Z" fill="white"/>
|
||||
<path d="M252.347 120.143H227.616H227.477L227.45 120.253L214.78 168.858L202.082 120.253L202.054 120.143H201.944H177.157H176.991V120.281V175.719V175.857H177.157H190.104H190.242V175.719V127.667L202.774 175.747L202.801 175.857H202.94H226.564H226.675L226.703 175.747L239.234 127.667V175.719V175.857H239.373H252.347H252.485V175.719V120.281V120.143H252.347Z" fill="white"/>
|
||||
<path d="M155.663 120.143H155.58L155.552 120.198L139.812 147.557L123.988 120.198L123.96 120.143H123.877H108.911H108.635L108.773 120.364L133.145 162.579V175.719V175.857H133.283H146.257H146.396V175.719V162.579L170.767 120.364L170.905 120.143H170.629H155.663Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_113_1244" x1="0" y1="148" x2="296" y2="148" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.09375" stop-color="#FB6E4E"/>
|
||||
<stop offset="1" stop-color="#FC1D60"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="210" height="56" viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z"/>
|
||||
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z"/>
|
||||
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="white"/>
|
||||
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="#070B15"/>
|
||||
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="#141521"/>
|
||||
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="white"/>
|
||||
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="#141521"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,10 +1,10 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crypto::generic_array::typenum::Unsigned;
|
||||
use log::*;
|
||||
use nymsphinx::anonymous_replies::{
|
||||
encryption_key::EncryptionKeyDigest, encryption_key::Unsigned, SurbEncryptionKey,
|
||||
SurbEncryptionKeySize,
|
||||
encryption_key::EncryptionKeyDigest, SurbEncryptionKey, SurbEncryptionKeySize,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
@@ -43,7 +43,7 @@ impl ReplyKeyStorage {
|
||||
// if this fails it means we have some database corruption and we
|
||||
// absolutely can't continue
|
||||
|
||||
if key_bytes_ref.len() != SurbEncryptionKeySize::to_usize() {
|
||||
if key_bytes_ref.len() != SurbEncryptionKeySize::USIZE {
|
||||
error!("REPLY KEY STORAGE DATA CORRUPTION - ENCRYPTION KEY HAS INVALID LENGTH");
|
||||
panic!("REPLY KEY STORAGE DATA CORRUPTION - ENCRYPTION KEY HAS INVALID LENGTH");
|
||||
}
|
||||
|
||||
@@ -121,12 +121,16 @@ impl<T: NymConfig> Config<T> {
|
||||
self.client.testnet_mode = testnet_mode;
|
||||
}
|
||||
|
||||
pub fn with_gateway_id<S: Into<String>>(&mut self, id: S) {
|
||||
self.client.gateway_id = id.into();
|
||||
pub fn with_gateway_endpoint<S: Into<String>>(&mut self, id: S, owner: S, listener: S) {
|
||||
self.client.gateway_endpoint = GatewayEndpoint {
|
||||
gateway_id: id.into(),
|
||||
gateway_owner: owner.into(),
|
||||
gateway_listener: listener.into(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn with_gateway_listener<S: Into<String>>(&mut self, gateway_listener: S) {
|
||||
self.client.gateway_listener = gateway_listener.into();
|
||||
pub fn with_gateway_id<S: Into<String>>(&mut self, id: S) {
|
||||
self.client.gateway_endpoint.gateway_id = id.into();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
@@ -198,11 +202,15 @@ impl<T: NymConfig> Config<T> {
|
||||
}
|
||||
|
||||
pub fn get_gateway_id(&self) -> String {
|
||||
self.client.gateway_id.clone()
|
||||
self.client.gateway_endpoint.gateway_id.clone()
|
||||
}
|
||||
|
||||
pub fn get_gateway_owner(&self) -> String {
|
||||
self.client.gateway_endpoint.gateway_owner.clone()
|
||||
}
|
||||
|
||||
pub fn get_gateway_listener(&self) -> String {
|
||||
self.client.gateway_listener.clone()
|
||||
self.client.gateway_endpoint.gateway_listener.clone()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
@@ -272,6 +280,19 @@ impl<T: NymConfig> Default for Config<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
struct GatewayEndpoint {
|
||||
/// gateway_id specifies ID of the gateway to which the client should send messages.
|
||||
/// If initially omitted, a random gateway will be chosen from the available topology.
|
||||
gateway_id: String,
|
||||
|
||||
/// Address of the gateway owner to which the client should send messages.
|
||||
gateway_owner: String,
|
||||
|
||||
/// Address of the gateway listener to which all client requests should be sent.
|
||||
gateway_listener: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct Client<T> {
|
||||
/// Version of the client for which this configuration was created.
|
||||
@@ -313,12 +334,8 @@ pub struct Client<T> {
|
||||
/// sent but not received back.
|
||||
reply_encryption_key_store_path: PathBuf,
|
||||
|
||||
/// gateway_id specifies ID of the gateway to which the client should send messages.
|
||||
/// If initially omitted, a random gateway will be chosen from the available topology.
|
||||
gateway_id: String,
|
||||
|
||||
/// Address of the gateway listener to which all client requests should be sent.
|
||||
gateway_listener: String,
|
||||
/// Information regarding how the client should send data to gateway.
|
||||
gateway_endpoint: GatewayEndpoint,
|
||||
|
||||
/// Path to directory containing public/private keys used for bandwidth token purchase.
|
||||
/// Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
|
||||
@@ -357,8 +374,7 @@ impl<T: NymConfig> Default for Client<T> {
|
||||
gateway_shared_key_file: Default::default(),
|
||||
ack_key_file: Default::default(),
|
||||
reply_encryption_key_store_path: Default::default(),
|
||||
gateway_id: "".to_string(),
|
||||
gateway_listener: "".to_string(),
|
||||
gateway_endpoint: Default::default(),
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
backup_bandwidth_token_keys_dir: Default::default(),
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
|
||||
@@ -2441,9 +2441,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.14.7",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
|
||||
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
|
||||
"version": "1.14.8",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
|
||||
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -6085,9 +6085,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.4.tgz",
|
||||
"integrity": "sha512-ITeAByWWoqutFClc/lRZnFplgXgEZr3WJ6XngMM/N9DMIm4K8zXPCZ1Jdu0rERwO84w1WC5wkle2ubwTA4NTBg==",
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
@@ -8853,9 +8853,9 @@
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.7",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
|
||||
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
|
||||
"version": "1.14.8",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
|
||||
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==",
|
||||
"dev": true
|
||||
},
|
||||
"for-in": {
|
||||
@@ -11733,9 +11733,9 @@
|
||||
}
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.4.tgz",
|
||||
"integrity": "sha512-ITeAByWWoqutFClc/lRZnFplgXgEZr3WJ6XngMM/N9DMIm4K8zXPCZ1Jdu0rERwO84w1WC5wkle2ubwTA4NTBg==",
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"querystringify": "^2.1.1",
|
||||
|
||||
@@ -59,12 +59,6 @@ eth_endpoint = '{{ client.eth_endpoint }}'
|
||||
|
||||
##### additional client config options #####
|
||||
|
||||
# ID of the gateway from which the client should be fetching messages.
|
||||
gateway_id = '{{ client.gateway_id }}'
|
||||
|
||||
# Address of the gateway listener to which all client requests should be sent.
|
||||
gateway_listener = '{{ client.gateway_listener }}'
|
||||
|
||||
# A gateway specific, optional, base58 stringified shared key used for
|
||||
# communication with particular gateway.
|
||||
gateway_shared_key_file = '{{ client.gateway_shared_key_file }}'
|
||||
@@ -78,6 +72,17 @@ ack_key_file = '{{ client.ack_key_file }}'
|
||||
# Absolute path to the home Nym Clients directory.
|
||||
nym_root_directory = '{{ client.nym_root_directory }}'
|
||||
|
||||
[client.gateway_endpoint]
|
||||
# ID of the gateway from which the client should be fetching messages.
|
||||
gateway_id = '{{ client.gateway_endpoint.gateway_id }}'
|
||||
|
||||
# Address of the gateway owner to which the client should send messages.
|
||||
gateway_owner = '{{ client.gateway_endpoint.gateway_owner }}'
|
||||
|
||||
# Address of the gateway listener to which all client requests should be sent.
|
||||
gateway_listener = '{{ client.gateway_endpoint.gateway_listener }}'
|
||||
|
||||
|
||||
|
||||
##### socket config options #####
|
||||
|
||||
|
||||
@@ -160,6 +160,10 @@ impl NymClient {
|
||||
if gateway_id.is_empty() {
|
||||
panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
|
||||
}
|
||||
let gateway_owner = self.config.get_base().get_gateway_owner();
|
||||
if gateway_owner.is_empty() {
|
||||
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
|
||||
}
|
||||
let gateway_address = self.config.get_base().get_gateway_listener();
|
||||
if gateway_address.is_empty() {
|
||||
panic!("The address of the gateway is unknown - did you run `nym-client` init?")
|
||||
@@ -185,6 +189,7 @@ impl NymClient {
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
gateway_identity,
|
||||
gateway_owner,
|
||||
Some(self.key_manager.gateway_shared_key()),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
|
||||
@@ -136,6 +136,7 @@ async fn register_with_gateway(
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway.clients_address(),
|
||||
gateway.identity_key,
|
||||
gateway.owner.clone(),
|
||||
our_identity.clone(),
|
||||
timeout,
|
||||
);
|
||||
@@ -255,15 +256,14 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
chosen_gateway_id,
|
||||
)
|
||||
.await;
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_id(gateway_details.identity_key.to_base58_string());
|
||||
let shared_keys =
|
||||
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
|
||||
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_listener(gateway_details.clients_address());
|
||||
config.get_base_mut().with_gateway_endpoint(
|
||||
gateway_details.identity_key.to_base58_string(),
|
||||
gateway_details.owner.clone(),
|
||||
gateway_details.clients_address(),
|
||||
);
|
||||
key_manager.insert_gateway_shared_key(shared_keys);
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
|
||||
|
||||
@@ -58,7 +58,7 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> C
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
|
||||
config.get_base_mut().with_eth_endpoint(eth_endpoint);
|
||||
} else {
|
||||
} else if !cfg!(feature = "eth") {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
|
||||
@@ -66,7 +66,7 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> C
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
|
||||
config.get_base_mut().with_eth_private_key(eth_private_key);
|
||||
} else {
|
||||
} else if !cfg!(feature = "eth") {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
|
||||
|
||||
@@ -59,12 +59,6 @@ eth_endpoint = '{{ client.eth_endpoint }}'
|
||||
|
||||
##### additional client config options #####
|
||||
|
||||
# ID of the gateway from which the client should be fetching messages.
|
||||
gateway_id = '{{ client.gateway_id }}'
|
||||
|
||||
# Address of the gateway listener to which all client requests should be sent.
|
||||
gateway_listener = '{{ client.gateway_listener }}'
|
||||
|
||||
# A gateway specific, optional, base58 stringified shared key used for
|
||||
# communication with particular gateway.
|
||||
gateway_shared_key_file = '{{ client.gateway_shared_key_file }}'
|
||||
@@ -72,12 +66,22 @@ gateway_shared_key_file = '{{ client.gateway_shared_key_file }}'
|
||||
# Path to file containing key used for encrypting and decrypting the content of an
|
||||
# acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
ack_key_file = '{{ client.ack_key_file }}'
|
||||
|
||||
|
||||
##### advanced configuration options #####
|
||||
|
||||
# Absolute path to the home Nym Clients directory.
|
||||
nym_root_directory = '{{ client.nym_root_directory }}'
|
||||
|
||||
[client.gateway_endpoint]
|
||||
# ID of the gateway from which the client should be fetching messages.
|
||||
gateway_id = '{{ client.gateway_endpoint.gateway_id }}'
|
||||
|
||||
# Address of the gateway owner to which the client should send messages.
|
||||
gateway_owner = '{{ client.gateway_endpoint.gateway_owner }}'
|
||||
|
||||
# Address of the gateway listener to which all client requests should be sent.
|
||||
gateway_listener = '{{ client.gateway_endpoint.gateway_listener }}'
|
||||
|
||||
|
||||
##### socket config options #####
|
||||
|
||||
|
||||
@@ -148,6 +148,10 @@ impl NymClient {
|
||||
if gateway_id.is_empty() {
|
||||
panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
|
||||
}
|
||||
let gateway_owner = self.config.get_base().get_gateway_owner();
|
||||
if gateway_owner.is_empty() {
|
||||
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
|
||||
}
|
||||
let gateway_address = self.config.get_base().get_gateway_listener();
|
||||
if gateway_address.is_empty() {
|
||||
panic!("The address of the gateway is unknown - did you run `nym-client` init?")
|
||||
@@ -173,6 +177,7 @@ impl NymClient {
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
gateway_identity,
|
||||
gateway_owner,
|
||||
Some(self.key_manager.gateway_shared_key()),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
|
||||
@@ -136,6 +136,7 @@ async fn register_with_gateway(
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway.clients_address(),
|
||||
gateway.identity_key,
|
||||
gateway.owner.clone(),
|
||||
our_identity.clone(),
|
||||
timeout,
|
||||
);
|
||||
@@ -256,15 +257,14 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
chosen_gateway_id,
|
||||
)
|
||||
.await;
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_id(gateway_details.identity_key.to_base58_string());
|
||||
let shared_keys =
|
||||
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
|
||||
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_listener(gateway_details.clients_address());
|
||||
config.get_base_mut().with_gateway_endpoint(
|
||||
gateway_details.identity_key.to_base58_string(),
|
||||
gateway_details.owner.clone(),
|
||||
gateway_details.clients_address(),
|
||||
);
|
||||
key_manager.insert_gateway_shared_key(shared_keys);
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
|
||||
|
||||
@@ -54,7 +54,7 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> C
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
|
||||
config.get_base_mut().with_eth_endpoint(eth_endpoint);
|
||||
} else {
|
||||
} else if !cfg!(feature = "eth") {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
|
||||
@@ -62,7 +62,7 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> C
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
|
||||
config.get_base_mut().with_eth_private_key(eth_private_key);
|
||||
} else {
|
||||
} else if !cfg!(feature = "eth") {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@rollup/plugin-node-resolve": "^8.0.0",
|
||||
"@rollup/plugin-replace": "^2.4.0",
|
||||
"@rollup/plugin-url": "^5.0.0",
|
||||
"@tauri-apps/cli": "^1.0.0-beta.5",
|
||||
"@tauri-apps/cli": "^1.0.0-rc.5",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-svelte": "^7.0.0",
|
||||
"rollup-plugin-terser": "^7.0.0",
|
||||
|
||||
@@ -12,15 +12,19 @@ build = "src/build.rs"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.0.0-beta.2" }
|
||||
tauri-build = { version = "=1.0.0-rc.2" }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.0.0-beta.4", features = [] }
|
||||
tauri = { version = "=1.0.0-rc.2", features = [] }
|
||||
tokio = "1.4"
|
||||
url = "2.2"
|
||||
|
||||
tauri-codegen = "=1.0.0-rc.1"
|
||||
tauri-macros = "=1.0.0-rc.1"
|
||||
core-graphics = "=0.22.2"
|
||||
|
||||
coconut-interface = { path = "../../../common/coconut-interface" }
|
||||
credentials = { path = "../../../common/credentials" }
|
||||
validator-client = {path = "../../../common/client-libs/validator-client"}
|
||||
|
||||
@@ -64,15 +64,23 @@ export default class ValidatorClient implements INymClient {
|
||||
|
||||
readonly vestingContract: string;
|
||||
|
||||
readonly mainnetDenom = "unym";
|
||||
|
||||
readonly mainnetPrefix = "n";
|
||||
|
||||
private constructor(
|
||||
client: SigningClient | QueryClient,
|
||||
prefix: string,
|
||||
mixnetContract: string,
|
||||
vestingContract: string,
|
||||
vestingContract: string
|
||||
) {
|
||||
this.client = client;
|
||||
this.prefix = prefix;
|
||||
this.denom = `u${prefix}`;
|
||||
if (prefix == this.mainnetPrefix) {
|
||||
this.denom = this.mainnetDenom;
|
||||
} else {
|
||||
this.denom = `u${prefix}`;
|
||||
}
|
||||
|
||||
this.mixnetContract = mixnetContract;
|
||||
this.vestingContract = vestingContract;
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
import axios from 'axios';
|
||||
import { GasPrice } from '@cosmjs/stargate';
|
||||
|
||||
const mainnetPrefix = 'n';
|
||||
const mainnetDenom = 'nym';
|
||||
|
||||
export function nymGasPrice(prefix: string): GasPrice {
|
||||
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
|
||||
if (typeof prefix === 'string') {
|
||||
if (prefix === mainnetPrefix) {
|
||||
prefix = mainnetDenom;
|
||||
}
|
||||
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
|
||||
}
|
||||
else {
|
||||
throw new Error(`${prefix} is not of type string`);
|
||||
}
|
||||
}
|
||||
|
||||
export const downloadWasm = async (url: string): Promise<Uint8Array> => {
|
||||
|
||||
@@ -22,7 +22,10 @@
|
||||
"webpack-dev-server": "^3.11.0"
|
||||
}
|
||||
},
|
||||
"../pkg": {},
|
||||
"../pkg": {
|
||||
"name": "@nymproject/nym-client-wasm",
|
||||
"version": "0.0.1"
|
||||
},
|
||||
"node_modules/@nymproject/nym-client-wasm": {
|
||||
"resolved": "../pkg",
|
||||
"link": true
|
||||
@@ -2351,9 +2354,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
|
||||
"integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==",
|
||||
"version": "1.14.8",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
|
||||
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -5859,9 +5862,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
|
||||
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
|
||||
"version": "1.5.7",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz",
|
||||
"integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
@@ -6500,9 +6503,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
|
||||
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
|
||||
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"async-limiter": "~1.0.0"
|
||||
@@ -8542,9 +8545,9 @@
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
|
||||
"integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==",
|
||||
"version": "1.14.8",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
|
||||
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==",
|
||||
"dev": true
|
||||
},
|
||||
"for-in": {
|
||||
@@ -11399,9 +11402,9 @@
|
||||
}
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
|
||||
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
|
||||
"version": "1.5.7",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz",
|
||||
"integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"querystringify": "^2.1.1",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"@testing-library/react": "^9.5.0",
|
||||
"@testing-library/user-event": "^7.2.1",
|
||||
"bootstrap": "^4.5.0",
|
||||
"node-sass": "^4.14.1",
|
||||
"node-sass": "^7.0.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-scripts": "^4.0.0",
|
||||
|
||||
@@ -127,6 +127,7 @@ impl NymClient {
|
||||
gateway.clients_address(),
|
||||
Arc::clone(&client.identity),
|
||||
gateway.identity_key,
|
||||
gateway.owner.clone(),
|
||||
None,
|
||||
mixnet_messages_sender,
|
||||
ack_sender,
|
||||
|
||||
@@ -23,6 +23,7 @@ credentials = { path = "../../credentials" }
|
||||
crypto = { path = "../../crypto" }
|
||||
gateway-requests = { path = "../../../gateway/gateway-requests" }
|
||||
nymsphinx = { path = "../../nymsphinx" }
|
||||
pemstore = { path = "../../pemstore" }
|
||||
coconut-interface = { path = "../../coconut-interface", optional = true }
|
||||
network-defaults = { path = "../../network-defaults" }
|
||||
|
||||
|
||||
@@ -16,22 +16,27 @@ use crypto::asymmetric::identity::PublicKey;
|
||||
use network_defaults::BANDWIDTH_VALUE;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use network_defaults::{
|
||||
eth_contract::ETH_JSON_ABI, ETH_BURN_FUNCTION_NAME, ETH_CONTRACT_ADDRESS, ETH_MIN_BLOCK_DEPTH,
|
||||
TOKENS_TO_BURN,
|
||||
eth_contract::ETH_ERC20_JSON_ABI, eth_contract::ETH_JSON_ABI, ETH_BURN_FUNCTION_NAME,
|
||||
ETH_CONTRACT_ADDRESS, ETH_ERC20_APPROVE_FUNCTION_NAME, ETH_ERC20_CONTRACT_ADDRESS,
|
||||
ETH_MIN_BLOCK_DEPTH, TOKENS_TO_BURN, UTOKENS_TO_BURN,
|
||||
};
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use pemstore::traits::PemStorableKeyPair;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use rand::rngs::OsRng;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use secp256k1::SecretKey;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use std::io::Write;
|
||||
use std::io::{Read, Write};
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use std::str::FromStr;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use web3::{
|
||||
contract::{Contract, Options},
|
||||
ethabi::Token,
|
||||
signing::{Key, SecretKeyRef},
|
||||
transports::Http,
|
||||
types::{Address, Bytes, U256, U64},
|
||||
types::{Address, U256, U64},
|
||||
Web3,
|
||||
};
|
||||
|
||||
@@ -50,6 +55,19 @@ pub fn eth_contract(web3: Web3<Http>) -> Contract<Http> {
|
||||
.expect("Invalid json abi")
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub fn eth_erc20_contract(web3: Web3<Http>) -> Contract<Http> {
|
||||
Contract::from_json(
|
||||
web3.eth(),
|
||||
Address::from(ETH_ERC20_CONTRACT_ADDRESS),
|
||||
json::parse(ETH_ERC20_JSON_ABI)
|
||||
.expect("Invalid json abi")
|
||||
.dump()
|
||||
.as_bytes(),
|
||||
)
|
||||
.expect("Invalid json abi")
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BandwidthController {
|
||||
#[cfg(feature = "coconut")]
|
||||
@@ -59,6 +77,8 @@ pub struct BandwidthController {
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
contract: Contract<Http>,
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
erc20_contract: Contract<Http>,
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
eth_private_key: SecretKey,
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
backup_bandwidth_token_keys_dir: std::path::PathBuf,
|
||||
@@ -84,12 +104,14 @@ impl BandwidthController {
|
||||
Http::new(ð_endpoint).map_err(|_| GatewayClientError::InvalidURL(eth_endpoint))?;
|
||||
let web3 = web3::Web3::new(transport);
|
||||
// Fail early, on invalid abi
|
||||
let contract = eth_contract(web3);
|
||||
let contract = eth_contract(web3.clone());
|
||||
let erc20_contract = eth_erc20_contract(web3);
|
||||
let eth_private_key = secp256k1::SecretKey::from_str(ð_private_key)
|
||||
.map_err(|_| GatewayClientError::InvalidEthereumPrivateKey)?;
|
||||
|
||||
Ok(BandwidthController {
|
||||
contract,
|
||||
erc20_contract,
|
||||
eth_private_key,
|
||||
backup_bandwidth_token_keys_dir,
|
||||
})
|
||||
@@ -107,6 +129,45 @@ impl BandwidthController {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
fn restore_keypair(&self) -> Result<identity::KeyPair, GatewayClientError> {
|
||||
std::fs::create_dir_all(&self.backup_bandwidth_token_keys_dir)?;
|
||||
let file = std::fs::read_dir(&self.backup_bandwidth_token_keys_dir)?
|
||||
.find(|entry| {
|
||||
entry
|
||||
.as_ref()
|
||||
.map(|entry| entry.path().is_file())
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.unwrap_or_else(|| Err(std::io::Error::from(std::io::ErrorKind::NotFound)))?;
|
||||
let file_path = file.path();
|
||||
let pub_key = file_path
|
||||
.file_name()
|
||||
.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::NotFound))?
|
||||
.to_str()
|
||||
.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::NotFound))?;
|
||||
let mut priv_key = vec![];
|
||||
std::fs::File::open(file_path.clone())?.read_to_end(&mut priv_key)?;
|
||||
Ok(identity::KeyPair::from_keys(
|
||||
identity::PrivateKey::from_bytes(&priv_key).unwrap(),
|
||||
identity::PublicKey::from_base58_string(pub_key).unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
fn mark_keypair_as_spent(&self, keypair: &identity::KeyPair) -> Result<(), GatewayClientError> {
|
||||
let mut spent_dir = self.backup_bandwidth_token_keys_dir.clone();
|
||||
spent_dir.push("spent");
|
||||
std::fs::create_dir_all(&spent_dir)?;
|
||||
let file_path_old = self
|
||||
.backup_bandwidth_token_keys_dir
|
||||
.join(keypair.public_key().to_base58_string());
|
||||
let file_path_new = spent_dir.join(keypair.public_key().to_base58_string());
|
||||
std::fs::rename(file_path_old, file_path_new)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub async fn prepare_coconut_credential(
|
||||
&self,
|
||||
@@ -145,17 +206,25 @@ impl BandwidthController {
|
||||
pub async fn prepare_token_credential(
|
||||
&self,
|
||||
gateway_identity: PublicKey,
|
||||
gateway_owner: String,
|
||||
) -> Result<TokenCredential, GatewayClientError> {
|
||||
let mut rng = OsRng;
|
||||
|
||||
let kp = identity::KeyPair::new(&mut rng);
|
||||
self.backup_keypair(&kp)?;
|
||||
let kp = match self.restore_keypair() {
|
||||
Ok(kp) => kp,
|
||||
Err(_) => {
|
||||
let mut rng = OsRng;
|
||||
let kp = identity::KeyPair::new(&mut rng);
|
||||
self.backup_keypair(&kp)?;
|
||||
kp
|
||||
}
|
||||
};
|
||||
|
||||
let verification_key = *kp.public_key();
|
||||
let signed_verification_key = kp.private_key().sign(&verification_key.to_bytes());
|
||||
self.buy_token_credential(verification_key, signed_verification_key)
|
||||
self.buy_token_credential(verification_key, signed_verification_key, gateway_owner)
|
||||
.await?;
|
||||
|
||||
self.mark_keypair_as_spent(&kp)?;
|
||||
|
||||
let message: Vec<u8> = verification_key
|
||||
.to_bytes()
|
||||
.iter()
|
||||
@@ -177,28 +246,109 @@ impl BandwidthController {
|
||||
&self,
|
||||
verification_key: PublicKey,
|
||||
signed_verification_key: identity::Signature,
|
||||
gateway_owner: String,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
// 0 means a transaction failure, 1 means success
|
||||
let confirmations = if cfg!(debug_assertions) {
|
||||
1
|
||||
} else {
|
||||
ETH_MIN_BLOCK_DEPTH
|
||||
};
|
||||
// 15 seconds per confirmation block + 10 seconds of network overhead
|
||||
// 15 seconds per confirmation block + 10 seconds of network overhead + 20 seconds of wait for kill
|
||||
log::info!(
|
||||
"Waiting for Ethereum transaction. This should take about {} seconds",
|
||||
confirmations * 15 + 10
|
||||
(confirmations + 1) * 15 + 30
|
||||
);
|
||||
let mut options = Options::default();
|
||||
let estimation = self
|
||||
.erc20_contract
|
||||
.estimate_gas(
|
||||
ETH_ERC20_APPROVE_FUNCTION_NAME,
|
||||
(
|
||||
Token::Address(Address::from(ETH_CONTRACT_ADDRESS)),
|
||||
Token::Uint(U256::from(UTOKENS_TO_BURN)),
|
||||
),
|
||||
SecretKeyRef::from(&self.eth_private_key).address(),
|
||||
options.clone(),
|
||||
)
|
||||
.await?;
|
||||
options.gas = Some(estimation);
|
||||
log::info!("Calling ERC20 approve in 10 seconds with an estimated gas of {}. Kill the process if you want to abort", estimation);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if let Err(err) = fluvio_wasm_timer::Delay::new(std::time::Duration::from_secs(10)).await {
|
||||
log::error!(
|
||||
"the timer has gone away while waiting for possible kill! - {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
let recipt = self
|
||||
.erc20_contract
|
||||
.signed_call_with_confirmations(
|
||||
ETH_ERC20_APPROVE_FUNCTION_NAME,
|
||||
(
|
||||
Token::Address(Address::from(ETH_CONTRACT_ADDRESS)),
|
||||
Token::Uint(U256::from(UTOKENS_TO_BURN)),
|
||||
),
|
||||
options,
|
||||
1, // One confirmation is enough, as we'll be consuming the approved token next anyway
|
||||
&self.eth_private_key,
|
||||
)
|
||||
.await?;
|
||||
if Some(U64::from(0u64)) == recipt.status {
|
||||
return Err(GatewayClientError::BurnTokenError(
|
||||
web3::Error::InvalidResponse(format!(
|
||||
"Approve transaction status is 0 (failure): {:?}",
|
||||
recipt.logs,
|
||||
)),
|
||||
));
|
||||
} else {
|
||||
log::info!(
|
||||
"Approved {} tokens for bandwidth use on Ethereum",
|
||||
TOKENS_TO_BURN
|
||||
);
|
||||
}
|
||||
|
||||
let mut options = Options::default();
|
||||
let estimation = self
|
||||
.contract
|
||||
.estimate_gas(
|
||||
ETH_BURN_FUNCTION_NAME,
|
||||
(
|
||||
Token::Uint(U256::from(UTOKENS_TO_BURN)),
|
||||
Token::Uint(U256::from(&verification_key.to_bytes())),
|
||||
Token::Bytes(signed_verification_key.to_bytes().to_vec()),
|
||||
Token::String(gateway_owner.clone()),
|
||||
),
|
||||
SecretKeyRef::from(&self.eth_private_key).address(),
|
||||
options.clone(),
|
||||
)
|
||||
.await?;
|
||||
options.gas = Some(estimation);
|
||||
log::info!("Generating bandwidth on ETH contract in 10 seconds with an estimated gas of {}. \
|
||||
Kill the process if you want to abort. Keep in mind that if you abort now, you'll still have \
|
||||
some tokens approved for bandwidth spending from the previous action. \
|
||||
If you don't want that, you'll need to manually decreaseAllowance to revert the approval.", estimation);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if let Err(err) = fluvio_wasm_timer::Delay::new(std::time::Duration::from_secs(10)).await {
|
||||
log::error!(
|
||||
"the timer has gone away while waiting for possible kill! - {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
let recipt = self
|
||||
.contract
|
||||
.signed_call_with_confirmations(
|
||||
ETH_BURN_FUNCTION_NAME,
|
||||
(
|
||||
U256::from(TOKENS_TO_BURN),
|
||||
U256::from(&verification_key.to_bytes()),
|
||||
Bytes(signed_verification_key.to_bytes().to_vec()),
|
||||
Token::Uint(U256::from(UTOKENS_TO_BURN)),
|
||||
Token::Uint(U256::from(&verification_key.to_bytes())),
|
||||
Token::Bytes(signed_verification_key.to_bytes().to_vec()),
|
||||
Token::String(gateway_owner),
|
||||
),
|
||||
Options::default(),
|
||||
options,
|
||||
confirmations,
|
||||
&self.eth_private_key,
|
||||
)
|
||||
@@ -236,6 +386,15 @@ mod tests {
|
||||
eth_contract(web3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_erc20_contract() {
|
||||
let transport =
|
||||
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
|
||||
let web3 = web3::Web3::new(transport);
|
||||
// test no panic occurs
|
||||
eth_erc20_contract(web3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_event_name_constant_against_abi() {
|
||||
let transport =
|
||||
|
||||
@@ -20,6 +20,7 @@ use gateway_requests::iv::IV;
|
||||
use gateway_requests::registration::handshake::{client_handshake, SharedKeys};
|
||||
use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse};
|
||||
use log::*;
|
||||
use network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN};
|
||||
use nymsphinx::forwarding::packet::MixPacket;
|
||||
use rand::rngs::OsRng;
|
||||
use std::convert::TryFrom;
|
||||
@@ -44,6 +45,7 @@ pub struct GatewayClient {
|
||||
bandwidth_remaining: i64,
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
gateway_owner: String,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
shared_key: Option<Arc<SharedKeys>>,
|
||||
connection: SocketState,
|
||||
@@ -68,6 +70,7 @@ impl GatewayClient {
|
||||
gateway_address: String,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
gateway_identity: identity::PublicKey,
|
||||
gateway_owner: String,
|
||||
shared_key: Option<Arc<SharedKeys>>,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
@@ -80,6 +83,7 @@ impl GatewayClient {
|
||||
bandwidth_remaining: 0,
|
||||
gateway_address,
|
||||
gateway_identity,
|
||||
gateway_owner,
|
||||
local_identity,
|
||||
shared_key,
|
||||
connection: SocketState::NotConnected,
|
||||
@@ -112,6 +116,7 @@ impl GatewayClient {
|
||||
pub fn new_init(
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
gateway_owner: String,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
response_timeout_duration: Duration,
|
||||
) -> Self {
|
||||
@@ -129,6 +134,7 @@ impl GatewayClient {
|
||||
bandwidth_remaining: 0,
|
||||
gateway_address,
|
||||
gateway_identity,
|
||||
gateway_owner,
|
||||
local_identity,
|
||||
shared_key: None,
|
||||
connection: SocketState::NotConnected,
|
||||
@@ -548,6 +554,8 @@ impl GatewayClient {
|
||||
return self.try_claim_testnet_bandwidth().await;
|
||||
}
|
||||
|
||||
let _gateway_owner = self.gateway_owner.clone();
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
let credential = self
|
||||
.bandwidth_controller
|
||||
@@ -560,7 +568,7 @@ impl GatewayClient {
|
||||
.bandwidth_controller
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.prepare_token_credential(self.gateway_identity)
|
||||
.prepare_token_credential(self.gateway_identity, _gateway_owner)
|
||||
.await?;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
@@ -584,15 +592,10 @@ impl GatewayClient {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
if self.estimate_required_bandwidth(&packets) > self.bandwidth_remaining {
|
||||
// Try to claim more bandwidth first, and return an error only if that is still not
|
||||
// enough (the current granularity for bandwidth should be sufficient)
|
||||
self.claim_bandwidth().await?;
|
||||
if self.estimate_required_bandwidth(&packets) > self.bandwidth_remaining {
|
||||
return Err(GatewayClientError::NotEnoughBandwidth(
|
||||
self.estimate_required_bandwidth(&packets),
|
||||
self.bandwidth_remaining,
|
||||
));
|
||||
}
|
||||
return Err(GatewayClientError::NotEnoughBandwidth(
|
||||
self.estimate_required_bandwidth(&packets),
|
||||
self.bandwidth_remaining,
|
||||
));
|
||||
}
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
@@ -659,15 +662,10 @@ impl GatewayClient {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
|
||||
// Try to claim more bandwidth first, and return an error only if that is still not
|
||||
// enough
|
||||
self.claim_bandwidth().await?;
|
||||
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
|
||||
return Err(GatewayClientError::NotEnoughBandwidth(
|
||||
mix_packet.sphinx_packet().len() as i64,
|
||||
self.bandwidth_remaining,
|
||||
));
|
||||
}
|
||||
return Err(GatewayClientError::NotEnoughBandwidth(
|
||||
mix_packet.sphinx_packet().len() as i64,
|
||||
self.bandwidth_remaining,
|
||||
));
|
||||
}
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
@@ -737,6 +735,12 @@ impl GatewayClient {
|
||||
}
|
||||
let shared_key = self.perform_initial_authentication().await?;
|
||||
|
||||
if self.bandwidth_remaining < REMAINING_BANDWIDTH_THRESHOLD {
|
||||
info!("Claiming more bandwidth for your tokens. This will use {} token(s) from your wallet. \
|
||||
Stop the process now if you don't want that to happen.", TOKENS_TO_BURN);
|
||||
self.claim_bandwidth().await?;
|
||||
}
|
||||
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use tungstenite::Error as WsError;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::JsValue;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use web3::Error as Web3Error;
|
||||
use web3::{contract::Error as Web3ContractError, Error as Web3Error};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GatewayClientError {
|
||||
@@ -27,13 +27,17 @@ pub enum GatewayClientError {
|
||||
NetworkErrorWasm(JsValue),
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[error("Could not backup keypair - {0}")]
|
||||
#[error("Keypair IO error - {0}")]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[error("Could not burn ERC20 token in Ethereum smart contract - {0}")]
|
||||
BurnTokenError(#[from] Web3Error),
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[error("Could not run web3 contract - {0}")]
|
||||
Web3ContractError(#[from] Web3ContractError),
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[error("Invalid Ethereum private key")]
|
||||
InvalidEthereumPrivateKey,
|
||||
|
||||
@@ -39,7 +39,9 @@ flate2 = { version = "1.0.20", optional = true }
|
||||
sha2 = { version = "0.9.5", optional = true }
|
||||
itertools = { version = "0.10", optional = true }
|
||||
cosmwasm-std = { version = "1.0.0-beta3", optional = true }
|
||||
ts-rs = { version = "5.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
[features]
|
||||
nymd-client = [
|
||||
@@ -53,4 +55,3 @@ nymd-client = [
|
||||
"itertools",
|
||||
"cosmwasm-std",
|
||||
]
|
||||
typescript-types = ["ts-rs", "validator-api-requests/ts-rs"]
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
use crate::{validator_api, ValidatorClientError};
|
||||
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, Interval, MixNodeBond};
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
use url::Url;
|
||||
use validator_api_requests::models::{
|
||||
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
|
||||
@@ -19,7 +20,7 @@ use mixnet_contract_common::ContractStateParams;
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use mixnet_contract_common::{
|
||||
Delegation, IdentityKey, Interval, MixnetContractVersion, MixnodeRewardingStatusResponse,
|
||||
Delegation, IdentityKey, MixnetContractVersion, MixnodeRewardingStatusResponse,
|
||||
RewardedSetNodeStatus, RewardedSetUpdateDetails,
|
||||
};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
@@ -29,12 +30,14 @@ use std::str::FromStr;
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
network: network_defaults::all::Network,
|
||||
api_url: Url,
|
||||
nymd_url: Url,
|
||||
mixnet_contract_address: Option<cosmrs::AccountId>,
|
||||
vesting_contract_address: Option<cosmrs::AccountId>,
|
||||
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
|
||||
|
||||
mixnode_page_limit: Option<u32>,
|
||||
gateway_page_limit: Option<u32>,
|
||||
@@ -50,12 +53,14 @@ impl Config {
|
||||
api_url: Url,
|
||||
mixnet_contract_address: Option<cosmrs::AccountId>,
|
||||
vesting_contract_address: Option<cosmrs::AccountId>,
|
||||
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
|
||||
) -> Self {
|
||||
Config {
|
||||
network,
|
||||
nymd_url,
|
||||
mixnet_contract_address,
|
||||
vesting_contract_address,
|
||||
erc20_bridge_contract_address,
|
||||
api_url,
|
||||
mixnode_page_limit: None,
|
||||
gateway_page_limit: None,
|
||||
@@ -87,9 +92,10 @@ impl Config {
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
pub struct Client<C> {
|
||||
network: network_defaults::all::Network,
|
||||
pub network: network_defaults::all::Network,
|
||||
mixnet_contract_address: Option<cosmrs::AccountId>,
|
||||
vesting_contract_address: Option<cosmrs::AccountId>,
|
||||
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
|
||||
mnemonic: Option<bip39::Mnemonic>,
|
||||
|
||||
mixnode_page_limit: Option<u32>,
|
||||
@@ -114,6 +120,7 @@ impl Client<SigningNymdClient> {
|
||||
config.nymd_url.as_str(),
|
||||
config.mixnet_contract_address.clone(),
|
||||
config.vesting_contract_address.clone(),
|
||||
config.erc20_bridge_contract_address.clone(),
|
||||
mnemonic.clone(),
|
||||
None,
|
||||
)?;
|
||||
@@ -122,6 +129,7 @@ impl Client<SigningNymdClient> {
|
||||
network: config.network,
|
||||
mixnet_contract_address: config.mixnet_contract_address,
|
||||
vesting_contract_address: config.vesting_contract_address,
|
||||
erc20_bridge_contract_address: config.erc20_bridge_contract_address,
|
||||
mnemonic: Some(mnemonic),
|
||||
mixnode_page_limit: config.mixnode_page_limit,
|
||||
gateway_page_limit: config.gateway_page_limit,
|
||||
@@ -138,6 +146,7 @@ impl Client<SigningNymdClient> {
|
||||
new_endpoint.as_ref(),
|
||||
self.mixnet_contract_address.clone(),
|
||||
self.vesting_contract_address.clone(),
|
||||
self.erc20_bridge_contract_address.clone(),
|
||||
self.mnemonic.clone().unwrap(),
|
||||
None,
|
||||
)?;
|
||||
@@ -152,19 +161,29 @@ impl Client<QueryNymdClient> {
|
||||
let nymd_client = NymdClient::connect(
|
||||
config.nymd_url.as_str(),
|
||||
Some(config.mixnet_contract_address.clone().unwrap_or_else(|| {
|
||||
cosmrs::AccountId::from_str(network_defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS)
|
||||
.unwrap()
|
||||
cosmrs::AccountId::from_str(DEFAULT_NETWORK.mixnet_contract_address()).unwrap()
|
||||
})),
|
||||
Some(config.vesting_contract_address.clone().unwrap_or_else(|| {
|
||||
cosmrs::AccountId::from_str(network_defaults::DEFAULT_VESTING_CONTRACT_ADDRESS)
|
||||
.unwrap()
|
||||
cosmrs::AccountId::from_str(DEFAULT_NETWORK.vesting_contract_address()).unwrap()
|
||||
})),
|
||||
Some(
|
||||
config
|
||||
.erc20_bridge_contract_address
|
||||
.clone()
|
||||
.unwrap_or_else(|| {
|
||||
cosmrs::AccountId::from_str(
|
||||
DEFAULT_NETWORK.bandwidth_claim_contract_address(),
|
||||
)
|
||||
.unwrap()
|
||||
}),
|
||||
),
|
||||
)?;
|
||||
|
||||
Ok(Client {
|
||||
network: config.network,
|
||||
mixnet_contract_address: config.mixnet_contract_address,
|
||||
vesting_contract_address: config.vesting_contract_address,
|
||||
erc20_bridge_contract_address: config.erc20_bridge_contract_address,
|
||||
mnemonic: None,
|
||||
mixnode_page_limit: config.mixnode_page_limit,
|
||||
gateway_page_limit: config.gateway_page_limit,
|
||||
@@ -180,6 +199,7 @@ impl Client<QueryNymdClient> {
|
||||
new_endpoint.as_ref(),
|
||||
self.mixnet_contract_address.clone(),
|
||||
self.vesting_contract_address.clone(),
|
||||
self.erc20_bridge_contract_address.clone(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -228,11 +248,40 @@ impl<C> Client<C> {
|
||||
Ok(self.nymd.get_contract_settings().await?)
|
||||
}
|
||||
|
||||
pub async fn get_operator_rewards(&self, address: String) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_operator_rewards(address).await?.u128())
|
||||
}
|
||||
|
||||
pub async fn get_delegator_rewards(
|
||||
&self,
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self
|
||||
.nymd
|
||||
.get_delegator_rewards(address, mix_identity)
|
||||
.await?
|
||||
.u128())
|
||||
}
|
||||
|
||||
pub async fn get_current_epoch(&self) -> Result<Option<Interval>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_current_epoch().await?)
|
||||
}
|
||||
|
||||
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_mixnet_contract_version().await?)
|
||||
self.nymd.get_mixnet_contract_version().await
|
||||
}
|
||||
|
||||
pub async fn get_rewarding_status(
|
||||
@@ -256,13 +305,6 @@ impl<C> Client<C> {
|
||||
Ok(self.nymd.get_reward_pool().await?.u128())
|
||||
}
|
||||
|
||||
pub async fn get_current_interval(&self) -> Result<Interval, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_current_interval().await?)
|
||||
}
|
||||
|
||||
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -284,6 +326,13 @@ impl<C> Client<C> {
|
||||
Ok(self.nymd.get_active_set_work_factor().await?)
|
||||
}
|
||||
|
||||
pub async fn get_epochs_in_interval(&self) -> Result<u64, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_epochs_in_interval().await?)
|
||||
}
|
||||
|
||||
pub async fn get_interval_reward_percent(&self) -> Result<u8, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nymd::cosmwasm_client::helpers::create_pagination;
|
||||
use crate::nymd::cosmwasm_client::helpers::{create_pagination, next_page_key};
|
||||
use crate::nymd::cosmwasm_client::types::{
|
||||
Account, Code, CodeDetails, Contract, ContractCodeHistoryEntry, ContractCodeId,
|
||||
SequenceResponse, SimulateResponse,
|
||||
@@ -150,8 +150,8 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
.await?;
|
||||
|
||||
raw_balances.append(&mut res.balances);
|
||||
if let Some(pagination_info) = res.pagination {
|
||||
pagination = Some(create_pagination(pagination_info.next_key))
|
||||
if let Some(next_key) = next_page_key(res.pagination) {
|
||||
pagination = Some(create_pagination(next_key))
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -182,8 +182,8 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
.await?;
|
||||
|
||||
supply.append(&mut res.supply);
|
||||
if let Some(pagination_info) = res.pagination {
|
||||
pagination = Some(create_pagination(pagination_info.next_key))
|
||||
if let Some(next_key) = next_page_key(res.pagination) {
|
||||
pagination = Some(create_pagination(next_key))
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -274,8 +274,8 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
.await?;
|
||||
|
||||
raw_codes.append(&mut res.code_infos);
|
||||
if let Some(pagination_info) = res.pagination {
|
||||
pagination = Some(create_pagination(pagination_info.next_key))
|
||||
if let Some(next_key) = next_page_key(res.pagination) {
|
||||
pagination = Some(create_pagination(next_key))
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -319,8 +319,8 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
.await?;
|
||||
|
||||
raw_contracts.append(&mut res.contracts);
|
||||
if let Some(pagination_info) = res.pagination {
|
||||
pagination = Some(create_pagination(pagination_info.next_key))
|
||||
if let Some(next_key) = next_page_key(res.pagination) {
|
||||
pagination = Some(create_pagination(next_key))
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -375,8 +375,8 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
.await?;
|
||||
|
||||
raw_entries.append(&mut res.entries);
|
||||
if let Some(pagination_info) = res.pagination {
|
||||
pagination = Some(create_pagination(pagination_info.next_key))
|
||||
if let Some(next_key) = next_page_key(res.pagination) {
|
||||
pagination = Some(create_pagination(next_key))
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nymd::error::NymdError;
|
||||
use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest;
|
||||
use cosmrs::proto::cosmos::base::query::v1beta1::{PageRequest, PageResponse};
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use flate2::write::GzEncoder;
|
||||
use flate2::Compression;
|
||||
@@ -53,3 +53,15 @@ pub(crate) fn create_pagination(key: Vec<u8>) -> PageRequest {
|
||||
count_total: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn next_page_key(pagination_info: Option<PageResponse>) -> Option<Vec<u8>> {
|
||||
if let Some(next_page_info) = pagination_info {
|
||||
// it turns out, even though `PageResponse` is always returned wrapped in an `Option`,
|
||||
// the `next_key` can still be empty, so check whether we actually need to perform another call
|
||||
if !next_page_info.next_key.is_empty() {
|
||||
return Some(next_page_info.next_key);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ where
|
||||
}
|
||||
|
||||
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
|
||||
pub fn connect_with_signer<U>(
|
||||
pub fn connect_with_signer<U: Clone>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: Option<GasPrice>,
|
||||
gas_price: GasPrice,
|
||||
) -> Result<signing_client::Client, NymdError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
|
||||
@@ -641,10 +641,10 @@ pub struct Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn connect_with_signer<U>(
|
||||
pub fn connect_with_signer<U: Clone>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: Option<GasPrice>,
|
||||
gas_price: GasPrice,
|
||||
) -> Result<Self, NymdError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
@@ -653,7 +653,7 @@ impl Client {
|
||||
Ok(Client {
|
||||
rpc_client,
|
||||
signer,
|
||||
gas_price: gas_price.unwrap_or_default(),
|
||||
gas_price,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,11 +71,9 @@ impl FromStr for GasPrice {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GasPrice {
|
||||
fn default() -> Self {
|
||||
format!("{}{}", defaults::GAS_PRICE_AMOUNT, defaults::DENOM)
|
||||
.parse()
|
||||
.unwrap()
|
||||
impl GasPrice {
|
||||
pub fn new_with_default_price(denom: &str) -> Result<Self, NymdError> {
|
||||
format!("{}{}", defaults::GAS_PRICE_AMOUNT, denom).parse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +83,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn default_gas_price_is_valid() {
|
||||
let _ = GasPrice::default();
|
||||
let denom = "unym";
|
||||
GasPrice::new_with_default_price(denom).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -7,7 +7,11 @@ use cosmrs::Coin;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../../../nym-wallet/src/types/rust/operation.ts")
|
||||
)]
|
||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub enum Operation {
|
||||
Upload,
|
||||
@@ -43,9 +47,12 @@ pub enum Operation {
|
||||
CreatePeriodicVestingAccount,
|
||||
|
||||
AdvanceCurrentInterval,
|
||||
AdvanceCurrentEpoch,
|
||||
WriteRewardedSet,
|
||||
ClearRewardedSet,
|
||||
UpdateMixnetAddress,
|
||||
CheckpointMixnodes,
|
||||
ReconcileDelegations,
|
||||
}
|
||||
|
||||
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
|
||||
@@ -87,6 +94,9 @@ impl fmt::Display for Operation {
|
||||
Operation::WriteRewardedSet => f.write_str("WriteRewardedSet"),
|
||||
Operation::ClearRewardedSet => f.write_str("ClearRewardedSet"),
|
||||
Operation::UpdateMixnetAddress => f.write_str("UpdateMixnetAddress"),
|
||||
Operation::CheckpointMixnodes => f.write_str("CheckpointMixnodes"),
|
||||
Operation::ReconcileDelegations => f.write_str("ReconcileDelegations"),
|
||||
Operation::AdvanceCurrentEpoch => f.write_str("AdvanceCurrentEpoch"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,6 +138,9 @@ impl Operation {
|
||||
Operation::WriteRewardedSet => 175_000u64.into(),
|
||||
Operation::ClearRewardedSet => 175_000u64.into(),
|
||||
Operation::UpdateMixnetAddress => 80_000u64.into(),
|
||||
Operation::CheckpointMixnodes => 175_000u64.into(),
|
||||
Operation::ReconcileDelegations => 500_000u64.into(),
|
||||
Operation::AdvanceCurrentEpoch => 175_000u64.into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ use crate::nymd::cosmwasm_client::types::{
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
|
||||
use cosmrs::rpc::Error as TendermintRpcError;
|
||||
use cosmrs::rpc::HttpClientUrl;
|
||||
use cosmwasm_std::{Coin, Uint128};
|
||||
pub use fee::gas_price::GasPrice;
|
||||
use fee::helpers::Operation;
|
||||
@@ -52,6 +53,7 @@ pub struct NymdClient<C> {
|
||||
client: C,
|
||||
mixnet_contract_address: Option<AccountId>,
|
||||
vesting_contract_address: Option<AccountId>,
|
||||
erc20_bridge_contract_address: Option<AccountId>,
|
||||
client_address: Option<Vec<AccountId>>,
|
||||
custom_gas_limits: HashMap<Operation, Gas>,
|
||||
simulated_gas_multiplier: f32,
|
||||
@@ -62,6 +64,7 @@ impl NymdClient<QueryNymdClient> {
|
||||
endpoint: U,
|
||||
mixnet_contract_address: Option<AccountId>,
|
||||
vesting_contract_address: Option<AccountId>,
|
||||
erc20_bridge_contract_address: Option<AccountId>,
|
||||
) -> Result<NymdClient<QueryNymdClient>, NymdError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
@@ -70,8 +73,9 @@ impl NymdClient<QueryNymdClient> {
|
||||
client: QueryNymdClient::new(endpoint)?,
|
||||
mixnet_contract_address,
|
||||
vesting_contract_address,
|
||||
erc20_bridge_contract_address,
|
||||
client_address: None,
|
||||
custom_gas_limits: Default::default(),
|
||||
custom_gas_limits: HashMap::default(),
|
||||
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
|
||||
})
|
||||
}
|
||||
@@ -79,37 +83,43 @@ impl NymdClient<QueryNymdClient> {
|
||||
|
||||
impl NymdClient<SigningNymdClient> {
|
||||
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
|
||||
pub fn connect_with_signer<U>(
|
||||
pub fn connect_with_signer<U: Clone>(
|
||||
network: config::defaults::all::Network,
|
||||
endpoint: U,
|
||||
mixnet_contract_address: Option<AccountId>,
|
||||
vesting_contract_address: Option<AccountId>,
|
||||
erc20_bridge_contract_address: Option<AccountId>,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: Option<GasPrice>,
|
||||
) -> Result<NymdClient<SigningNymdClient>, NymdError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
let denom = network.denom();
|
||||
let client_address = signer
|
||||
.try_derive_accounts()?
|
||||
.into_iter()
|
||||
.map(|account| account.address)
|
||||
.collect();
|
||||
let gas_price = gas_price.unwrap_or(GasPrice::new_with_default_price(denom)?);
|
||||
|
||||
Ok(NymdClient {
|
||||
client: SigningNymdClient::connect_with_signer(endpoint, signer, gas_price)?,
|
||||
mixnet_contract_address,
|
||||
vesting_contract_address,
|
||||
erc20_bridge_contract_address,
|
||||
client_address: Some(client_address),
|
||||
custom_gas_limits: Default::default(),
|
||||
custom_gas_limits: HashMap::default(),
|
||||
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn connect_with_mnemonic<U>(
|
||||
pub fn connect_with_mnemonic<U: Clone>(
|
||||
network: config::defaults::all::Network,
|
||||
endpoint: U,
|
||||
mixnet_contract_address: Option<AccountId>,
|
||||
vesting_contract_address: Option<AccountId>,
|
||||
erc20_bridge_contract_address: Option<AccountId>,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
gas_price: Option<GasPrice>,
|
||||
) -> Result<NymdClient<SigningNymdClient>, NymdError>
|
||||
@@ -117,19 +127,22 @@ impl NymdClient<SigningNymdClient> {
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
let prefix = network.bech32_prefix();
|
||||
let denom = network.denom();
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)?;
|
||||
let client_address = wallet
|
||||
.try_derive_accounts()?
|
||||
.into_iter()
|
||||
.map(|account| account.address)
|
||||
.collect();
|
||||
let gas_price = gas_price.unwrap_or(GasPrice::new_with_default_price(denom)?);
|
||||
|
||||
Ok(NymdClient {
|
||||
client: SigningNymdClient::connect_with_signer(endpoint, wallet, gas_price)?,
|
||||
mixnet_contract_address,
|
||||
vesting_contract_address,
|
||||
erc20_bridge_contract_address,
|
||||
client_address: Some(client_address),
|
||||
custom_gas_limits: Default::default(),
|
||||
custom_gas_limits: HashMap::default(),
|
||||
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
|
||||
})
|
||||
}
|
||||
@@ -148,11 +161,10 @@ impl<C> NymdClient<C> {
|
||||
.ok_or(NymdError::NoContractAddressAvailable)
|
||||
}
|
||||
|
||||
// now the question is as follows: will denom always be in the format of `u{prefix}`?
|
||||
pub fn denom(&self) -> Result<Denom, NymdError> {
|
||||
Ok(format!("u{}", self.mixnet_contract_address()?.prefix())
|
||||
.parse()
|
||||
.unwrap())
|
||||
pub fn erc20_bridge_contract_address(&self) -> Result<&AccountId, NymdError> {
|
||||
self.erc20_bridge_contract_address
|
||||
.as_ref()
|
||||
.ok_or(NymdError::NoContractAddressAvailable)
|
||||
}
|
||||
|
||||
pub fn address(&self) -> &AccountId
|
||||
@@ -278,6 +290,43 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_operator_rewards(&self, address: String) -> Result<Uint128, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::QueryOperatorReward { address };
|
||||
self.client
|
||||
.query_contract_smart(self.mixnet_contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_delegator_rewards(
|
||||
&self,
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
) -> Result<Uint128, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::QueryDelegatorReward {
|
||||
address,
|
||||
mix_identity,
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.mixnet_contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_current_epoch(&self) -> Result<Option<Interval>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetCurrentEpoch {};
|
||||
self.client
|
||||
.query_contract_smart(self.mixnet_contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -357,16 +406,6 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_current_interval(&self) -> Result<Interval, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetCurrentInterval {};
|
||||
self.client
|
||||
.query_contract_smart(self.mixnet_contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_reward_pool(&self) -> Result<Uint128, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -417,6 +456,16 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_epochs_in_interval(&self) -> Result<u64, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetEpochsInInterval {};
|
||||
self.client
|
||||
.query_contract_smart(self.mixnet_contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Checks whether there is a bonded mixnode associated with the provided client's address
|
||||
pub async fn owns_mixnode(&self, address: &AccountId) -> Result<Option<MixNodeBond>, NymdError>
|
||||
where
|
||||
@@ -485,7 +534,7 @@ impl<C> NymdClient<C> {
|
||||
pub async fn get_mix_delegations_paged(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
start_after: Option<String>,
|
||||
start_after: Option<(String, u64)>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedMixDelegationsResponse, NymdError>
|
||||
where
|
||||
@@ -504,7 +553,7 @@ impl<C> NymdClient<C> {
|
||||
/// Gets list of all mixnode delegations on particular page.
|
||||
pub async fn get_all_network_delegations_paged(
|
||||
&self,
|
||||
start_after: Option<(IdentityKey, String)>,
|
||||
start_after: Option<(IdentityKey, Vec<u8>, u64)>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedAllDelegationsResponse, NymdError>
|
||||
where
|
||||
@@ -1164,20 +1213,58 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn advance_current_interval(&self) -> Result<ExecuteResult, NymdError>
|
||||
pub async fn advance_current_epoch(&self) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.operation_fee(Operation::AdvanceCurrentInterval);
|
||||
let fee = self.operation_fee(Operation::AdvanceCurrentEpoch);
|
||||
|
||||
let req = ExecuteMsg::AdvanceCurrentInterval {};
|
||||
let req = ExecuteMsg::AdvanceCurrentEpoch {};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.mixnet_contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"Advancing current interval",
|
||||
"Advance current epoch",
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn reconcile_delegations(&self) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.operation_fee(Operation::ReconcileDelegations);
|
||||
|
||||
let req = ExecuteMsg::ReconcileDelegations {};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.mixnet_contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"Reconciling delegation events",
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn checkpoint_mixnodes(&self) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.operation_fee(Operation::CheckpointMixnodes);
|
||||
|
||||
let req = ExecuteMsg::CheckpointMixnodes {};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.mixnet_contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"Snapshotting mixnodes",
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -6,8 +6,10 @@ use crate::nymd::error::NymdError;
|
||||
use crate::nymd::NymdClient;
|
||||
use async_trait::async_trait;
|
||||
use cosmwasm_std::{Coin, Timestamp};
|
||||
use vesting_contract::vesting::{Account, PledgeData};
|
||||
use vesting_contract_common::messages::QueryMsg as VestingQueryMsg;
|
||||
use vesting_contract::vesting::Account;
|
||||
use vesting_contract_common::{
|
||||
messages::QueryMsg as VestingQueryMsg, OriginalVestingResponse, Period, PledgeData,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
pub trait VestingQueryClient {
|
||||
@@ -43,7 +45,10 @@ pub trait VestingQueryClient {
|
||||
async fn vesting_end_time(&self, vesting_account_address: &str)
|
||||
-> Result<Timestamp, NymdError>;
|
||||
|
||||
async fn original_vesting(&self, vesting_account_address: &str) -> Result<Coin, NymdError>;
|
||||
async fn original_vesting(
|
||||
&self,
|
||||
vesting_account_address: &str,
|
||||
) -> Result<OriginalVestingResponse, NymdError>;
|
||||
|
||||
async fn delegated_free(
|
||||
&self,
|
||||
@@ -60,6 +65,10 @@ pub trait VestingQueryClient {
|
||||
async fn get_account(&self, address: &str) -> Result<Account, NymdError>;
|
||||
async fn get_mixnode_pledge(&self, address: &str) -> Result<Option<PledgeData>, NymdError>;
|
||||
async fn get_gateway_pledge(&self, address: &str) -> Result<Option<PledgeData>, NymdError>;
|
||||
async fn get_current_vesting_period(
|
||||
&self,
|
||||
vesting_account_address: &str,
|
||||
) -> Result<Period, NymdError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -142,7 +151,10 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn original_vesting(&self, vesting_account_address: &str) -> Result<Coin, NymdError> {
|
||||
async fn original_vesting(
|
||||
&self,
|
||||
vesting_account_address: &str,
|
||||
) -> Result<OriginalVestingResponse, NymdError> {
|
||||
let request = VestingQueryMsg::GetOriginalVesting {
|
||||
vesting_account_address: vesting_account_address.to_string(),
|
||||
};
|
||||
@@ -203,4 +215,13 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
|
||||
.query_contract_smart(self.vesting_contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_current_vesting_period(&self, address: &str) -> Result<Period, NymdError> {
|
||||
let request = VestingQueryMsg::GetCurrentVestingPeriod {
|
||||
address: address.to_string(),
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.vesting_contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@ use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, Vesting
|
||||
|
||||
#[async_trait]
|
||||
pub trait VestingSigningClient {
|
||||
async fn vesting_update_mixnode_config(
|
||||
&self,
|
||||
profix_margin_percent: u8,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
|
||||
async fn update_mixnet_address(&self, address: &str) -> Result<ExecuteResult, NymdError>;
|
||||
|
||||
async fn vesting_bond_gateway(
|
||||
@@ -316,4 +321,24 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn vesting_update_mixnode_config(
|
||||
&self,
|
||||
profit_margin_percent: u8,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
let fee = self.operation_fee(Operation::UpdateMixnodeConfig);
|
||||
let req = VestingExecuteMsg::UpdateMixnodeConfig {
|
||||
profit_margin_percent,
|
||||
};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.vesting_contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"VestingContract::UpdateMixnetConfig",
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,18 +57,16 @@ pub struct DirectSecp256k1HdWallet {
|
||||
}
|
||||
|
||||
impl DirectSecp256k1HdWallet {
|
||||
pub fn builder() -> DirectSecp256k1HdWalletBuilder {
|
||||
DirectSecp256k1HdWalletBuilder::default()
|
||||
pub fn builder(prefix: &str) -> DirectSecp256k1HdWalletBuilder {
|
||||
DirectSecp256k1HdWalletBuilder::new(prefix)
|
||||
}
|
||||
|
||||
/// Restores a wallet from the given BIP39 mnemonic using default options.
|
||||
pub fn from_mnemonic(prefix: String, mnemonic: bip39::Mnemonic) -> Result<Self, NymdError> {
|
||||
DirectSecp256k1HdWalletBuilder::new()
|
||||
.with_prefix(prefix)
|
||||
.build(mnemonic)
|
||||
pub fn from_mnemonic(prefix: &str, mnemonic: bip39::Mnemonic) -> Result<Self, NymdError> {
|
||||
DirectSecp256k1HdWalletBuilder::new(prefix).build(mnemonic)
|
||||
}
|
||||
|
||||
pub fn generate(prefix: String, word_count: usize) -> Result<Self, NymdError> {
|
||||
pub fn generate(prefix: &str, word_count: usize) -> Result<Self, NymdError> {
|
||||
let mneomonic = bip39::Mnemonic::generate(word_count)?;
|
||||
Self::from_mnemonic(prefix, mneomonic)
|
||||
}
|
||||
@@ -147,20 +145,14 @@ pub struct DirectSecp256k1HdWalletBuilder {
|
||||
prefix: String,
|
||||
}
|
||||
|
||||
impl Default for DirectSecp256k1HdWalletBuilder {
|
||||
fn default() -> Self {
|
||||
impl DirectSecp256k1HdWalletBuilder {
|
||||
pub fn new(prefix: &str) -> Self {
|
||||
DirectSecp256k1HdWalletBuilder {
|
||||
bip39_password: String::new(),
|
||||
hd_paths: vec![defaults::COSMOS_DERIVATION_PATH.parse().unwrap()],
|
||||
prefix: defaults::BECH32_PREFIX.to_string(),
|
||||
prefix: prefix.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectSecp256k1HdWalletBuilder {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn with_bip39_password<S: Into<String>>(mut self, password: S) -> Self {
|
||||
self.bip39_password = password.into();
|
||||
@@ -205,11 +197,11 @@ impl DirectSecp256k1HdWalletBuilder {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use network_defaults::{default_network, BECH32_PREFIX};
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
|
||||
#[test]
|
||||
fn generating_account_addresses() {
|
||||
let (addr1, addr2, addr3) = match BECH32_PREFIX {
|
||||
let (addr1, addr2, addr3) = match DEFAULT_NETWORK.bech32_prefix() {
|
||||
"punk" => (
|
||||
"punk1jw6mp7d5xqc7w6xm79lha27glmd0vdt32a3fj2",
|
||||
"punk1h5hgn94nsq4kh99rjj794hr5h5q6yfm22mcqqn",
|
||||
@@ -230,7 +222,7 @@ mod tests {
|
||||
];
|
||||
|
||||
for (mnemonic, address) in mnemonic_address.into_iter() {
|
||||
let prefix = default_network().bech32_prefix();
|
||||
let prefix = DEFAULT_NETWORK.bech32_prefix();
|
||||
let wallet =
|
||||
DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.parse().unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
|
||||
@@ -83,8 +83,6 @@ impl VerifyCredentialBody {
|
||||
pub struct BlindSignRequestBody {
|
||||
#[getset(get = "pub")]
|
||||
blind_sign_request: BlindSignRequest,
|
||||
#[getset(get = "pub")]
|
||||
public_key: nymcoconut::PublicKey,
|
||||
public_attributes: Vec<String>,
|
||||
#[getset(get = "pub")]
|
||||
total_params: u32,
|
||||
@@ -93,13 +91,11 @@ pub struct BlindSignRequestBody {
|
||||
impl BlindSignRequestBody {
|
||||
pub fn new(
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
public_key: &nymcoconut::PublicKey,
|
||||
public_attributes: &[Attribute],
|
||||
total_params: u32,
|
||||
) -> BlindSignRequestBody {
|
||||
BlindSignRequestBody {
|
||||
blind_sign_request: blind_sign_request.clone(),
|
||||
public_key: public_key.clone(),
|
||||
public_attributes: public_attributes
|
||||
.iter()
|
||||
.map(|attr| attr.to_bs58())
|
||||
|
||||
@@ -12,7 +12,6 @@ cosmwasm-std = "1.0.0-beta3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_repr = "0.1"
|
||||
schemars = "0.8"
|
||||
ts-rs = { version = "5.1", optional = true }
|
||||
thiserror = "1.0"
|
||||
network-defaults = { path = "../../network-defaults" }
|
||||
fixed = { version = "1.1", features = ["serde"] }
|
||||
@@ -24,7 +23,7 @@ contracts-common = { path = "../contracts-common" }
|
||||
|
||||
[dev-dependencies]
|
||||
time = { version = "0.3.5", features = ["serde", "macros"] }
|
||||
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -37,8 +37,12 @@ impl Delegation {
|
||||
}
|
||||
|
||||
// TODO: change that to use .joined_key() and return Vec<u8>
|
||||
pub fn storage_key(&self) -> (IdentityKey, Addr) {
|
||||
(self.node_identity(), self.owner())
|
||||
pub fn storage_key(&self) -> (IdentityKey, Vec<u8>, u64) {
|
||||
(
|
||||
self.node_identity(),
|
||||
self.owner().as_bytes().to_vec(),
|
||||
self.block_height(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn increment_amount(&mut self, amount: Uint128, at_height: Option<u64>) {
|
||||
@@ -78,14 +82,14 @@ impl Display for Delegation {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedMixDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<String>,
|
||||
pub start_next_after: Option<(String, u64)>,
|
||||
}
|
||||
|
||||
impl PagedMixDelegationsResponse {
|
||||
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<Addr>) -> Self {
|
||||
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<(Addr, u64)>) -> Self {
|
||||
PagedMixDelegationsResponse {
|
||||
delegations,
|
||||
start_next_after: start_next_after.map(|s| s.to_string()),
|
||||
start_next_after: start_next_after.map(|(s, h)| (s.to_string(), h)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,17 +112,17 @@ impl PagedDelegatorDelegationsResponse {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedAllDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<(IdentityKey, String)>,
|
||||
pub start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
|
||||
}
|
||||
|
||||
impl PagedAllDelegationsResponse {
|
||||
pub fn new(
|
||||
delegations: Vec<Delegation>,
|
||||
start_next_after: Option<(IdentityKey, Addr)>,
|
||||
start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
|
||||
) -> Self {
|
||||
PagedAllDelegationsResponse {
|
||||
delegations,
|
||||
start_next_after: start_next_after.map(|(id, addr)| (id, addr.to_string())),
|
||||
start_next_after: start_next_after.map(|(id, addr, height)| (id, addr, height)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,11 @@ pub enum MixnetContractError {
|
||||
OverflowError(#[from] cosmwasm_std::OverflowError),
|
||||
#[error("reward_blockstamp field not set, set_reward_blockstamp must be called before attempting to issue rewards")]
|
||||
BlockstampNotSet,
|
||||
#[error("{source}")]
|
||||
TryFromIntError {
|
||||
#[from]
|
||||
source: std::num::TryFromIntError,
|
||||
},
|
||||
#[error("Error casting from U128")]
|
||||
CastError,
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::mixnode::NodeRewardResult;
|
||||
use crate::{ContractStateParams, Delegation, IdentityKeyRef, Interval, Layer};
|
||||
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer};
|
||||
use cosmwasm_std::{Addr, Coin, Event, Uint128};
|
||||
|
||||
pub use contracts_common::events::*;
|
||||
|
||||
// FIXME: This should becoma an Enum
|
||||
// event types
|
||||
pub const DELEGATION_EVENT_TYPE: &str = "delegation";
|
||||
pub const PENDING_DELEGATION_EVENT_TYPE: &str = "pending_delegation";
|
||||
pub const RECONCILE_DELEGATION_EVENT_TYPE: &str = "reconcile_delegation";
|
||||
pub const UNDELEGATION_EVENT_TYPE: &str = "undelegation";
|
||||
pub const PENDING_UNDELEGATION_EVENT_TYPE: &str = "pending_undelegation";
|
||||
pub const GATEWAY_BONDING_EVENT_TYPE: &str = "gateway_bonding";
|
||||
pub const GATEWAY_UNBONDING_EVENT_TYPE: &str = "gateway_unbonding";
|
||||
pub const MIXNODE_BONDING_EVENT_TYPE: &str = "mixnode_bonding";
|
||||
@@ -19,6 +21,10 @@ pub const OPERATOR_REWARDING_EVENT_TYPE: &str = "mix_rewarding";
|
||||
pub const MIX_DELEGATORS_REWARDING_EVENT_TYPE: &str = "mix_delegators_rewarding";
|
||||
pub const CHANGE_REWARDED_SET_EVENT_TYPE: &str = "change_rewarded_set";
|
||||
pub const ADVANCE_INTERVAL_EVENT_TYPE: &str = "advance_interval";
|
||||
pub const ADVANCE_EPOCH_EVENT_TYPE: &str = "advance_epoch";
|
||||
pub const COMPOUND_DELEGATOR_REWARD_EVENT_TYPE: &str = "compound_delegator_reward";
|
||||
pub const COMPOUND_OPERATOR_REWARD_EVENT_TYPE: &str = "compound_operator_reward";
|
||||
pub const SNAPSHOT_MIXNODES_EVENT: &str = "snapshot_mixnodes";
|
||||
|
||||
// attributes that are used in multiple places
|
||||
pub const OWNER_KEY: &str = "owner";
|
||||
@@ -52,6 +58,8 @@ pub const NEW_MIXNODE_ACTIVE_SET_SIZE_KEY: &str = "new_mixnode_active_set_size";
|
||||
pub const INTERVAL_ID_KEY: &str = "interval_id";
|
||||
pub const TOTAL_MIXNODE_REWARD_KEY: &str = "total_node_reward";
|
||||
pub const OPERATOR_REWARD_KEY: &str = "operator_reward";
|
||||
pub const TOTAL_PLEDGE_KEY: &str = "pledge";
|
||||
pub const TOTAL_DELEGATIONS_KEY: &str = "delegated";
|
||||
pub const LAMBDA_KEY: &str = "lambda";
|
||||
pub const SIGMA_KEY: &str = "sigma";
|
||||
pub const DISTRIBUTED_DELEGATION_REWARDS_KEY: &str = "distributed_delegation_rewards";
|
||||
@@ -68,6 +76,19 @@ pub const NODES_IN_REWARDED_SET_KEY: &str = "nodes_in_rewarded_set";
|
||||
pub const CURRENT_INTERVAL_ID_KEY: &str = "current_interval";
|
||||
|
||||
pub const NEW_CURRENT_INTERVAL_KEY: &str = "new_current_interval";
|
||||
pub const NEW_CURRENT_EPOCH_KEY: &str = "new_current_epoch";
|
||||
pub const BLOCK_HEIGHT_KEY: &str = "block_height";
|
||||
pub const CHECKPOINT_MIXNODES_EVENT: &str = "checkpoint_mixnodes";
|
||||
pub const RECONCILIATION_ERROR_EVENT: &str = "reconciliation_error";
|
||||
|
||||
pub fn new_checkpoint_mixnodes_event(block_height: u64) -> Event {
|
||||
Event::new(CHECKPOINT_MIXNODES_EVENT)
|
||||
.add_attribute(BLOCK_HEIGHT_KEY, format!("{}", block_height))
|
||||
}
|
||||
|
||||
pub fn new_error_event(err: String) -> Event {
|
||||
Event::new(RECONCILIATION_ERROR_EVENT).add_attribute("error", err)
|
||||
}
|
||||
|
||||
pub fn new_delegation_event(
|
||||
delegator: &Addr,
|
||||
@@ -87,11 +108,74 @@ pub fn new_delegation_event(
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_pending_delegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
let mut event =
|
||||
Event::new(PENDING_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
if let Some(proxy) = proxy {
|
||||
event = event.add_attribute(PROXY_KEY, proxy)
|
||||
}
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_reconcile_delegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
let mut event =
|
||||
Event::new(RECONCILE_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
if let Some(proxy) = proxy {
|
||||
event = event.add_attribute(PROXY_KEY, proxy)
|
||||
}
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_compound_operator_reward_event(owner: &Addr, amount: Uint128) -> Event {
|
||||
let event = Event::new(COMPOUND_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner);
|
||||
event.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
}
|
||||
|
||||
pub fn new_compound_delegator_reward_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: Uint128,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
let mut event =
|
||||
Event::new(COMPOUND_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
if let Some(proxy) = proxy {
|
||||
event = event.add_attribute(PROXY_KEY, proxy)
|
||||
}
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
}
|
||||
|
||||
pub fn new_undelegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
old_delegation: &Delegation,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
amount: Uint128,
|
||||
) -> Event {
|
||||
let mut event = Event::new(UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
@@ -101,14 +185,26 @@ pub fn new_undelegation_event(
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event
|
||||
.add_attribute(AMOUNT_KEY, old_delegation.amount.to_string())
|
||||
.add_attribute(
|
||||
DELEGATION_HEIGHT_KEY,
|
||||
old_delegation.block_height.to_string(),
|
||||
)
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_pending_undelegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
mix_identity: IdentityKeyRef<'_>,
|
||||
) -> Event {
|
||||
let mut event =
|
||||
Event::new(PENDING_UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
|
||||
|
||||
if let Some(proxy) = proxy {
|
||||
event = event.add_attribute(PROXY_KEY, proxy)
|
||||
}
|
||||
|
||||
// coin implements Display trait and we use that implementation here
|
||||
event.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
|
||||
}
|
||||
|
||||
pub fn new_gateway_bonding_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
@@ -271,32 +367,25 @@ pub fn new_zero_uptime_mix_operator_rewarding_event(
|
||||
.add_attribute(NO_REWARD_REASON_KEY, ZERO_UPTIME_VALUE)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_mix_operator_rewarding_event(
|
||||
interval_id: u32,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
node_reward_result: NodeRewardResult,
|
||||
operator_reward: Uint128,
|
||||
delegation_rewards_distributed: Uint128,
|
||||
further_delegations: bool,
|
||||
node_pledge: Uint128,
|
||||
node_delegation: Uint128,
|
||||
) -> Event {
|
||||
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
|
||||
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
|
||||
.add_attribute(NODE_IDENTITY_KEY, identity)
|
||||
.add_attribute(TOTAL_PLEDGE_KEY, node_pledge)
|
||||
.add_attribute(TOTAL_DELEGATIONS_KEY, node_delegation)
|
||||
.add_attribute(
|
||||
TOTAL_MIXNODE_REWARD_KEY,
|
||||
node_reward_result.reward().to_string(),
|
||||
)
|
||||
.add_attribute(LAMBDA_KEY, node_reward_result.lambda().to_string())
|
||||
.add_attribute(SIGMA_KEY, node_reward_result.sigma().to_string())
|
||||
.add_attribute(OPERATOR_REWARD_KEY, operator_reward)
|
||||
.add_attribute(
|
||||
DISTRIBUTED_DELEGATION_REWARDS_KEY,
|
||||
delegation_rewards_distributed,
|
||||
)
|
||||
.add_attribute(
|
||||
FURTHER_DELEGATIONS_TO_REWARD_KEY,
|
||||
further_delegations.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_mix_delegators_rewarding_event(
|
||||
@@ -323,16 +412,18 @@ pub fn new_change_rewarded_set_event(
|
||||
active_set_size: u32,
|
||||
rewarded_set_size: u32,
|
||||
nodes_in_rewarded_set: u32,
|
||||
current_interval_id: u32,
|
||||
) -> Event {
|
||||
Event::new(CHANGE_REWARDED_SET_EVENT_TYPE)
|
||||
.add_attribute(ACTIVE_SET_SIZE_KEY, active_set_size.to_string())
|
||||
.add_attribute(REWARDED_SET_SIZE_KEY, rewarded_set_size.to_string())
|
||||
.add_attribute(NODES_IN_REWARDED_SET_KEY, nodes_in_rewarded_set.to_string())
|
||||
.add_attribute(CURRENT_INTERVAL_ID_KEY, current_interval_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_advance_interval_event(interval: Interval) -> Event {
|
||||
Event::new(ADVANCE_INTERVAL_EVENT_TYPE)
|
||||
.add_attribute(NEW_CURRENT_INTERVAL_KEY, interval.to_string())
|
||||
}
|
||||
|
||||
pub fn new_advance_epoch_event(interval: Interval) -> Event {
|
||||
Event::new(ADVANCE_EPOCH_EVENT_TYPE).add_attribute(NEW_CURRENT_EPOCH_KEY, interval.to_string())
|
||||
}
|
||||
|
||||
@@ -8,7 +8,11 @@ use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../../../nym-wallet/src/types/rust/gateway.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
pub struct Gateway {
|
||||
pub host: String,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::Env;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::{Display, Formatter};
|
||||
@@ -65,14 +66,28 @@ pub struct Interval {
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
/// Creates new interval instance.
|
||||
pub const fn new(id: u32, start: OffsetDateTime, length: Duration) -> Self {
|
||||
Interval { id, start, length }
|
||||
/// Initialize epoch in the contract with default values.
|
||||
/// FIXME: THIS unwrap!
|
||||
pub fn init_epoch(env: Env) -> Self {
|
||||
Interval {
|
||||
id: 0,
|
||||
start: OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap(),
|
||||
length: Duration::from_secs(3600),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_over(&self, env: Env) -> bool {
|
||||
self.end_unix_timestamp() <= env.block.time.seconds() as i64
|
||||
}
|
||||
|
||||
pub fn in_progress(&self, env: Env) -> bool {
|
||||
let block_time = env.block.time.seconds() as i64;
|
||||
self.start_unix_timestamp() <= block_time && block_time < self.end_unix_timestamp()
|
||||
}
|
||||
|
||||
/// Returns the next interval.
|
||||
#[must_use]
|
||||
pub fn next_interval(&self) -> Self {
|
||||
pub fn next(&self) -> Self {
|
||||
Interval {
|
||||
id: self.id + 1,
|
||||
start: self.end(),
|
||||
@@ -80,8 +95,19 @@ impl Interval {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_on_chain(&self, env: Env) -> Self {
|
||||
let start = self
|
||||
.end()
|
||||
.max(OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap());
|
||||
Interval {
|
||||
id: self.id + 1,
|
||||
start,
|
||||
length: self.length,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the last interval.
|
||||
pub fn previous_interval(&self) -> Option<Self> {
|
||||
pub fn previous(&self) -> Option<Self> {
|
||||
if self.id > 0 {
|
||||
Some(Interval {
|
||||
id: self.id - 1,
|
||||
@@ -125,14 +151,14 @@ impl Interval {
|
||||
if candidate.contains(now) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.next_interval();
|
||||
candidate = candidate.next();
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
if candidate.contains(now) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.previous_interval()?;
|
||||
candidate = candidate.previous()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,14 +177,14 @@ impl Interval {
|
||||
if candidate.contains_timestamp(now_unix) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.next_interval();
|
||||
candidate = candidate.next();
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
if candidate.contains_timestamp(now_unix) {
|
||||
return Some(candidate);
|
||||
}
|
||||
candidate = candidate.previous_interval()?;
|
||||
candidate = candidate.previous()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,7 +265,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn previous_interval() {
|
||||
fn previous() {
|
||||
let interval = Interval {
|
||||
id: 1,
|
||||
start: time::macros::datetime!(2021-08-23 12:00 UTC),
|
||||
@@ -250,18 +276,18 @@ mod tests {
|
||||
start: time::macros::datetime!(2021-08-22 12:00 UTC),
|
||||
length: Duration::from_secs(24 * 60 * 60),
|
||||
};
|
||||
assert_eq!(expected, interval.previous_interval().unwrap());
|
||||
assert_eq!(expected, interval.previous().unwrap());
|
||||
|
||||
let genesis_interval = Interval {
|
||||
id: 0,
|
||||
start: time::macros::datetime!(2021-08-23 12:00 UTC),
|
||||
length: Duration::from_secs(24 * 60 * 60),
|
||||
};
|
||||
assert!(genesis_interval.previous_interval().is_none());
|
||||
assert!(genesis_interval.previous().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_interval() {
|
||||
fn next() {
|
||||
let interval = Interval {
|
||||
id: 0,
|
||||
start: time::macros::datetime!(2021-08-23 12:00 UTC),
|
||||
@@ -273,7 +299,7 @@ mod tests {
|
||||
length: Duration::from_secs(24 * 60 * 60),
|
||||
};
|
||||
|
||||
assert_eq!(expected, interval.next_interval())
|
||||
assert_eq!(expected, interval.next())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -291,8 +317,8 @@ mod tests {
|
||||
let in_the_midle = interval.start + Duration::from_secs(interval.length.as_secs() / 2);
|
||||
assert!(interval.contains(in_the_midle));
|
||||
|
||||
assert!(!interval.contains(interval.next_interval().end()));
|
||||
assert!(!interval.contains(interval.previous_interval().unwrap().start()));
|
||||
assert!(!interval.contains(interval.next().end()));
|
||||
assert!(!interval.contains(interval.previous().unwrap().start()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -305,10 +331,7 @@ mod tests {
|
||||
|
||||
// interval just before
|
||||
let fake_now = first_interval.start - Duration::from_secs(123);
|
||||
assert_eq!(
|
||||
first_interval.previous_interval(),
|
||||
first_interval.current(fake_now)
|
||||
);
|
||||
assert_eq!(first_interval.previous(), first_interval.current(fake_now));
|
||||
|
||||
// this interval (start boundary)
|
||||
assert_eq!(
|
||||
@@ -329,7 +352,7 @@ mod tests {
|
||||
// next interval
|
||||
let fake_now = first_interval.end() + Duration::from_secs(123);
|
||||
assert_eq!(
|
||||
first_interval.next_interval(),
|
||||
first_interval.next(),
|
||||
first_interval.current(fake_now).unwrap()
|
||||
);
|
||||
|
||||
@@ -340,11 +363,11 @@ mod tests {
|
||||
- first_interval.length;
|
||||
assert_eq!(
|
||||
first_interval
|
||||
.previous_interval()
|
||||
.previous()
|
||||
.unwrap()
|
||||
.previous_interval()
|
||||
.previous()
|
||||
.unwrap()
|
||||
.previous_interval()
|
||||
.previous()
|
||||
.unwrap(),
|
||||
first_interval.current(fake_now).unwrap()
|
||||
);
|
||||
@@ -355,10 +378,7 @@ mod tests {
|
||||
+ first_interval.length
|
||||
+ first_interval.length;
|
||||
assert_eq!(
|
||||
first_interval
|
||||
.next_interval()
|
||||
.next_interval()
|
||||
.next_interval(),
|
||||
first_interval.next().next().next(),
|
||||
first_interval.current(fake_now).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ mod gateway;
|
||||
mod interval;
|
||||
pub mod mixnode;
|
||||
mod msg;
|
||||
pub mod reward_params;
|
||||
mod types;
|
||||
|
||||
pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
|
||||
@@ -24,3 +25,9 @@ pub use mixnode::{
|
||||
};
|
||||
pub use msg::*;
|
||||
pub use types::*;
|
||||
|
||||
pub type U128 = fixed::types::U75F53;
|
||||
|
||||
fixed::const_fixed_from_int! {
|
||||
const ONE: U128 = 1;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
// due to code generated by JsonSchema
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
|
||||
use crate::{IdentityKey, SphinxKey};
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::reward_params::RewardParams;
|
||||
use crate::{Delegation, IdentityKey, SphinxKey};
|
||||
use crate::{ONE, U128};
|
||||
use az::CheckedCast;
|
||||
use cosmwasm_std::{coin, Addr, Coin, Uint128};
|
||||
use log::error;
|
||||
use network_defaults::DEFAULT_OPERATOR_INTERVAL_COST;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
|
||||
type U128 = fixed::types::U75F53; // u128 with 18 significant digits
|
||||
|
||||
fixed::const_fixed_from_int! {
|
||||
const ONE: U128 = 1;
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(
|
||||
export,
|
||||
export_to = "../../../nym-wallet/src/types/rust/rewardedsetnodestatus.ts"
|
||||
)
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
|
||||
pub enum RewardedSetNodeStatus {
|
||||
Active,
|
||||
Standby,
|
||||
@@ -31,7 +34,57 @@ impl RewardedSetNodeStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub enum DelegationEvent {
|
||||
Delegate(Delegation),
|
||||
Undelegate(PendingUndelegate),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct PendingUndelegate {
|
||||
mix_identity: IdentityKey,
|
||||
delegate: Addr,
|
||||
proxy: Option<Addr>,
|
||||
block_height: u64,
|
||||
}
|
||||
|
||||
impl PendingUndelegate {
|
||||
pub fn new(
|
||||
mix_identity: IdentityKey,
|
||||
delegate: Addr,
|
||||
proxy: Option<Addr>,
|
||||
block_height: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
mix_identity,
|
||||
delegate,
|
||||
proxy,
|
||||
block_height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mix_identity(&self) -> IdentityKey {
|
||||
self.mix_identity.clone()
|
||||
}
|
||||
|
||||
pub fn delegate(&self) -> Addr {
|
||||
self.delegate.clone()
|
||||
}
|
||||
|
||||
pub fn proxy(&self) -> Option<Addr> {
|
||||
self.proxy.clone()
|
||||
}
|
||||
|
||||
pub fn block_height(&self) -> u64 {
|
||||
self.block_height
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../../../nym-wallet/src/types/rust/mixnode.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
pub struct MixNode {
|
||||
pub host: String,
|
||||
@@ -76,112 +129,6 @@ impl From<Layer> for String {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct NodeRewardParams {
|
||||
period_reward_pool: Uint128,
|
||||
rewarded_set_size: Uint128,
|
||||
active_set_size: Uint128,
|
||||
reward_blockstamp: u64,
|
||||
circulating_supply: Uint128,
|
||||
uptime: Uint128,
|
||||
sybil_resistance_percent: u8,
|
||||
in_active_set: bool,
|
||||
active_set_work_factor: u8,
|
||||
}
|
||||
|
||||
impl NodeRewardParams {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
period_reward_pool: u128,
|
||||
rewarded_set_size: u128,
|
||||
active_set_size: u128,
|
||||
reward_blockstamp: u64,
|
||||
circulating_supply: u128,
|
||||
uptime: u128,
|
||||
sybil_resistance_percent: u8,
|
||||
in_active_set: bool,
|
||||
active_set_work_factor: u8,
|
||||
) -> NodeRewardParams {
|
||||
NodeRewardParams {
|
||||
period_reward_pool: Uint128::new(period_reward_pool),
|
||||
rewarded_set_size: Uint128::new(rewarded_set_size),
|
||||
active_set_size: Uint128::new(active_set_size),
|
||||
reward_blockstamp,
|
||||
circulating_supply: Uint128::new(circulating_supply),
|
||||
uptime: Uint128::new(uptime),
|
||||
sybil_resistance_percent,
|
||||
in_active_set,
|
||||
active_set_work_factor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn omega(&self) -> U128 {
|
||||
// As per keybase://chat/nymtech#tokeneconomics/1179
|
||||
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
|
||||
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
|
||||
|
||||
if self.in_active_set() {
|
||||
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
self.active_set_work_factor() / denom * self.rewarded_set_size()
|
||||
} else {
|
||||
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
ONE / denom * self.rewarded_set_size()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn idle_nodes(&self) -> Uint128 {
|
||||
self.rewarded_set_size - self.active_set_size
|
||||
}
|
||||
|
||||
pub fn active_set_work_factor(&self) -> U128 {
|
||||
U128::from_num(self.active_set_work_factor)
|
||||
}
|
||||
|
||||
pub fn in_active_set(&self) -> bool {
|
||||
self.in_active_set
|
||||
}
|
||||
|
||||
pub fn performance(&self) -> U128 {
|
||||
U128::from_num(self.uptime.u128()) / U128::from_num(100)
|
||||
}
|
||||
|
||||
pub fn operator_cost(&self) -> U128 {
|
||||
U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
|
||||
}
|
||||
|
||||
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
|
||||
self.reward_blockstamp = blockstamp;
|
||||
}
|
||||
|
||||
pub fn period_reward_pool(&self) -> u128 {
|
||||
self.period_reward_pool.u128()
|
||||
}
|
||||
|
||||
pub fn rewarded_set_size(&self) -> u128 {
|
||||
self.rewarded_set_size.u128()
|
||||
}
|
||||
|
||||
pub fn circulating_supply(&self) -> u128 {
|
||||
self.circulating_supply.u128()
|
||||
}
|
||||
|
||||
pub fn reward_blockstamp(&self) -> u64 {
|
||||
self.reward_blockstamp
|
||||
}
|
||||
|
||||
pub fn uptime(&self) -> u128 {
|
||||
self.uptime.u128()
|
||||
}
|
||||
|
||||
pub fn one_over_k(&self) -> U128 {
|
||||
ONE / U128::from_num(self.rewarded_set_size.u128())
|
||||
}
|
||||
|
||||
pub fn alpha(&self) -> U128 {
|
||||
U128::from_num(self.sybil_resistance_percent) / U128::from_num(100)
|
||||
}
|
||||
}
|
||||
|
||||
// cosmwasm's limited serde doesn't work with U128 directly
|
||||
#[allow(non_snake_case)]
|
||||
pub mod fixed_U128_as_string {
|
||||
@@ -215,7 +162,7 @@ pub mod fixed_U128_as_string {
|
||||
// everything required to reward delegator of given mixnode
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DelegatorRewardParams {
|
||||
node_reward_params: NodeRewardParams,
|
||||
reward_params: RewardParams,
|
||||
|
||||
// to be completely honest I don't understand all consequences of using `#[schemars(with = "String")]`
|
||||
// for U128 here, but it seems that CosmWasm is using the same attribute for their Uint128
|
||||
@@ -231,19 +178,24 @@ pub struct DelegatorRewardParams {
|
||||
}
|
||||
|
||||
impl DelegatorRewardParams {
|
||||
pub fn new(mixnode_bond: &MixNodeBond, node_reward_params: NodeRewardParams) -> Self {
|
||||
pub fn new(
|
||||
sigma: U128,
|
||||
profit_margin: U128,
|
||||
node_profit: U128,
|
||||
reward_params: RewardParams,
|
||||
) -> Self {
|
||||
DelegatorRewardParams {
|
||||
sigma: mixnode_bond.sigma(&node_reward_params),
|
||||
profit_margin: mixnode_bond.profit_margin(),
|
||||
node_profit: mixnode_bond.node_profit(&node_reward_params),
|
||||
node_reward_params,
|
||||
sigma,
|
||||
profit_margin,
|
||||
node_profit,
|
||||
reward_params,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn determine_delegation_reward(&self, delegation_amount: Uint128) -> u128 {
|
||||
// change all values into their fixed representations
|
||||
let delegation_amount = U128::from_num(delegation_amount.u128());
|
||||
let circulating_supply = U128::from_num(self.node_reward_params.circulating_supply());
|
||||
let circulating_supply = U128::from_num(self.reward_params.circulating_supply());
|
||||
|
||||
let scaled_delegation_amount = delegation_amount / circulating_supply;
|
||||
let delegator_reward =
|
||||
@@ -261,8 +213,56 @@ impl DelegatorRewardParams {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node_reward_params(&self) -> &NodeRewardParams {
|
||||
&self.node_reward_params
|
||||
pub fn node_reward_params(&self) -> RewardParams {
|
||||
self.reward_params
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct StoredNodeRewardResult {
|
||||
reward: Uint128,
|
||||
lambda: Uint128,
|
||||
sigma: Uint128,
|
||||
}
|
||||
|
||||
impl StoredNodeRewardResult {
|
||||
pub fn reward(&self) -> Uint128 {
|
||||
self.reward
|
||||
}
|
||||
|
||||
pub fn lambda(&self) -> Uint128 {
|
||||
self.lambda
|
||||
}
|
||||
|
||||
pub fn sigma(&self) -> Uint128 {
|
||||
self.sigma
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NodeRewardResult> for StoredNodeRewardResult {
|
||||
type Error = MixnetContractError;
|
||||
|
||||
fn try_from(node_reward_result: NodeRewardResult) -> Result<Self, Self::Error> {
|
||||
Ok(StoredNodeRewardResult {
|
||||
reward: Uint128::new(
|
||||
node_reward_result
|
||||
.reward()
|
||||
.checked_cast()
|
||||
.ok_or(MixnetContractError::CastError)?,
|
||||
),
|
||||
lambda: Uint128::new(
|
||||
node_reward_result
|
||||
.lambda()
|
||||
.checked_cast()
|
||||
.ok_or(MixnetContractError::CastError)?,
|
||||
),
|
||||
sigma: Uint128::new(
|
||||
node_reward_result
|
||||
.sigma()
|
||||
.checked_cast()
|
||||
.ok_or(MixnetContractError::CastError)?,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +296,7 @@ pub struct MixNodeBond {
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
pub proxy: Option<Addr>,
|
||||
pub accumulated_rewards: Option<Uint128>,
|
||||
}
|
||||
|
||||
impl MixNodeBond {
|
||||
@@ -315,9 +316,14 @@ impl MixNodeBond {
|
||||
block_height,
|
||||
mix_node,
|
||||
proxy,
|
||||
accumulated_rewards: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accumulated_rewards(&self) -> Uint128 {
|
||||
self.accumulated_rewards.unwrap_or_else(Uint128::zero)
|
||||
}
|
||||
|
||||
pub fn profit_margin(&self) -> U128 {
|
||||
U128::from_num(self.mix_node.profit_margin_percent) / U128::from_num(100)
|
||||
}
|
||||
@@ -338,11 +344,16 @@ impl MixNodeBond {
|
||||
&self.mix_node
|
||||
}
|
||||
|
||||
// Takes into account accumulated rewards as well as current pledge and delegation amounts
|
||||
pub fn total_bond(&self) -> Option<u128> {
|
||||
if self.pledge_amount.denom != self.total_delegation.denom {
|
||||
None
|
||||
} else {
|
||||
Some(self.pledge_amount.amount.u128() + self.total_delegation.amount.u128())
|
||||
Some(
|
||||
self.pledge_amount.amount.u128()
|
||||
+ self.total_delegation.amount.u128()
|
||||
+ self.accumulated_rewards().u128(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +366,10 @@ impl MixNodeBond {
|
||||
* U128::from_num(rewarded_set_size)
|
||||
}
|
||||
|
||||
// TODO: There is an effect here when adding accumulted rewards to the total bond, ie accumulated rewards will not
|
||||
// affect lambda, but will affect sigma, in turn over time, if left unclaimed operator rewards will not compound, but
|
||||
// behave similarly to delegations.
|
||||
// The question is should this be taken into account when calculating operator rewards?
|
||||
pub fn pledge_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
|
||||
U128::from_num(self.pledge_amount().amount.u128()) / U128::from_num(circulating_supply)
|
||||
}
|
||||
@@ -364,26 +379,46 @@ impl MixNodeBond {
|
||||
/ U128::from_num(circulating_supply)
|
||||
}
|
||||
|
||||
pub fn lambda(&self, params: &NodeRewardParams) -> U128 {
|
||||
pub fn lambda(&self, params: &RewardParams) -> U128 {
|
||||
// Ratio of a bond to the token circulating supply
|
||||
let pledge_to_circulating_supply_ratio =
|
||||
self.pledge_to_circulating_supply(params.circulating_supply());
|
||||
pledge_to_circulating_supply_ratio.min(params.one_over_k())
|
||||
}
|
||||
|
||||
pub fn sigma(&self, params: &NodeRewardParams) -> U128 {
|
||||
pub fn sigma(&self, params: &RewardParams) -> U128 {
|
||||
// Ratio of a delegation to the the token circulating supply
|
||||
let total_bond_to_circulating_supply_ratio =
|
||||
self.total_bond_to_circulating_supply(params.circulating_supply());
|
||||
total_bond_to_circulating_supply_ratio.min(params.one_over_k())
|
||||
}
|
||||
|
||||
pub fn reward(&self, params: &NodeRewardParams) -> NodeRewardResult {
|
||||
pub fn estimate_reward(
|
||||
&self,
|
||||
params: &RewardParams,
|
||||
) -> Result<(u64, u64, u64), MixnetContractError> {
|
||||
let total_node_reward = self.reward(params);
|
||||
let operator_reward = self.operator_reward(params);
|
||||
// TODO: This overestimates the reward by a lot, it should take a Uint128 and return estiamte for that
|
||||
let delegators_reward = self.reward_delegation(self.total_delegation().amount, params);
|
||||
|
||||
Ok((
|
||||
total_node_reward
|
||||
.reward()
|
||||
.checked_to_num::<u128>()
|
||||
.unwrap_or_default()
|
||||
.try_into()?,
|
||||
operator_reward.try_into()?,
|
||||
delegators_reward.try_into()?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn reward(&self, params: &RewardParams) -> NodeRewardResult {
|
||||
let lambda = self.lambda(params);
|
||||
let sigma = self.sigma(params);
|
||||
|
||||
let reward = params.performance()
|
||||
* params.period_reward_pool()
|
||||
* params.epoch_reward_pool()
|
||||
* (sigma * params.omega()
|
||||
+ params.alpha() * lambda * sigma * params.rewarded_set_size())
|
||||
/ (ONE + params.alpha());
|
||||
@@ -395,22 +430,22 @@ impl MixNodeBond {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node_profit(&self, params: &NodeRewardParams) -> U128 {
|
||||
if self.reward(params).reward() < params.operator_cost() {
|
||||
U128::from_num(0)
|
||||
pub fn node_profit(&self, params: &RewardParams) -> U128 {
|
||||
if self.reward(params).reward() < params.node.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
} else {
|
||||
self.reward(params).reward() - params.operator_cost()
|
||||
self.reward(params).reward() - params.node.operator_cost()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operator_reward(&self, params: &NodeRewardParams) -> u128 {
|
||||
pub fn operator_reward(&self, params: &RewardParams) -> u128 {
|
||||
let reward = self.reward(params);
|
||||
let profit = if reward.reward < params.operator_cost() {
|
||||
U128::from_num(0)
|
||||
let profit = if reward.reward < params.node.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
} else {
|
||||
reward.reward - params.operator_cost()
|
||||
reward.reward - params.node.operator_cost()
|
||||
};
|
||||
let operator_base_reward = reward.reward.min(params.operator_cost());
|
||||
let operator_base_reward = reward.reward.min(params.node.operator_cost());
|
||||
let operator_reward = (self.profit_margin()
|
||||
+ (ONE - self.profit_margin()) * reward.lambda / reward.sigma)
|
||||
* profit;
|
||||
@@ -429,7 +464,7 @@ impl MixNodeBond {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sigma_ratio(&self, params: &NodeRewardParams) -> U128 {
|
||||
pub fn sigma_ratio(&self, params: &RewardParams) -> U128 {
|
||||
if self.total_bond_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
|
||||
{
|
||||
self.total_bond_to_circulating_supply(params.circulating_supply())
|
||||
@@ -438,8 +473,13 @@ impl MixNodeBond {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &NodeRewardParams) -> u128 {
|
||||
let reward_params = DelegatorRewardParams::new(self, *params);
|
||||
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &RewardParams) -> u128 {
|
||||
let reward_params = DelegatorRewardParams::new(
|
||||
self.sigma(params),
|
||||
self.profit_margin(),
|
||||
self.node_profit(params),
|
||||
params.to_owned(),
|
||||
);
|
||||
reward_params.determine_delegation_reward(delegation_amount)
|
||||
}
|
||||
}
|
||||
@@ -578,6 +618,7 @@ mod tests {
|
||||
block_height: 100,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
let mix2 = MixNodeBond {
|
||||
@@ -588,6 +629,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
let mix3 = MixNodeBond {
|
||||
@@ -598,6 +640,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
let mix4 = MixNodeBond {
|
||||
@@ -608,6 +651,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
let mix5 = MixNodeBond {
|
||||
@@ -618,6 +662,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
proxy: None,
|
||||
accumulated_rewards: Some(Uint128::zero()),
|
||||
};
|
||||
|
||||
// summary:
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::mixnode::NodeRewardParams;
|
||||
use crate::reward_params::NodeRewardParams;
|
||||
use crate::ContractStateParams;
|
||||
use crate::{Gateway, IdentityKey, MixNode};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
type BlockHeight = u64;
|
||||
type DelegateAddress = Vec<u8>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct InstantiateMsg {
|
||||
pub rewarding_validator_address: String,
|
||||
@@ -15,6 +18,19 @@ pub struct InstantiateMsg {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
ReconcileDelegations {},
|
||||
CheckpointMixnodes {},
|
||||
CompoundOperatorRewardOnBehalf {
|
||||
owner: String,
|
||||
},
|
||||
CompoundDelegatorRewardOnBehalf {
|
||||
owner: String,
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
CompoundOperatorReward {},
|
||||
CompoundDelegatorReward {
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
BondMixnode {
|
||||
mix_node: MixNode,
|
||||
owner_signature: String,
|
||||
@@ -23,6 +39,10 @@ pub enum ExecuteMsg {
|
||||
UpdateMixnodeConfig {
|
||||
profit_margin_percent: u8,
|
||||
},
|
||||
UpdateMixnodeConfigOnBehalf {
|
||||
profit_margin_percent: u8,
|
||||
owner: String,
|
||||
},
|
||||
BondGateway {
|
||||
gateway: Gateway,
|
||||
owner_signature: String,
|
||||
@@ -46,11 +66,11 @@ pub enum ExecuteMsg {
|
||||
// id of the current rewarding interval
|
||||
interval_id: u32,
|
||||
},
|
||||
RewardNextMixDelegators {
|
||||
mix_identity: IdentityKey,
|
||||
// id of the current rewarding interval
|
||||
interval_id: u32,
|
||||
},
|
||||
// RewardNextMixDelegators {
|
||||
// mix_identity: IdentityKey,
|
||||
// // id of the current rewarding interval
|
||||
// interval_id: u32,
|
||||
// },
|
||||
DelegateToMixnodeOnBehalf {
|
||||
mix_identity: IdentityKey,
|
||||
delegate: String,
|
||||
@@ -79,7 +99,8 @@ pub enum ExecuteMsg {
|
||||
rewarded_set: Vec<IdentityKey>,
|
||||
expected_active_set_size: u32,
|
||||
},
|
||||
AdvanceCurrentInterval {},
|
||||
// AdvanceCurrentInterval {},
|
||||
AdvanceCurrentEpoch {},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
@@ -104,7 +125,7 @@ pub enum QueryMsg {
|
||||
// gets all [paged] delegations in the entire network
|
||||
// TODO: do we even want that?
|
||||
GetAllNetworkDelegations {
|
||||
start_after: Option<(IdentityKey, String)>,
|
||||
start_after: Option<(IdentityKey, DelegateAddress, BlockHeight)>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
// gets all [paged] delegations associated with particular mixnode
|
||||
@@ -112,7 +133,7 @@ pub enum QueryMsg {
|
||||
mix_identity: IdentityKey,
|
||||
// since `start_after` is user-provided input, we can't use `Addr` as we
|
||||
// can't guarantee it's validated.
|
||||
start_after: Option<String>,
|
||||
start_after: Option<(String, u64)>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
// gets all [paged] delegations associated with particular delegator
|
||||
@@ -143,13 +164,18 @@ pub enum QueryMsg {
|
||||
start_after: Option<IdentityKey>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetRewardedSetHeightsForInterval {
|
||||
interval_id: u32,
|
||||
},
|
||||
GetRewardedSetUpdateDetails {},
|
||||
GetCurrentRewardedSetHeight {},
|
||||
GetCurrentInterval {},
|
||||
GetRewardedSetRefreshBlocks {},
|
||||
GetCurrentEpoch {},
|
||||
GetEpochsInInterval {},
|
||||
QueryOperatorReward {
|
||||
address: String,
|
||||
},
|
||||
QueryDelegatorReward {
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
use crate::{error::MixnetContractError, mixnode::StoredNodeRewardResult, ONE, U128};
|
||||
use az::CheckedCast;
|
||||
use cosmwasm_std::Uint128;
|
||||
use network_defaults::DEFAULT_OPERATOR_INTERVAL_COST;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct NodeEpochRewards {
|
||||
params: NodeRewardParams,
|
||||
result: StoredNodeRewardResult,
|
||||
epoch_id: u32,
|
||||
}
|
||||
|
||||
impl NodeEpochRewards {
|
||||
pub fn new(params: NodeRewardParams, result: StoredNodeRewardResult, epoch_id: u32) -> Self {
|
||||
Self {
|
||||
params,
|
||||
result,
|
||||
epoch_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn epoch_id(&self) -> u32 {
|
||||
self.epoch_id
|
||||
}
|
||||
|
||||
pub fn sigma(&self) -> Uint128 {
|
||||
self.result.sigma()
|
||||
}
|
||||
|
||||
pub fn lambda(&self) -> Uint128 {
|
||||
self.result.lambda()
|
||||
}
|
||||
|
||||
pub fn params(&self) -> NodeRewardParams {
|
||||
self.params
|
||||
}
|
||||
|
||||
pub fn reward(&self) -> Uint128 {
|
||||
self.result.reward()
|
||||
}
|
||||
|
||||
pub fn operator_cost(&self) -> U128 {
|
||||
U128::from_num(self.params.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
|
||||
}
|
||||
|
||||
pub fn node_profit(&self) -> U128 {
|
||||
let reward = U128::from_num(self.reward().u128());
|
||||
if reward < self.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
} else {
|
||||
reward - self.operator_cost()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operator_reward(&self, profit_margin: U128) -> Result<Uint128, MixnetContractError> {
|
||||
let reward = self.node_profit();
|
||||
let operator_base_reward = reward.min(self.operator_cost());
|
||||
let operator_reward = (profit_margin
|
||||
+ (ONE - profit_margin) * U128::from_num(self.lambda().u128())
|
||||
/ U128::from_num(self.sigma().u128()))
|
||||
* reward;
|
||||
|
||||
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0u128));
|
||||
|
||||
if let Some(int_reward) = reward.checked_cast() {
|
||||
Ok(Uint128::new(int_reward))
|
||||
} else {
|
||||
Err(MixnetContractError::CastError)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delegation_reward(
|
||||
&self,
|
||||
delegation_amount: Uint128,
|
||||
profit_margin: U128,
|
||||
epoch_reward_params: EpochRewardParams,
|
||||
) -> Result<Uint128, MixnetContractError> {
|
||||
// change all values into their fixed representations
|
||||
let delegation_amount = U128::from_num(delegation_amount.u128());
|
||||
let circulating_supply = U128::from_num(epoch_reward_params.circulating_supply());
|
||||
|
||||
let scaled_delegation_amount = delegation_amount / circulating_supply;
|
||||
let delegator_reward = (ONE - profit_margin) * scaled_delegation_amount
|
||||
/ U128::from_num(self.sigma().u128())
|
||||
* self.node_profit();
|
||||
|
||||
let reward = delegator_reward.max(U128::ZERO);
|
||||
if let Some(int_reward) = reward.checked_cast() {
|
||||
Ok(Uint128::new(int_reward))
|
||||
} else {
|
||||
Err(MixnetContractError::CastError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct EpochRewardParams {
|
||||
epoch_reward_pool: Uint128,
|
||||
rewarded_set_size: Uint128,
|
||||
active_set_size: Uint128,
|
||||
circulating_supply: Uint128,
|
||||
sybil_resistance_percent: u8,
|
||||
active_set_work_factor: u8,
|
||||
}
|
||||
|
||||
impl EpochRewardParams {
|
||||
pub fn new(
|
||||
epoch_reward_pool: u128,
|
||||
rewarded_set_size: u128,
|
||||
active_set_size: u128,
|
||||
circulating_supply: u128,
|
||||
sybil_resistance_percent: u8,
|
||||
active_set_work_factor: u8,
|
||||
) -> EpochRewardParams {
|
||||
EpochRewardParams {
|
||||
epoch_reward_pool: Uint128::new(epoch_reward_pool),
|
||||
rewarded_set_size: Uint128::new(rewarded_set_size),
|
||||
active_set_size: Uint128::new(active_set_size),
|
||||
circulating_supply: Uint128::new(circulating_supply),
|
||||
sybil_resistance_percent,
|
||||
active_set_work_factor,
|
||||
}
|
||||
}
|
||||
|
||||
// technically it's identical to what would have been derived with a Default implementation,
|
||||
// however, I prefer to be explicit about it, as a `Default::default` value makes no sense
|
||||
// apart from the `ValidatorCacheInner` context, where this value is not going to be touched anyway
|
||||
// (it's guarded behind an `initialised` flag)
|
||||
pub fn new_empty() -> Self {
|
||||
EpochRewardParams {
|
||||
epoch_reward_pool: Uint128::new(0),
|
||||
circulating_supply: Uint128::new(0),
|
||||
sybil_resistance_percent: 0,
|
||||
rewarded_set_size: Uint128::new(0),
|
||||
active_set_size: Uint128::new(0),
|
||||
active_set_work_factor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewarded_set_size(&self) -> u128 {
|
||||
self.rewarded_set_size.u128()
|
||||
}
|
||||
|
||||
pub fn active_set_size(&self) -> u128 {
|
||||
self.active_set_size.u128()
|
||||
}
|
||||
|
||||
pub fn circulating_supply(&self) -> u128 {
|
||||
self.circulating_supply.u128()
|
||||
}
|
||||
|
||||
pub fn epoch_reward_pool(&self) -> u128 {
|
||||
self.epoch_reward_pool.u128()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct NodeRewardParams {
|
||||
reward_blockstamp: u64,
|
||||
uptime: Uint128,
|
||||
in_active_set: bool,
|
||||
}
|
||||
|
||||
impl NodeRewardParams {
|
||||
pub fn new(reward_blockstamp: u64, uptime: u128, in_active_set: bool) -> NodeRewardParams {
|
||||
NodeRewardParams {
|
||||
reward_blockstamp,
|
||||
uptime: Uint128::new(uptime),
|
||||
in_active_set,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operator_cost(&self) -> U128 {
|
||||
U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
|
||||
}
|
||||
|
||||
pub fn uptime(&self) -> u128 {
|
||||
self.uptime.u128()
|
||||
}
|
||||
|
||||
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
|
||||
self.reward_blockstamp = blockstamp;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct RewardParams {
|
||||
pub epoch: EpochRewardParams,
|
||||
pub node: NodeRewardParams,
|
||||
}
|
||||
|
||||
impl RewardParams {
|
||||
pub fn new(epoch: EpochRewardParams, node: NodeRewardParams) -> RewardParams {
|
||||
RewardParams { epoch, node }
|
||||
}
|
||||
|
||||
pub fn omega(&self) -> U128 {
|
||||
// As per keybase://chat/nymtech#tokeneconomics/1179
|
||||
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
|
||||
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
|
||||
|
||||
if self.in_active_set() {
|
||||
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
self.active_set_work_factor() / denom * self.rewarded_set_size()
|
||||
} else {
|
||||
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
ONE / denom * self.rewarded_set_size()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn idle_nodes(&self) -> Uint128 {
|
||||
self.epoch.rewarded_set_size - self.epoch.active_set_size
|
||||
}
|
||||
|
||||
pub fn active_set_work_factor(&self) -> U128 {
|
||||
U128::from_num(self.epoch.active_set_work_factor)
|
||||
}
|
||||
|
||||
pub fn in_active_set(&self) -> bool {
|
||||
self.node.in_active_set
|
||||
}
|
||||
|
||||
pub fn performance(&self) -> U128 {
|
||||
U128::from_num(self.node.uptime.u128()) / U128::from_num(100)
|
||||
}
|
||||
|
||||
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
|
||||
self.node.reward_blockstamp = blockstamp;
|
||||
}
|
||||
|
||||
pub fn epoch_reward_pool(&self) -> u128 {
|
||||
self.epoch.epoch_reward_pool.u128()
|
||||
}
|
||||
|
||||
pub fn rewarded_set_size(&self) -> u128 {
|
||||
self.epoch.rewarded_set_size.u128()
|
||||
}
|
||||
|
||||
pub fn circulating_supply(&self) -> u128 {
|
||||
self.epoch.circulating_supply.u128()
|
||||
}
|
||||
|
||||
pub fn reward_blockstamp(&self) -> u64 {
|
||||
self.node.reward_blockstamp
|
||||
}
|
||||
|
||||
pub fn uptime(&self) -> u128 {
|
||||
self.node.uptime.u128()
|
||||
}
|
||||
|
||||
pub fn one_over_k(&self) -> U128 {
|
||||
ONE / U128::from_num(self.epoch.rewarded_set_size.u128())
|
||||
}
|
||||
|
||||
pub fn alpha(&self) -> U128 {
|
||||
U128::from_num(self.epoch.sybil_resistance_percent) / U128::from_num(100)
|
||||
}
|
||||
}
|
||||
@@ -73,8 +73,7 @@ impl Display for ContractStateParams {
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RewardingResult {
|
||||
pub operator_reward: Uint128,
|
||||
pub total_delegator_reward: Uint128,
|
||||
pub node_reward: Uint128,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -124,7 +123,7 @@ pub type IdentityKey = String;
|
||||
pub type IdentityKeyRef<'a> = &'a str;
|
||||
pub type SphinxKey = String;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
|
||||
pub struct PagedRewardedSetResponse {
|
||||
pub identities: Vec<(IdentityKey, RewardedSetNodeStatus)>,
|
||||
pub start_next_after: Option<IdentityKey>,
|
||||
|
||||
@@ -11,4 +11,7 @@ mixnet-contract-common = { path = "../mixnet-contract" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
cw-storage-plus = "0.11.1"
|
||||
config = {path = "../../config"}
|
||||
config = { path = "../../config" }
|
||||
|
||||
[dev-dependencies]
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
@@ -15,6 +15,7 @@ pub const VESTING_GATEWAY_BONDING_EVENT_TYPE: &str = "vesting_gateway_bonding";
|
||||
pub const VESTING_GATEWAY_UNBONDING_EVENT_TYPE: &str = "vesting_gateway_unbonding";
|
||||
pub const VESTING_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding";
|
||||
pub const VESTING_MIXNODE_UNBONDING_EVENT_TYPE: &str = "vesting_mixnode_unbonding";
|
||||
pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixnode_config";
|
||||
|
||||
pub const TRACK_MIXNODE_UNBOND_EVENT_TYPE: &str = "track_mixnode_unbond";
|
||||
pub const TRACK_GATEWAY_UNBOND_EVENT_TYPE: &str = "track_gateway_unbond";
|
||||
@@ -108,6 +109,10 @@ pub fn new_vesting_mixnode_bonding_event() -> Event {
|
||||
Event::new(VESTING_MIXNODE_BONDING_EVENT_TYPE)
|
||||
}
|
||||
|
||||
pub fn new_vesting_update_mixnode_config_event() -> Event {
|
||||
Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE)
|
||||
}
|
||||
|
||||
pub fn new_vesting_mixnode_unbonding_event() -> Event {
|
||||
Event::new(VESTING_MIXNODE_UNBONDING_EVENT_TYPE)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,74 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::Coin;
|
||||
use cosmwasm_std::{Coin, Timestamp};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod events;
|
||||
pub mod messages;
|
||||
|
||||
pub fn one_unym() -> Coin {
|
||||
pub fn one_ucoin() -> Coin {
|
||||
Coin::new(1, DENOM)
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../../../nym-wallet/src/types/rust/period.ts")
|
||||
)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, JsonSchema)]
|
||||
pub enum Period {
|
||||
Before,
|
||||
In(usize),
|
||||
After,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct PledgeData {
|
||||
amount: Coin,
|
||||
block_time: Timestamp,
|
||||
}
|
||||
|
||||
impl PledgeData {
|
||||
pub fn amount(&self) -> Coin {
|
||||
self.amount.clone()
|
||||
}
|
||||
|
||||
pub fn block_time(&self) -> Timestamp {
|
||||
self.block_time
|
||||
}
|
||||
|
||||
pub fn new(amount: Coin, block_time: Timestamp) -> Self {
|
||||
Self { amount, block_time }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct OriginalVestingResponse {
|
||||
amount: Coin,
|
||||
number_of_periods: usize,
|
||||
period_duration: u64,
|
||||
}
|
||||
|
||||
impl OriginalVestingResponse {
|
||||
pub fn amount(&self) -> Coin {
|
||||
self.amount.clone()
|
||||
}
|
||||
|
||||
pub fn number_of_periods(&self) -> usize {
|
||||
self.number_of_periods
|
||||
}
|
||||
|
||||
pub fn period_duration(&self) -> u64 {
|
||||
self.period_duration
|
||||
}
|
||||
|
||||
pub fn new(amount: Coin, number_of_periods: usize, period_duration: u64) -> Self {
|
||||
Self {
|
||||
amount,
|
||||
number_of_periods,
|
||||
period_duration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,9 @@ impl VestingSpecification {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
UpdateMixnodeConfig {
|
||||
profit_margin_percent: u8,
|
||||
},
|
||||
UpdateMixnetAddress {
|
||||
address: String,
|
||||
},
|
||||
@@ -145,4 +148,7 @@ pub enum QueryMsg {
|
||||
GetGateway {
|
||||
address: String,
|
||||
},
|
||||
GetCurrentVestingPeriod {
|
||||
address: String,
|
||||
},
|
||||
}
|
||||
|
||||