diff --git a/.github/workflows/connect-desktop-ci.yml b/.github/workflows/connect-desktop-ci.yml index 569203cfff..76f39572dc 100644 --- a/.github/workflows/connect-desktop-ci.yml +++ b/.github/workflows/connect-desktop-ci.yml @@ -23,8 +23,14 @@ jobs: node-version: 18 - name: Install Yarn run: npm install -g yarn - - run: yarn + + - name: Install project dependencies + run: cd ../.. && yarn --network-timeout 100000 + + - name: Install app dependencies + run: yarn continue-on-error: true + - name: Set environment from the example run: cp .env.sample .env - run: yarn storybook:build diff --git a/.github/workflows/nym-connect-publish-macos.yml b/.github/workflows/nym-connect-publish-macos.yml index fd52c251e2..21ec00ce1e 100644 --- a/.github/workflows/nym-connect-publish-macos.yml +++ b/.github/workflows/nym-connect-publish-macos.yml @@ -15,8 +15,15 @@ jobs: fail-fast: false matrix: platform: [macos-latest] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + version: ${{ steps.release-info.outputs.version }} + filename: ${{ steps.release-info.outputs.filename }} + file_hash: ${{ steps.release-info.outputs.file_hash }} + steps: - uses: actions/checkout@v2 @@ -85,10 +92,38 @@ jobs: run: | security delete-keychain $RUNNER_TEMP/app-signing.keychain-db - - name: Upload to release based on tag name + - id: create-release + name: Upload to release based on tag name uses: softprops/action-gh-release@v1 if: github.event_name == 'release' with: files: | nym-connect/desktop/target/release/bundle/dmg/*.dmg nym-connect/desktop/target/release/bundle/macos/*.app.tar.gz* + - id: release-info + name: Prepare release info + run: | + semver="${${{ github.ref_name }}##nym-connect-}" && semver="${semver##v}" + echo "version=$semver" >> "$GITHUB_OUTPUT" + echo "filename=nym-connect_$version_x64.dmg" >> "$GITHUB_OUTPUT" + echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/dmg/nym-connect_*_x64.dmg') }}" >> "$GITHUB_OUTPUT" + + push-release-data: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-tauri + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-tauri.outputs.release_id }} + release_date: ${{ needs.publish-tauri.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md + archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect.app.tar.gz + sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect.app.tar.gz.sig + version: ${{ needs.publish-tauri.outputs.version }} + filename: ${{ needs.publish-tauri.outputs.filename }} + file_hash: ${{ needs.publish-tauri.outputs.file_hash }} + name: NymConnect + category: connect + platform: MacOS + secrets: inherit diff --git a/.github/workflows/nym-connect-publish-ubuntu.yml b/.github/workflows/nym-connect-publish-ubuntu.yml index dbeef8a378..f71c277e0c 100644 --- a/.github/workflows/nym-connect-publish-ubuntu.yml +++ b/.github/workflows/nym-connect-publish-ubuntu.yml @@ -15,8 +15,15 @@ jobs: fail-fast: false matrix: platform: [custom-runner-linux] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + version: ${{ steps.release-info.outputs.version }} + filename: ${{ steps.release-info.outputs.filename }} + file_hash: ${{ steps.release-info.outputs.file_hash }} + steps: - uses: actions/checkout@v2 @@ -62,10 +69,38 @@ jobs: path: nym-connect/desktop/target/release/bundle/appimage/nym-connect_1*_amd64.AppImage retention-days: 30 - - name: Upload to release based on tag name + - id: create-release + name: Upload to release based on tag name uses: softprops/action-gh-release@v1 if: github.event_name == 'release' with: files: | nym-connect/desktop/target/release/bundle/appimage/*.AppImage nym-connect/desktop/target/release/bundle/appimage/*.AppImage.tar.gz* + - id: release-info + name: Prepare release info + run: | + semver="${${{ github.ref_name }}##nym-connect-}" && semver="${semver##v}" + echo "version=$semver" >> "$GITHUB_OUTPUT" + echo "filename=nym-connect_$version_amd64.AppImage" >> "$GITHUB_OUTPUT" + echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/appimage/nym-connect_*_amd64.AppImage') }}" >> "$GITHUB_OUTPUT" + + push-release-data: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-tauri + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-tauri.outputs.release_id }} + release_date: ${{ needs.publish-tauri.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md + archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz + sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz.sig + version: ${{ needs.publish-tauri.outputs.version }} + filename: ${{ needs.publish-tauri.outputs.filename }} + file_hash: ${{ needs.publish-tauri.outputs.file_hash }} + name: NymConnect + category: connect + platform: Ubuntu + secrets: inherit diff --git a/.github/workflows/nym-connect-publish-windows10.yml b/.github/workflows/nym-connect-publish-windows10.yml index 0a7911c6ed..8c793d88d0 100644 --- a/.github/workflows/nym-connect-publish-windows10.yml +++ b/.github/workflows/nym-connect-publish-windows10.yml @@ -15,8 +15,15 @@ jobs: fail-fast: false matrix: platform: [windows10] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + version: ${{ steps.release-info.outputs.version }} + filename: ${{ steps.release-info.outputs.filename }} + file_hash: ${{ steps.release-info.outputs.file_hash }} + steps: - name: Clean up first continue-on-error: true @@ -81,10 +88,38 @@ jobs: path: nym-connect/desktop/target/release/bundle/msi/nym-connect_1*_x64_en-US.msi retention-days: 30 - - name: Upload to release based on tag name + - id: create-release + name: Upload to release based on tag name uses: softprops/action-gh-release@v1 if: github.event_name == 'release' with: files: | nym-connect/desktop/target/release/bundle/msi/*.msi nym-connect/desktop/target/release/bundle/msi/*.msi.zip* + - id: release-info + name: Prepare release info + run: | + semver="${${{ github.ref_name }}##nym-connect-}" && semver="${semver##v}" + echo "version=$semver" >> "$GITHUB_OUTPUT" + echo "filename=nym-connect_$version_x64_en-US.msi" >> "$GITHUB_OUTPUT" + echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/msi/nym-connect_*_x64_en-US.msi') }}" >> "$GITHUB_OUTPUT" + + push-release-data: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-tauri + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-tauri.outputs.release_id }} + release_date: ${{ needs.publish-tauri.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md + archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip + sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip.sig + version: ${{ needs.publish-tauri.outputs.version }} + filename: ${{ needs.publish-tauri.outputs.filename }} + file_hash: ${{ needs.publish-tauri.outputs.file_hash }} + name: NymConnect + category: connect + platform: Windows + secrets: inherit diff --git a/.github/workflows/nym-release-publish.yml b/.github/workflows/nym-release-publish.yml index a73eabfcb3..9252d7122d 100644 --- a/.github/workflows/nym-release-publish.yml +++ b/.github/workflows/nym-release-publish.yml @@ -2,17 +2,17 @@ name: Publish Nym binaries on: workflow_dispatch: - inputs: + inputs: add_tokio_unstable: description: 'True to add RUSTFLAGS="--cfg tokio_unstable"' required: true default: false - type: boolean + type: boolean release: types: [created] - + env: - NETWORK: mainnet + NETWORK: mainnet jobs: publish-nym: @@ -21,15 +21,33 @@ jobs: fail-fast: false matrix: platform: [custom-runner-linux] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + client_hash: ${{ steps.binary-hashes.outputs.client_hash }} + mixnode_hash: ${{ steps.binary-hashes.outputs.mixnode_hash }} + gateway_hash: ${{ steps.binary-hashes.outputs.gateway_hash }} + socks5_hash: ${{ steps.binary-hashes.outputs.socks5_hash }} + netreq_hash: ${{ steps.binary-hashes.outputs.netreq_hash }} + cli_hash: ${{ steps.binary-hashes.outputs.cli_hash }} + netstat_hash: ${{ steps.binary-hashes.outputs.netstat_hash }} + client_version: ${{ steps.binary-versions.outputs.client_version }} + mixnode_version: ${{ steps.binary-versions.outputs.mixnode_version }} + gateway_version: ${{ steps.binary-versions.outputs.gateway_version }} + socks5_version: ${{ steps.binary-versions.outputs.socks5_version }} + netreq_version: ${{ steps.binary-versions.outputs.netreq_version }} + cli_version: ${{ steps.binary-versions.outputs.cli_version }} + netstat_version: ${{ steps.binary-versions.outputs.netstat_version }} + steps: - uses: actions/checkout@v3 - name: Install Dependencies (Linux) - run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools + run: sudo apt-get update && sudo apt-get -y install ripgrep libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools continue-on-error: true - + - name: Sets env vars for tokio if set in manual dispatch inputs run: | echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV @@ -62,7 +80,8 @@ jobs: target/release/nym-cli retention-days: 30 - - name: Upload to release based on tag name + - id: create-release + name: Upload to release based on tag name uses: softprops/action-gh-release@v1 if: github.event_name == 'release' with: @@ -76,3 +95,150 @@ jobs: target/release/nym-network-requester target/release/nym-network-statistics target/release/nym-cli + + - id: release-info + name: Prepare release info + run: | + semver="${${{ github.ref_name }}##nym-binaries-}" && semver="${semver##v}" + echo "version=$semver" >> "$GITHUB_OUTPUT" + + - id: binary-hashes + name: Generate binary hashes + run: | + echo "client_hash=${{ hashFiles('target/release/nym-client') }}" >> "$GITHUB_OUTPUT" + echo "mixnode_hash=${{ hashFiles('target/release/nym-mixnode') }}" >> "$GITHUB_OUTPUT" + echo "gateway_hash=${{ hashFiles('target/release/nym-gateway') }}" >> "$GITHUB_OUTPUT" + echo "socks5_hash=${{ hashFiles('target/release/nym-socks5-client') }}" >> "$GITHUB_OUTPUT" + echo "netreq_hash=${{ hashFiles('target/release/nym-network-requester') }}" >> "$GITHUB_OUTPUT" + echo "cli_hash=${{ hashFiles('target/release/nym-cli') }}" >> "$GITHUB_OUTPUT" + echo "netstat_hash=${{ hashFiles('target/release/nym-network-statistics') }}" >> "$GITHUB_OUTPUT" + + - id: binary-versions + name: Get binary versions + run: | + v=$(rg '^version = "(.*)"' -or '$1' clients/native/Cargo.toml) && echo "client_version=$v" >> "$GITHUB_OUTPUT" + v=$(rg '^version = "(.*)"' -or '$1' mixnode/Cargo.toml) && echo "mixnode_version=$v" >> "$GITHUB_OUTPUT" + v=$(rg '^version = "(.*)"' -or '$1' gateway/Cargo.toml) && echo "gateway_version=$v" >> "$GITHUB_OUTPUT" + v=$(rg '^version = "(.*)"' -or '$1' clients/socks5/Cargo.toml) && echo "socks5_version=$v" >> "$GITHUB_OUTPUT" + v=$(rg '^version = "(.*)"' -or '$1' service-providers/network-requester/Cargo.toml) && echo "netreq_version=$v" >> "$GITHUB_OUTPUT" + v=$(rg '^version = "(.*)"' -or '$1' tools/nym-cli/Cargo.toml) && echo "cli_version=$v" >> "$GITHUB_OUTPUT" + v=$(rg '^version = "(.*)"' -or '$1' service-providers/network-statistics/Cargo.toml) && echo "netstat_version=$v" >> "$GITHUB_OUTPUT" + + push-release-data-client: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.client_version }} + filename: nym-client + file_hash: ${{ needs.publish-nym.outputs.client_hash }} + name: Client + category: binaries + secrets: inherit + + push-release-data-mixnode: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.mixnode_version }} + filename: nym-mixnode + file_hash: ${{ needs.publish-nym.outputs.mixnode_hash }} + name: Mixnode + category: binaries + secrets: inherit + + push-release-data-gateway: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.gateway_version }} + filename: nym-gateway + file_hash: ${{ needs.publish-nym.outputs.gateway_hash }} + name: Gateway + category: binaries + secrets: inherit + + push-release-data-socks5: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.socks5_version }} + filename: nym-socks5-client + file_hash: ${{ needs.publish-nym.outputs.socks5_hash }} + name: Socks5 Client + category: binaries + secrets: inherit + + push-release-data-network-requester: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.netreq_version }} + filename: nym-network-requester + file_hash: ${{ needs.publish-nym.outputs.netreq_hash }} + name: Network Requester + category: binaries + secrets: inherit + + push-release-data-cli: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.cli_version }} + filename: nym-cli + file_hash: ${{ needs.publish-nym.outputs.cli_hash }} + name: Cli + category: binaries + secrets: inherit + + push-release-data-network-stat: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.netstat_version }} + filename: nym-network-statistics + file_hash: ${{ needs.publish-nym.outputs.netstat_hash }} + name: Network Statistics + category: binaries + secrets: inherit diff --git a/.github/workflows/nym-wallet-publish-macos.yml b/.github/workflows/nym-wallet-publish-macos.yml index 1023af55ba..5a87e38d1c 100644 --- a/.github/workflows/nym-wallet-publish-macos.yml +++ b/.github/workflows/nym-wallet-publish-macos.yml @@ -15,8 +15,15 @@ jobs: fail-fast: false matrix: platform: [macos-latest] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + version: ${{ steps.release-info.outputs.version }} + filename: ${{ steps.release-info.outputs.filename }} + file_hash: ${{ steps.release-info.outputs.file_hash }} + steps: - uses: actions/checkout@v2 @@ -84,11 +91,40 @@ jobs: if: ${{ always() }} run: | security delete-keychain $RUNNER_TEMP/app-signing.keychain-db - - - name: Upload to release based on tag name + - id: create-release + name: Upload to release based on tag name uses: softprops/action-gh-release@v1 if: github.event_name == 'release' with: files: | nym-wallet/target/release/bundle/dmg/*.dmg nym-wallet/target/release/bundle/macos/*.app.tar.gz* + + - id: release-info + name: Prepare release info + run: | + ref=${{ github.ref_name }} + semver="${ref##nym-wallet-}" && semver="${semver##v}" + echo "version=${semver}" >> "$GITHUB_OUTPUT" + echo "filename=nym-wallet_${version}_x64.dmg" >> "$GITHUB_OUTPUT" + echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/dmg/nym-wallet_*_x64.dmg') }}" >> "$GITHUB_OUTPUT" + + push-release-data: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-tauri + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-tauri.outputs.release_id }} + release_date: ${{ needs.publish-tauri.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md + archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet.app.tar.gz + sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet.app.tar.gz.sig + version: ${{ needs.publish-tauri.outputs.version }} + filename: ${{ needs.publish-tauri.outputs.filename }} + file_hash: ${{ needs.publish-tauri.outputs.file_hash }} + name: Wallet + category: wallet + platform: MacOS + secrets: inherit diff --git a/.github/workflows/nym-wallet-publish-ubuntu.yml b/.github/workflows/nym-wallet-publish-ubuntu.yml index bb653d7593..027908e2f6 100644 --- a/.github/workflows/nym-wallet-publish-ubuntu.yml +++ b/.github/workflows/nym-wallet-publish-ubuntu.yml @@ -15,8 +15,15 @@ jobs: fail-fast: false matrix: platform: [custom-runner-linux] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + version: ${{ steps.release-info.outputs.version }} + filename: ${{ steps.release-info.outputs.filename }} + file_hash: ${{ steps.release-info.outputs.file_hash }} + steps: - uses: actions/checkout@v2 @@ -61,10 +68,40 @@ jobs: path: nym-wallet/target/release/bundle/appimage/nym-wallet*.AppImage.tar.gz retention-days: 30 - - name: Upload to release based on tag name + - id: create-release + name: Upload to release based on tag name uses: softprops/action-gh-release@v1 if: github.event_name == 'release' with: files: | nym-wallet/target/release/bundle/appimage/*.AppImage nym-wallet/target/release/bundle/appimage/*.AppImage.tar.gz* + + - id: release-info + name: Prepare release info + run: | + ref=${{ github.ref_name }} + semver="${ref##nym-wallet-}" && semver="${semver##v}" + echo "version=${semver}" >> "$GITHUB_OUTPUT" + echo "filename=nym-wallet_${version}_amd64.AppImage" >> "$GITHUB_OUTPUT" + echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/appimage/nym-wallet_*_amd64.AppImage') }}" >> "$GITHUB_OUTPUT" + + push-release-data: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-tauri + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-tauri.outputs.release_id }} + release_date: ${{ needs.publish-tauri.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md + archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz + sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz.sig + version: ${{ needs.publish-tauri.outputs.version }} + filename: ${{ needs.publish-tauri.outputs.filename }} + file_hash: ${{ needs.publish-tauri.outputs.file_hash }} + name: Wallet + category: wallet + platform: Ubuntu + secrets: inherit diff --git a/.github/workflows/nym-wallet-publish-windows10.yml b/.github/workflows/nym-wallet-publish-windows10.yml index 92ce582d32..974de92435 100644 --- a/.github/workflows/nym-wallet-publish-windows10.yml +++ b/.github/workflows/nym-wallet-publish-windows10.yml @@ -15,8 +15,15 @@ jobs: fail-fast: false matrix: platform: [windows10] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + version: ${{ steps.release-info.outputs.version }} + filename: ${{ steps.release-info.outputs.filename }} + file_hash: ${{ steps.release-info.outputs.file_hash }} + steps: - name: Clean up first continue-on-error: true @@ -81,10 +88,40 @@ jobs: path: nym-wallet/target/release/bundle/msi/nym-wallet_1.*.msi retention-days: 30 - - name: Upload to release based on tag name + - id: create-release + name: Upload to release based on tag name uses: softprops/action-gh-release@v1 if: github.event_name == 'release' with: files: | nym-wallet/target/release/bundle/msi/*.msi nym-wallet/target/release/bundle/msi/*.msi.zip* + + - id: release-info + name: Prepare release info + run: | + ref=${{ github.ref_name }} + semver="${ref##nym-wallet-}" && semver="${semver##v}" + echo "version=${semver}" >> "$GITHUB_OUTPUT" + echo "filename=nym-wallet_${version}_x64_en-US.msi" >> "$GITHUB_OUTPUT" + echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/msi/nym-wallet_*_x64_en-US.msi') }}" >> "$GITHUB_OUTPUT" + + push-release-data: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-tauri + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-tauri.outputs.release_id }} + release_date: ${{ needs.publish-tauri.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md + archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip + sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip.sig + version: ${{ needs.publish-tauri.outputs.version }} + filename: ${{ needs.publish-tauri.outputs.filename }} + file_hash: ${{ needs.publish-tauri.outputs.file_hash }} + name: Wallet + category: wallet + platform: Windows + secrets: inherit diff --git a/.github/workflows/nym-wallet-storybook.yml b/.github/workflows/nym-wallet-storybook.yml index 88f1230895..54f5804cbf 100644 --- a/.github/workflows/nym-wallet-storybook.yml +++ b/.github/workflows/nym-wallet-storybook.yml @@ -19,6 +19,12 @@ jobs: node-version: 18 - name: Setup yarn run: npm install -g yarn + - name: Install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - name: Build dependencies run: yarn && yarn build - name: Build storybook diff --git a/.github/workflows/push-release-data.yml b/.github/workflows/push-release-data.yml index 0e9a931bc0..db115af817 100644 --- a/.github/workflows/push-release-data.yml +++ b/.github/workflows/push-release-data.yml @@ -6,7 +6,7 @@ env: on: workflow_call: - inputs: &wf-inputs + inputs: release_tag: required: true description: Release tag @@ -61,7 +61,48 @@ on: type: string workflow_dispatch: inputs: - <<: *wf-inputs + # ⚠ since inputs are limited to 10 max for workflow_dispatch + # some properties were omitted + version: + required: true + description: Release version (semver) + type: string + default: '1.0.0' + release_id: + required: true + description: Release ID + type: string + default: '1234' + release_date: + required: true + description: Release date + type: string + default: '2023-06-26T10:09:16Z' + download_base_url: + required: true + description: Download base URL + type: string + default: 'https://github.com/nymtech/nym/releases/download/nym-wallet-v1.0.0' + changelog_url: + required: true + description: Changelog URL + type: string + default: 'https://github.com/nymtech/nym/blob/nym-wallet-v1.0.0/nym-wallet/CHANGELOG.md' + filename: + required: true + description: Binary file name + type: string + default: 'nym-wallet_1.0.0_amd64.AppImage' + file_hash: + required: true + description: Binary hash (sha256) + type: string + default: 'xxx' + name: + required: true + description: Name + type: string + default: 'Wallet' category: required: true description: Category @@ -87,18 +128,24 @@ jobs: runs-on: custom-runner-linux steps: + - name: Release info + run: | + echo "version: ${{ inputs.version }}" + echo "tag: ${{ inputs.release_tag }}" + - id: get_sig name: Get sig if: ${{ inputs.sig_url != null }} run: | output=$(curl -LsSf ${{ inputs.sig_url }}) echo "sig=$output" >> "$GITHUB_OUTPUT" - - name: Push download data + - id: strapi-request + name: Strapi request uses: fjogeleit/http-request-action@v1 with: url: ${{ env.strapi_download_url }} method: 'POST' - # bearerToken: ${{ secrets.STRAPI_AUTH_TOKEN }} TODO + bearerToken: ${{ secrets.STRAPI_API_TOKEN_RELEASES }} customHeaders: '{"Content-Type": "application/json"}' data: | { @@ -116,26 +163,34 @@ jobs: "sig": "${{ steps.get_sig.outputs.sig }}" } } + - name: Strapi Response + run: | + echo ${{ steps.strapi-request.outputs.response }} push-update-data: name: Push update data to Strapi - runs-on: ${{ matrix.platform }} + runs-on: custom-runner-linux # only push update data for tauri apps (desktop wallet and NC) if: ${{ inputs.category == 'wallet' || inputs.category == 'connect' }} steps: + - name: Release info + run: | + echo "version: ${{ inputs.version }}" + echo "tag: ${{ inputs.release_tag }}" - id: get_sig name: Get sig if: ${{ inputs.sig_url != null }} run: | output=$(curl -LsSf ${{ inputs.sig_url }}) echo "sig=$output" >> "$GITHUB_OUTPUT" - - name: Push update data + - id: strapi-request + name: Strapi request uses: fjogeleit/http-request-action@v1 with: url: ${{ env.strapi_updater_url }} method: 'POST' - # bearerToken: ${{ secrets.STRAPI_AUTH_TOKEN }} TODO + bearerToken: ${{ secrets.STRAPI_API_TOKEN_RELEASES }} customHeaders: '{"Content-Type": "application/json"}' data: | { @@ -152,3 +207,6 @@ jobs: "sig": "${{ steps.get_sig.outputs.sig }}" } } + - name: Strapi Response + run: | + echo ${{ steps.strapi-request.outputs.response }} diff --git a/.github/workflows/sdk-publish.yml b/.github/workflows/sdk-publish.yml index 7ef5f90b1b..40b334e6ac 100644 --- a/.github/workflows/sdk-publish.yml +++ b/.github/workflows/sdk-publish.yml @@ -32,4 +32,4 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} working-directory: ./sdk/typescript/packages/sdk - run: scripts/publish.sh \ No newline at end of file + run: scripts/publish.sh diff --git a/Cargo.lock b/Cargo.lock index 3f87508d0b..6d8e1dadd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4041,8 +4041,12 @@ name = "nym-name-service-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-contracts-common", "schemars", "serde", + "thiserror", ] [[package]] @@ -4092,6 +4096,7 @@ dependencies = [ "pretty_env_logger", "publicsuffix", "rand 0.7.3", + "regex", "reqwest", "serde", "serde_json", @@ -5706,13 +5711,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.1", + "regex-syntax 0.7.2", ] [[package]] @@ -5732,9 +5737,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "reqwest" diff --git a/clients/credential/README.md b/clients/credential/README.md index e332e42555..9d82b4b839 100644 --- a/clients/credential/README.md +++ b/clients/credential/README.md @@ -15,9 +15,9 @@ The credential binary is still experimental software. The infrastructure for usi From the project's root directory, run: ``` -cargo build -p credential +cargo build -p nym-credential-client ``` -which generates the `credential` binary in `target/debug/credential`. +which generates the `nym-credential-client` binary in `target/debug/nym-credential-client`. ### Running @@ -25,7 +25,7 @@ which generates the `credential` binary in `target/debug/credential`. For example, you can get a credential worth 3 nym (3000000 unym) in a socks5 client that was already initialized like so: ``` -./target/debug/credential --config-env-file envs/sandbox.env --client-home-directory ~/.nym/socks5-clients/cred_client --nyxd-url https://sandbox-validator1.nymtech.net --mnemonic $MNEMONIC --recovery-dir /tmp/recovery --amount 3000000 +./target/debug/nym-credential-client --config-env-file envs/sandbox.env --client-home-directory ~/.nym/socks5-clients/cred_client --nyxd-url https://sandbox-validator1.nymtech.net --mnemonic $MNEMONIC --recovery-dir /tmp/recovery --amount 3000000 ``` More information regarding how to run the binary can be found by running it with the `--help` argument. diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index 735b314185..08c4f9592d 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -24,7 +24,6 @@ use std::net::IpAddr; pub(crate) mod init; pub(crate) mod run; -pub(crate) mod upgrade; lazy_static! { pub static ref PRETTY_BUILD_INFORMATION: String = @@ -51,10 +50,9 @@ pub(crate) struct Cli { pub(crate) enum Commands { /// Initialise a Nym client. Do this first! Init(init::Init), + /// Run the Nym client with provided configuration client optionally overriding set parameters Run(run::Run), - /// Try to upgrade the client - Upgrade(upgrade::Upgrade), /// Generate shell completions Completions(ArgShell), @@ -81,7 +79,6 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Box init::execute(m).await?, Commands::Run(m) => run::execute(m).await?, - Commands::Upgrade(m) => upgrade::execute(m), Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name), } diff --git a/clients/native/src/commands/upgrade.rs b/clients/native/src/commands/upgrade.rs deleted file mode 100644 index b4c2d45ad3..0000000000 --- a/clients/native/src/commands/upgrade.rs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::client::config::Config; -use crate::commands::try_load_current_config; -use clap::Args; -use nym_bin_common::version_checker::Version; -use std::process; - -fn unimplemented_upgrade(current_version: &Version, config_version: &Version) -> ! { - eprintln!("Cannot perform upgrade from {config_version} to {current_version} as it hasn't been implemented yet"); - process::exit(1) -} - -#[derive(Args, Clone)] -pub(crate) struct Upgrade { - /// Id of the nym-client we want to upgrade - #[clap(long)] - id: String, -} - -fn parse_config_version(config: &Config) -> Version { - let version = Version::parse(&config.base.client.version).unwrap_or_else(|err| { - eprintln!("failed to parse client version! - {err}"); - process::exit(1) - }); - - if version.is_prerelease() || !version.build.is_empty() { - eprintln!( - "Trying to upgrade from a non-released version {version}. This is not supported!" - ); - process::exit(1) - } - - version -} - -fn parse_package_version() -> Version { - let version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); - - // technically this is not a correct way of checking it as a released version might contain valid build identifiers - // however, we are not using them ourselves at the moment and hence it should be fine. - // if we change our mind, we could easily tweak this code - if version.is_prerelease() || !version.build.is_empty() { - eprintln!("Trying to upgrade to a non-released version {version}. This is not supported!"); - process::exit(1) - } - - version -} - -fn do_upgrade(config: Config, _args: &Upgrade, package_version: &Version) { - let config_version = parse_config_version(&config); - if &config_version == package_version { - println!("You're using the most recent version!"); - return; - } - - unimplemented_upgrade(package_version, &config_version) -} - -pub(crate) fn execute(args: &Upgrade) { - let package_version = parse_package_version(); - - let id = &args.id; - - let existing_config = try_load_current_config(id).unwrap_or_else(|err| { - eprintln!("failed to load existing config file! - {err}"); - process::exit(1) - }); - - if existing_config.base.client.version.is_empty() { - eprintln!("the existing configuration file does not seem to contain version number."); - process::exit(1); - } - - do_upgrade(existing_config, args, &package_version) -} diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 61e992d4d0..2b5fe425b6 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -24,7 +24,6 @@ use std::error::Error; pub mod init; pub(crate) mod run; -pub(crate) mod upgrade; lazy_static! { pub static ref PRETTY_BUILD_INFORMATION: String = @@ -55,9 +54,6 @@ pub(crate) enum Commands { /// Run the Nym client with provided configuration client optionally overriding set parameters Run(run::Run), - /// Try to upgrade the client - Upgrade(upgrade::Upgrade), - /// Generate shell completions Completions(ArgShell), @@ -84,7 +80,6 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Box init::execute(m).await?, Commands::Run(m) => run::execute(m).await?, - Commands::Upgrade(m) => upgrade::execute(m), Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name), } diff --git a/clients/socks5/src/commands/upgrade.rs b/clients/socks5/src/commands/upgrade.rs deleted file mode 100644 index 00ffb9252b..0000000000 --- a/clients/socks5/src/commands/upgrade.rs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::commands::try_load_current_config; -use crate::config::Config; -use clap::Args; -use nym_bin_common::version_checker::Version; -use std::process; - -fn unimplemented_upgrade(current_version: &Version, config_version: &Version) -> ! { - eprintln!("Cannot perform upgrade from {config_version} to {current_version} as it hasn't been implemented yet"); - process::exit(1) -} - -#[derive(Args, Clone)] -pub(crate) struct Upgrade { - /// Id of the nym-client we want to upgrade - #[clap(long)] - id: String, -} - -fn parse_config_version(config: &Config) -> Version { - let version = Version::parse(&config.core.base.client.version).unwrap_or_else(|err| { - eprintln!("failed to parse client version! - {err}"); - process::exit(1) - }); - - if version.is_prerelease() || !version.build.is_empty() { - eprintln!( - "Trying to upgrade from a non-released version {version}. This is not supported!" - ); - process::exit(1) - } - - version -} - -fn parse_package_version() -> Version { - let version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); - - // technically this is not a correct way of checking it as a released version might contain valid build identifiers - // however, we are not using them ourselves at the moment and hence it should be fine. - // if we change our mind, we could easily tweak this code - if version.is_prerelease() || !version.build.is_empty() { - eprintln!("Trying to upgrade to a non-released version {version}. This is not supported!"); - process::exit(1) - } - - version -} - -fn do_upgrade(config: Config, _args: &Upgrade, package_version: &Version) { - let config_version = parse_config_version(&config); - if &config_version == package_version { - println!("You're using the most recent version!"); - return; - } - - unimplemented_upgrade(package_version, &config_version) -} - -pub(crate) fn execute(args: &Upgrade) { - let package_version = parse_package_version(); - - let id = &args.id; - - let existing_config = try_load_current_config(id).unwrap_or_else(|err| { - eprintln!("failed to load existing config file! - {err}"); - process::exit(1) - }); - - if existing_config.core.base.client.version.is_empty() { - eprintln!("the existing configuration file does not seem to contain version number."); - process::exit(1); - } - - do_upgrade(existing_config, args, &package_version) -} diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index d0ba9718c5..823fbbb252 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -2580,8 +2580,12 @@ name = "nym-name-service-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-contracts-common", "schemars", "serde", + "thiserror", ] [[package]] diff --git a/common/client-libs/validator-client/src/nyxd/traits/name_service_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/name_service_query_client.rs index 95f0a2517e..0823d97d6a 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/name_service_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/name_service_query_client.rs @@ -4,7 +4,7 @@ use nym_contracts_common::ContractBuildInformation; use nym_name_service_common::{ msg::QueryMsg as NameQueryMsg, response::{ConfigResponse, NamesListResponse, PagedNamesListResponse}, - Address, NameEntry, NameId, + Address, NameId, RegisteredName, }; use serde::Deserialize; @@ -21,7 +21,7 @@ pub trait NameServiceQueryClient { .await } - async fn get_name_entry(&self, name_id: NameId) -> Result { + async fn get_name_entry(&self, name_id: NameId) -> Result { self.query_name_service_contract(NameQueryMsg::NameId { name_id }) .await } @@ -54,14 +54,14 @@ pub trait NameServiceQueryClient { .await } - async fn get_all_names(&self) -> Result, NyxdError> { + async fn get_all_names(&self) -> Result, NyxdError> { let mut services = Vec::new(); let mut start_after = None; loop { let mut paged_response = self.get_names_paged(start_after.take(), None).await?; - let last_id = paged_response.names.last().map(|serv| serv.name_id); + let last_id = paged_response.names.last().map(|serv| serv.id); services.append(&mut paged_response.names); if let Some(start_after_res) = last_id { diff --git a/common/client-libs/validator-client/src/nyxd/traits/name_service_signing_client.rs b/common/client-libs/validator-client/src/nyxd/traits/name_service_signing_client.rs index 4e4e43ee77..1235043094 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/name_service_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/name_service_signing_client.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use async_trait::async_trait; -use nym_name_service_common::{msg::ExecuteMsg as NameExecuteMsg, Address, NameId, NymName}; +use nym_contracts_common::signing::MessageSignature; +use nym_name_service_common::{msg::ExecuteMsg as NameExecuteMsg, NameDetails, NameId, NymName}; use crate::nyxd::{ coin::Coin, cosmwasm_client::types::ExecuteResult, error::NyxdError, Fee, NyxdClient, @@ -20,14 +21,17 @@ pub trait NameServiceSigningClient { async fn register_name( &self, - name: NymName, - address: Address, + name: NameDetails, + owner_signature: MessageSignature, deposit: Coin, fee: Option, ) -> Result { self.execute_name_service_contract( fee, - NameExecuteMsg::Register { name, address }, + NameExecuteMsg::Register { + name, + owner_signature, + }, vec![deposit], ) .await diff --git a/common/commands/src/validator/mixnet/operators/name/register.rs b/common/commands/src/validator/mixnet/operators/name/register.rs index 6458faf8a6..e246e4241a 100644 --- a/common/commands/src/validator/mixnet/operators/name/register.rs +++ b/common/commands/src/validator/mixnet/operators/name/register.rs @@ -1,6 +1,7 @@ use clap::Parser; use log::{error, info}; -use nym_name_service_common::{Address, Coin, NymName}; +use nym_contracts_common::signing::MessageSignature; +use nym_name_service_common::{Address, Coin, NameDetails, NymName}; use nym_validator_client::nyxd::{error::NyxdError, traits::NameServiceSigningClient}; use tap::TapFallible; @@ -16,9 +17,15 @@ pub struct Args { #[clap(long)] pub nym_address: String, + #[clap(long)] + pub signature: MessageSignature, + /// Deposit to be made to the service provider directory, in curent DENOMINATION (e.g. 'unym') #[clap(long)] pub deposit: u128, + + #[clap(long)] + pub identity_key: String, } pub async fn register(args: Args, client: SigningClient) -> Result<(), NyxdError> { @@ -29,12 +36,17 @@ pub async fn register(args: Args, client: SigningClient) -> Result<(), NyxdError let name = NymName::new(&args.name).expect("invalid name"); let address = Address::new(&args.nym_address); + let name = NameDetails { + name, + address, + identity_key: args.identity_key, + }; let denom = client.current_chain_details().mix_denom.base.as_str(); let deposit = Coin::new(args.deposit, denom); let res = client - .register_name(name, address, deposit.into(), None) + .register_name(name, args.signature, deposit.into(), None) .await .tap_err(|err| error!("Failed to register name: {err:#?}"))?; diff --git a/common/commands/src/validator/mixnet/query/query_all_names.rs b/common/commands/src/validator/mixnet/query/query_all_names.rs index e3bb24ebae..5a17cad30d 100644 --- a/common/commands/src/validator/mixnet/query/query_all_names.rs +++ b/common/commands/src/validator/mixnet/query/query_all_names.rs @@ -34,8 +34,8 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) { table.set_header(vec!["Name Id", "Owner", "Nym Address", "Name"]); for name_entry in res.names { table.add_row(vec![ - name_entry.name_id.to_string(), - name_entry.name.owner.to_string(), + name_entry.id.to_string(), + name_entry.owner.to_string(), name_entry.name.address.to_string(), name_entry.name.name.to_string(), ]); diff --git a/common/cosmwasm-smart-contracts/name-service/Cargo.toml b/common/cosmwasm-smart-contracts/name-service/Cargo.toml index 46b767a3df..23fb221e23 100644 --- a/common/cosmwasm-smart-contracts/name-service/Cargo.toml +++ b/common/cosmwasm-smart-contracts/name-service/Cargo.toml @@ -7,5 +7,9 @@ edition = "2021" [dependencies] cosmwasm-std = { workspace = true } +cw-controllers = { workspace = true } +cw-utils = { workspace = true } +nym-contracts-common = { path = "../contracts-common", version = "0.5.0" } schemars = "0.8" serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } diff --git a/contracts/name-service/src/error.rs b/common/cosmwasm-smart-contracts/name-service/src/error.rs similarity index 71% rename from contracts/name-service/src/error.rs rename to common/cosmwasm-smart-contracts/name-service/src/error.rs index 66ca6485dd..8c31308b66 100644 --- a/contracts/name-service/src/error.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/error.rs @@ -1,8 +1,10 @@ use cosmwasm_std::{Addr, StdError}; use cw_controllers::AdminError; -use nym_name_service_common::{Address, NameId, NymName}; +use nym_contracts_common::signing::verifier::ApiVerifierError; use thiserror::Error; +use crate::{Address, NameId, NymName}; + #[derive(Error, Debug, PartialEq)] pub enum NameServiceError { #[error("{0}")] @@ -47,6 +49,21 @@ pub enum NameServiceError { error_message: String, }, + #[error("Failed to recover ed25519 public key from its base58 representation - {0}")] + MalformedEd25519IdentityKey(String), + + #[error("Failed to recover ed25519 signature from its base58 representation - {0}")] + MalformedEd25519Signature(String), + + #[error("Provided ed25519 signature did not verify correctly")] + InvalidEd25519Signature, + + #[error("failed to verify message signature: {source}")] + SignatureVerificationFailure { + #[from] + source: ApiVerifierError, + }, + #[error("duplicate entries detected for name: {name}")] DuplicateNames { name: NymName }, @@ -54,4 +71,4 @@ pub enum NameServiceError { NameAlreadyRegistered { name: NymName }, } -pub(crate) type Result = std::result::Result; +pub type Result = std::result::Result; diff --git a/common/cosmwasm-smart-contracts/name-service/src/events.rs b/common/cosmwasm-smart-contracts/name-service/src/events.rs index 0454a8fb28..5bf65e606d 100644 --- a/common/cosmwasm-smart-contracts/name-service/src/events.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/events.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Coin, Event}; -use crate::{NameId, RegisteredName}; +use crate::RegisteredName; pub enum NameEventType { Register, @@ -34,29 +34,29 @@ pub const OWNER: &str = "owner"; pub const DEPOSIT_REQUIRED: &str = "deposit_required"; -pub fn new_register_event(name_id: NameId, name: RegisteredName) -> Event { +pub fn new_register_event(name: RegisteredName) -> Event { Event::new(NameEventType::Register) .add_attribute(ACTION, NameEventType::Register) - .add_attribute(NAME_ID, name_id.to_string()) - .add_attribute(NAME, name.name.to_string()) - .add_attribute(name.address.event_tag(), name.address.to_string()) + .add_attribute(NAME_ID, name.id.to_string()) + .add_attribute(NAME, name.name.name.to_string()) + .add_attribute(name.name.address.event_tag(), name.name.address.to_string()) .add_attribute(OWNER, name.owner.to_string()) } -pub fn new_delete_id_event(name_id: NameId, name: RegisteredName) -> Event { +pub fn new_delete_id_event(name: RegisteredName) -> Event { Event::new(NameEventType::DeleteId) .add_attribute(ACTION, NameEventType::DeleteId) - .add_attribute(NAME_ID, name_id.to_string()) - .add_attribute(NAME, name.name.to_string()) - .add_attribute(name.address.event_tag(), name.address.to_string()) + .add_attribute(NAME_ID, name.id.to_string()) + .add_attribute(NAME, name.name.name.to_string()) + .add_attribute(name.name.address.event_tag(), name.name.address.to_string()) } -pub fn new_delete_name_event(name_id: NameId, name: RegisteredName) -> Event { +pub fn new_delete_name_event(name: RegisteredName) -> Event { Event::new(NameEventType::DeleteId) .add_attribute(ACTION, NameEventType::DeleteName) - .add_attribute(NAME_ID, name_id.to_string()) - .add_attribute(NAME, name.name.to_string()) - .add_attribute(name.address.event_tag(), name.address.to_string()) + .add_attribute(NAME_ID, name.id.to_string()) + .add_attribute(NAME, name.name.name.to_string()) + .add_attribute(name.name.address.event_tag(), name.name.address.to_string()) } pub fn new_update_deposit_required_event(deposit_required: Coin) -> Event { diff --git a/common/cosmwasm-smart-contracts/name-service/src/lib.rs b/common/cosmwasm-smart-contracts/name-service/src/lib.rs index 4609473c60..71a702b7c1 100644 --- a/common/cosmwasm-smart-contracts/name-service/src/lib.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/lib.rs @@ -1,6 +1,8 @@ +pub mod error; pub mod events; pub mod msg; pub mod response; +pub mod signing_types; pub mod types; // Re-export all types at the top-level diff --git a/common/cosmwasm-smart-contracts/name-service/src/msg.rs b/common/cosmwasm-smart-contracts/name-service/src/msg.rs index 6a6698f34d..9a8e2b8ecf 100644 --- a/common/cosmwasm-smart-contracts/name-service/src/msg.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/msg.rs @@ -1,5 +1,6 @@ -use crate::{Address, NameId, NymName}; +use crate::{Address, NameDetails, NameId, NymName}; use cosmwasm_std::Coin; +use nym_contracts_common::signing::MessageSignature; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] @@ -22,7 +23,10 @@ pub struct MigrateMsg {} #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { /// Announcing a name pointing to a nym-address - Register { name: NymName, address: Address }, + Register { + name: NameDetails, + owner_signature: MessageSignature, + }, /// Delete a name entry by id DeleteId { name_id: NameId }, /// Delete a name entry by name @@ -38,8 +42,11 @@ impl ExecuteMsg { pub fn default_memo(&self) -> String { match self { - ExecuteMsg::Register { name, address } => { - format!("registering {address} as name: {name}") + ExecuteMsg::Register { + name, + owner_signature: _, + } => { + format!("registering {} as name: {}", name.address, name.name) } ExecuteMsg::DeleteId { name_id } => { format!("deleting name with id {name_id}") @@ -75,6 +82,9 @@ pub enum QueryMsg { limit: Option, start_after: Option, }, + SigningNonce { + address: String, + }, Config {}, GetContractVersion {}, #[serde(rename = "get_cw2_contract_version")] diff --git a/common/cosmwasm-smart-contracts/name-service/src/response.rs b/common/cosmwasm-smart-contracts/name-service/src/response.rs index 5daa630d01..8087195eee 100644 --- a/common/cosmwasm-smart-contracts/name-service/src/response.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/response.rs @@ -1,36 +1,22 @@ -use crate::{msg::ExecuteMsg, NameEntry, NameId, RegisteredName}; +use crate::{NameId, RegisteredName}; use cosmwasm_std::Coin; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// Like [`NameEntry`] but since it's a response type the name is an option depending on if -/// the name exists or not. -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] -pub struct NameEntryResponse { - pub name_id: NameId, - pub name: Option, -} - #[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct NamesListResponse { - pub names: Vec, + pub names: Vec, } impl NamesListResponse { - pub fn new(names: Vec<(NameId, RegisteredName)>) -> NamesListResponse { - NamesListResponse { - names: names - .into_iter() - .map(|(name_id, name)| NameEntry::new(name_id, name)) - .collect(), - } + pub fn new(names: Vec) -> NamesListResponse { + NamesListResponse { names } } } -impl From<&[NameEntry]> for NamesListResponse { - fn from(names: &[NameEntry]) -> Self { +impl From<&[RegisteredName]> for NamesListResponse { + fn from(names: &[RegisteredName]) -> Self { NamesListResponse { names: names.to_vec(), } @@ -40,21 +26,17 @@ impl From<&[NameEntry]> for NamesListResponse { #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] #[serde(rename_all = "snake_case")] pub struct PagedNamesListResponse { - pub names: Vec, + pub names: Vec, pub per_page: usize, pub start_next_after: Option, } impl PagedNamesListResponse { pub fn new( - names: Vec<(NameId, RegisteredName)>, + names: Vec, per_page: usize, start_next_after: Option, ) -> PagedNamesListResponse { - let names = names - .into_iter() - .map(|(name_id, name)| NameEntry::new(name_id, name)) - .collect(); PagedNamesListResponse { names, per_page, @@ -68,12 +50,3 @@ impl PagedNamesListResponse { pub struct ConfigResponse { pub deposit_required: Coin, } - -impl From for ExecuteMsg { - fn from(name: RegisteredName) -> Self { - ExecuteMsg::Register { - name: name.name, - address: name.address, - } - } -} diff --git a/common/cosmwasm-smart-contracts/name-service/src/signing_types.rs b/common/cosmwasm-smart-contracts/name-service/src/signing_types.rs new file mode 100644 index 0000000000..80a19a7e3d --- /dev/null +++ b/common/cosmwasm-smart-contracts/name-service/src/signing_types.rs @@ -0,0 +1,32 @@ +use cosmwasm_std::{Addr, Coin}; +use nym_contracts_common::signing::{ + ContractMessageContent, MessageType, Nonce, SignableMessage, SigningPurpose, +}; +use serde::Serialize; + +use crate::NameDetails; + +pub type SignableNameRegisterMsg = SignableMessage>; + +#[derive(Serialize)] +pub struct NameRegister { + name: NameDetails, +} + +impl SigningPurpose for NameRegister { + fn message_type() -> MessageType { + MessageType::new("name-register") + } +} + +pub fn construct_name_register_sign_payload( + nonce: Nonce, + sender: Addr, + deposit: Coin, + name: NameDetails, +) -> SignableNameRegisterMsg { + let payload = NameRegister { name }; + let proxy = None; + let content = ContractMessageContent::new(sender, proxy, vec![deposit], payload); + SignableMessage::new(nonce, content) +} diff --git a/common/cosmwasm-smart-contracts/name-service/src/types.rs b/common/cosmwasm-smart-contracts/name-service/src/types.rs index 166e095a2f..dd44340815 100644 --- a/common/cosmwasm-smart-contracts/name-service/src/types.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/types.rs @@ -1,26 +1,50 @@ use std::fmt::{Display, Formatter}; use cosmwasm_std::{Addr, Coin}; +use nym_contracts_common::IdentityKey; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// The directory of services are indexed by [`ServiceId`]. +/// The directory of names are indexed by [`NameId`]. pub type NameId = u32; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)] pub struct RegisteredName { - /// The name pointing to the nym address - pub name: NymName, - /// The address of the service. - pub address: Address, - /// Service owner. + /// Unique id assigned to the registerd name. + pub id: NameId, + + /// The registerd name details. + pub name: NameDetails, + + /// name owner. pub owner: Addr, - /// Block height at which the service was added. + + /// Block height at which the name was added. pub block_height: u64, - /// The deposit used to announce the service. + + /// The deposit used to announce the name. pub deposit: Coin, } +impl RegisteredName { + // Shortcut for getting the actual name + pub fn entry(&self) -> &NymName { + &self.name.name + } +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)] +pub struct NameDetails { + /// The name pointing to the nym address + pub name: NymName, + + /// The address of the name alias. + pub address: Address, + + /// The identity key of the registered name. + pub identity_key: IdentityKey, +} + /// String representation of a nym address, which is of the form /// client_id.client_enc@gateway_id. /// NOTE: entirely unvalidated. @@ -68,6 +92,7 @@ pub enum NymNameError { InvalidName, } +/// Defines what names are allowed fn is_valid_name_char(c: char) -> bool { // Normal lowercase letters (c.is_alphabetic() && c.is_lowercase()) @@ -98,20 +123,6 @@ impl Display for NymName { } } -/// [`RegisterdName`] together with the assigned [`NameId`]. -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct NameEntry { - pub name_id: NameId, - pub name: RegisteredName, -} - -impl NameEntry { - pub fn new(name_id: NameId, name: RegisteredName) -> Self { - Self { name_id, name } - } -} - #[cfg(test)] mod tests { use super::NymName; diff --git a/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml b/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml index 67185a593c..3965734b2d 100644 --- a/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml +++ b/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml @@ -7,9 +7,9 @@ edition = "2021" [dependencies] cosmwasm-std = { workspace = true } +cw-controllers = { workspace = true } +cw-utils = { workspace = true } nym-contracts-common = { path = "../contracts-common", version = "0.5.0" } schemars = "0.8" serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } -cw-utils = { workspace = true } -cw-controllers = { workspace = true } diff --git a/common/socks5-client-core/src/socks/request.rs b/common/socks5-client-core/src/socks/request.rs index 782273f8b7..525391c9ab 100644 --- a/common/socks5-client-core/src/socks/request.rs +++ b/common/socks5-client-core/src/socks/request.rs @@ -78,7 +78,7 @@ impl SocksRequest { where R: AsyncRead + Unpin, { - log::info!("read from stream socks5"); + log::trace!("read from stream socks5"); let mut packet = [0u8; 4]; // Read a byte from the stream and determine the version being requested diff --git a/common/task/src/manager.rs b/common/task/src/manager.rs index 75ab028f81..358534e09b 100644 --- a/common/task/src/manager.rs +++ b/common/task/src/manager.rs @@ -30,7 +30,7 @@ enum TaskError { } // TODO: possibly we should create a `Status` trait instead of reusing `Error` -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, PartialEq, Eq)] pub enum TaskStatus { #[error("Ready")] Ready, diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index fc4605b2e0..1db4c0bd8a 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -1411,6 +1411,7 @@ name = "nym-name-service" version = "0.1.0" dependencies = [ "anyhow", + "bs58", "cosmwasm-std", "cw-controllers", "cw-multi-test", @@ -1418,8 +1419,10 @@ dependencies = [ "cw-utils", "cw2", "nym-contracts-common", + "nym-crypto", "nym-name-service-common", "rand 0.8.5", + "rand_chacha 0.2.2", "rstest", "semver", "serde", @@ -1432,8 +1435,12 @@ name = "nym-name-service-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-contracts-common", "schemars", "serde", + "thiserror", ] [[package]] @@ -1646,9 +1653,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.52" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ "unicode-ident", ] diff --git a/contracts/name-service/Cargo.toml b/contracts/name-service/Cargo.toml index 7ddb660d56..56fe0145cf 100644 --- a/contracts/name-service/Cargo.toml +++ b/contracts/name-service/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] +bs58 = "0.4.0" cosmwasm-std = { workspace = true } cw-controllers = { workspace = true } cw-storage-plus = { workspace = true } @@ -24,5 +25,7 @@ vergen = { version = "=7.4.3", default-features = false, features = ["build", "g [dev-dependencies] anyhow = "1.0.40" cw-multi-test = { workspace = true } +nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } rand = "0.8.5" +rand_chacha = "0.2" rstest = "0.17.0" diff --git a/contracts/name-service/src/constants.rs b/contracts/name-service/src/constants.rs index 6ac0998ae0..0b87f40264 100644 --- a/contracts/name-service/src/constants.rs +++ b/contracts/name-service/src/constants.rs @@ -14,3 +14,5 @@ pub const NAMES_PK_NAMESPACE: &str = "nanames"; pub const NAMES_OWNER_IDX_NAMESPACE: &str = "naowner"; pub const NAMES_ADDRESS_IDX_NAMESPACE: &str = "naaddress"; pub const NAMES_NAME_IDX_NAMESPACE: &str = "naname"; + +pub const SIGNING_NONCES_NAMESPACE: &str = "nasn"; diff --git a/contracts/name-service/src/contract.rs b/contracts/name-service/src/contract.rs index a88d831dc8..f3347c4d6a 100644 --- a/contracts/name-service/src/contract.rs +++ b/contracts/name-service/src/contract.rs @@ -1,6 +1,6 @@ use crate::{ - error::{NameServiceError, Result}, state::{self, Config}, + NameServiceError, Result, }; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; use nym_name_service_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; @@ -72,7 +72,10 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Register { name, address } => execute::register(deps, env, info, name, address), + ExecuteMsg::Register { + name, + owner_signature, + } => execute::register(deps, env, info, name, owner_signature), ExecuteMsg::DeleteId { name_id } => execute::delete_id(deps, info, name_id), ExecuteMsg::DeleteName { name } => execute::delete_name(deps, info, name), ExecuteMsg::UpdateDepositRequired { deposit_required } => { @@ -90,6 +93,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { QueryMsg::All { limit, start_after } => { to_binary(&query::query_all_paged(deps, limit, start_after)?) } + QueryMsg::SigningNonce { address } => { + to_binary(&query::query_current_signing_nonce(deps, address)?) + } QueryMsg::Config {} => to_binary(&query::query_config(deps)?), QueryMsg::GetContractVersion {} => to_binary(&query::query_contract_version()), QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?), @@ -102,16 +108,19 @@ mod tests { use super::*; use crate::test_helpers::{ - assert::{assert_config, assert_empty, assert_name, assert_names, assert_not_found}, - fixture::name_fixture, - helpers::{get_attribute, nyms}, + assert::{ + assert_config, assert_current_nonce, assert_empty, assert_name, assert_names, + assert_not_found, + }, + fixture::new_name_details_with_sign, + helpers::{get_attribute, nyms, test_rng}, }; use cosmwasm_std::{ testing::{mock_dependencies, mock_env, mock_info}, Addr, Coin, }; - use nym_name_service_common::{msg::ExecuteMsg, NameEntry, NameId}; + use nym_name_service_common::{msg::ExecuteMsg, NameId, RegisteredName}; const DENOM: &str = "unym"; @@ -135,23 +144,28 @@ mod tests { } #[test] - fn register_fails_incorrect_deposit() { + fn register_fails_deposit_too_small() { + let mut rng = test_rng(); let mut deps = mock_dependencies(); let msg = InstantiateMsg::new(nyms(100)); let info = mock_info("creator", &[]); - let admin = info.sender.clone(); let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(res.messages.len(), 0); - // Register - let msg: ExecuteMsg = name_fixture().into(); - let owner = name_fixture().owner.to_string(); + let deposit = nyms(99); + let owner = "steve"; + let (name, owner_signature) = + new_name_details_with_sign(deps.as_mut(), &mut rng, "foo", "address", owner, deposit); + let msg = ExecuteMsg::Register { + name, + owner_signature, + }; assert_eq!( execute( deps.as_mut(), mock_env(), - mock_info(&owner, &[nyms(99)]), + mock_info(owner, &[nyms(99)]), msg.clone() ) .unwrap_err(), @@ -161,34 +175,135 @@ mod tests { } ); + // Since we signed for 99unym deposit. assert_eq!( execute( deps.as_mut(), mock_env(), - mock_info(&owner, &[nyms(101)]), + mock_info(owner, &[nyms(100)]), msg ) .unwrap_err(), + NameServiceError::InvalidEd25519Signature, + ); + } + + #[test] + fn register_fails_deposit_too_large() { + let mut rng = test_rng(); + let mut deps = mock_dependencies(); + let msg = InstantiateMsg::new(nyms(100)); + let info = mock_info("creator", &[]); + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(res.messages.len(), 0); + + let deposit = nyms(101); + let owner = "steve"; + let (name, owner_signature) = + new_name_details_with_sign(deps.as_mut(), &mut rng, "foo", "address", owner, deposit); + let msg = ExecuteMsg::Register { + name, + owner_signature, + }; + + assert_eq!( + execute( + deps.as_mut(), + mock_env(), + mock_info(owner, &[nyms(101)]), + msg.clone() + ) + .unwrap_err(), NameServiceError::TooLargeDeposit { funds: 101u128.into(), deposit_required: 100u128.into(), } ); - assert_config(deps.as_ref(), &admin, Coin::new(100, DENOM)); - assert_empty(deps.as_ref()); + // Since we signed for 101unym deposit. + assert_eq!( + execute( + deps.as_mut(), + mock_env(), + mock_info(owner, &[nyms(100)]), + msg + ) + .unwrap_err(), + NameServiceError::InvalidEd25519Signature, + ); + } + + #[test] + fn register_fails_owner_mismatch() { + let mut rng = test_rng(); + let mut deps = mock_dependencies(); + let msg = InstantiateMsg::new(nyms(100)); + let info = mock_info("creator", &[]); + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(res.messages.len(), 0); + + // Setup + let deposit = nyms(100); + let owner = "steve"; + let (name, owner_signature) = new_name_details_with_sign( + deps.as_mut(), + &mut rng, + "my-name", + "my-address", + owner, + deposit, + ); + + // Register + let msg = ExecuteMsg::Register { + name, + owner_signature, + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env(), + mock_info("timmy", &[nyms(100)]), + msg.clone(), + ) + .unwrap_err(), + NameServiceError::InvalidEd25519Signature, + ); + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info("steve", &[nyms(100)]), + msg + ) + .is_ok()); } #[test] fn register_success() { + let mut rng = test_rng(); let mut deps = mock_dependencies(); let msg = InstantiateMsg::new(nyms(100)); let info = mock_info("creator", &[]); let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(res.messages.len(), 0); + // Setup + let deposit = nyms(100); + let owner = "steve"; + let (name, owner_signature) = new_name_details_with_sign( + deps.as_mut(), + &mut rng, + "my-name", + "my-address", + owner, + deposit.clone(), + ); + // Register - let msg: ExecuteMsg = name_fixture().into(); + let msg = ExecuteMsg::Register { + name: name.clone(), + owner_signature, + }; let info = mock_info("steve", &[nyms(100)]); let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); @@ -198,17 +313,24 @@ mod tests { assert_eq!(id, expected_id); assert_eq!( get_attribute(&res, "register", "name"), - "my-service".to_string() + "my-name".to_string() ); assert_eq!( get_attribute(&res, "register", "nym_address"), - "client_id.client_key@gateway_id".to_string() + "my-address".to_string() ); + // Check that the nonce has been incremented, but only for the owner + assert_current_nonce(deps.as_ref(), &Addr::unchecked("steve"), 1); + assert_current_nonce(deps.as_ref(), &Addr::unchecked("timmy"), 0); + // The expected registered name - let expected_name = NameEntry { - name_id: expected_id, - name: name_fixture(), + let expected_name = RegisteredName { + id: expected_id, + name, + owner: Addr::unchecked(owner), + block_height: 12345, + deposit, }; assert_names(deps.as_ref(), &[expected_name.clone()]); assert_name(deps.as_ref(), &expected_name); @@ -216,6 +338,7 @@ mod tests { #[test] fn delete() { + let mut rng = test_rng(); let mut deps = mock_dependencies(); let msg = InstantiateMsg::new(Coin::new(100, "unym")); let info = mock_info("creator", &[]); @@ -223,16 +346,31 @@ mod tests { assert_eq!(res.messages.len(), 0); // Register - let msg: ExecuteMsg = name_fixture().into(); + let deposit = nyms(100); + let steve = "steve"; + let (name, owner_signature) = new_name_details_with_sign( + deps.as_mut(), + &mut rng, + "my-name", + "my-address", + steve, + deposit.clone(), + ); + let msg = ExecuteMsg::Register { + name: name.clone(), + owner_signature, + }; let info_steve = mock_info("steve", &[nyms(100)]); - assert_eq!(info_steve.sender, name_fixture().owner); - execute(deps.as_mut(), mock_env(), info_steve, msg).unwrap(); + execute(deps.as_mut(), mock_env(), info_steve.clone(), msg).unwrap(); // The expected registerd name let expected_id = 1; - let expected_name = NameEntry { - name_id: expected_id, - name: name_fixture(), + let expected_name = RegisteredName { + id: expected_id, + name, + owner: Addr::unchecked(steve), + block_height: 12345, + deposit, }; assert_names(deps.as_ref(), &[expected_name]); @@ -248,12 +386,8 @@ mod tests { // Removing an non-existent name will fail let msg = ExecuteMsg::delete_id(expected_id + 1); - let info_owner = MessageInfo { - sender: name_fixture().owner, - funds: vec![], - }; assert_eq!( - execute(deps.as_mut(), mock_env(), info_owner.clone(), msg).unwrap_err(), + execute(deps.as_mut(), mock_env(), info_steve.clone(), msg).unwrap_err(), NameServiceError::NotFound { name_id: expected_id + 1 } @@ -261,7 +395,7 @@ mod tests { // Remove as correct owner succeeds let msg = ExecuteMsg::delete_id(expected_id); - let res = execute(deps.as_mut(), mock_env(), info_owner, msg).unwrap(); + let res = execute(deps.as_mut(), mock_env(), info_steve, msg).unwrap(); assert_eq!( get_attribute(&res, "delete_id", "name_id"), expected_id.to_string() diff --git a/contracts/name-service/src/contract/execute.rs b/contracts/name-service/src/contract/execute.rs index a7396c1229..e41e57d7f4 100644 --- a/contracts/name-service/src/contract/execute.rs +++ b/contracts/name-service/src/contract/execute.rs @@ -1,15 +1,19 @@ use crate::{ constants::{MAX_NUMBER_OF_NAMES_FOR_ADDRESS, MAX_NUMBER_OF_NAMES_PER_OWNER}, - error::{NameServiceError, Result}, - state, + state, NameServiceError, Result, }; use cosmwasm_std::{Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; +use nym_contracts_common::{ + signing::{MessageSignature, Verifier}, + IdentityKey, +}; use nym_name_service_common::{ events::{ new_delete_id_event, new_delete_name_event, new_register_event, new_update_deposit_required_event, }, - Address, NameId, NymName, RegisteredName, + signing_types::construct_name_register_sign_payload, + Address, NameDetails, NameId, NymName, RegisteredName, }; use super::query; @@ -86,17 +90,54 @@ fn return_deposit(name_to_delete: &RegisteredName) -> BankMsg { } } +fn verify_register_signature( + deps: Deps<'_>, + sender: Addr, + deposit: Coin, + name: NameDetails, + signature: MessageSignature, +) -> Result<()> { + // recover the public key + let public_key = decode_ed25519_identity_key(&name.identity_key)?; + + // reconstruct the payload + let nonce = state::get_signing_nonce(deps.storage, sender.clone())?; + + let msg = construct_name_register_sign_payload(nonce, sender, deposit, name); + + if deps.api.verify_message(msg, signature, &public_key)? { + Ok(()) + } else { + Err(NameServiceError::InvalidEd25519Signature) + } +} + +fn decode_ed25519_identity_key(encoded: &IdentityKey) -> Result<[u8; 32]> { + let mut public_key = [0u8; 32]; + let used = bs58::decode(encoded) + .into(&mut public_key) + .map_err(|err| NameServiceError::MalformedEd25519IdentityKey(err.to_string()))?; + + if used != 32 { + return Err(NameServiceError::MalformedEd25519IdentityKey( + "Too few bytes provided for the public key".into(), + )); + } + + Ok(public_key) +} + /// Register a new name. It will be assigned a new name id. pub fn register( deps: DepsMut, env: Env, info: MessageInfo, - name: NymName, - address: Address, + name: NameDetails, + owner_signature: MessageSignature, ) -> Result { - ensure_name_not_exists(deps.as_ref(), &name)?; + ensure_name_not_exists(deps.as_ref(), &name.name)?; ensure_max_names_per_owner(deps.as_ref(), info.sender.clone())?; - ensure_max_names_per_address(deps.as_ref(), address.clone())?; + ensure_max_names_per_address(deps.as_ref(), name.address.clone())?; let deposit_required = state::deposit_required(deps.storage)?; let denom = deposit_required.denom.clone(); @@ -104,16 +145,29 @@ pub fn register( .map_err(|err| NameServiceError::DepositRequired { source: err })?; ensure_correct_deposit(will_deposit, deposit_required.amount)?; + let deposit = Coin::new(will_deposit.u128(), denom); + + verify_register_signature( + deps.as_ref(), + info.sender.clone(), + deposit.clone(), + name.clone(), + owner_signature, + )?; + + state::increment_signing_nonce(deps.storage, info.sender.clone())?; + + let id = state::next_name_id_counter(deps.storage)?; let new_name = RegisteredName { - address, + id, name, owner: info.sender, block_height: env.block.height, - deposit: Coin::new(will_deposit.u128(), denom), + deposit, }; - let name_id = state::names::save(deps.storage, &new_name)?; + state::names::save(deps.storage, &new_name)?; - Ok(Response::new().add_event(new_register_event(name_id, new_name))) + Ok(Response::new().add_event(new_register_event(new_name))) } /// Delete an exsisting name. @@ -127,23 +181,20 @@ pub fn delete_id(deps: DepsMut, info: MessageInfo, name_id: NameId) -> Result Result { let name_to_delete = query::query_name(deps.as_ref(), name)?; - ensure_sender_authorized(info, &name_to_delete.name)?; + ensure_sender_authorized(info, &name_to_delete)?; - state::names::remove_id(deps.storage, name_to_delete.name_id)?; - let return_deposit_msg = return_deposit(&name_to_delete.name); + state::names::remove_id(deps.storage, name_to_delete.id)?; + let return_deposit_msg = return_deposit(&name_to_delete); Ok(Response::new() .add_message(return_deposit_msg) - .add_event(new_delete_name_event( - name_to_delete.name_id, - name_to_delete.name, - ))) + .add_event(new_delete_name_event(name_to_delete))) } /// Update the deposit required to register new names diff --git a/contracts/name-service/src/contract/query.rs b/contracts/name-service/src/contract/query.rs index 0ba8355e96..29b7b4b057 100644 --- a/contracts/name-service/src/contract/query.rs +++ b/contracts/name-service/src/contract/query.rs @@ -1,18 +1,17 @@ use cosmwasm_std::Deps; -use nym_contracts_common::ContractBuildInformation; +use nym_contracts_common::{signing::Nonce, ContractBuildInformation}; use nym_name_service_common::{ response::{ConfigResponse, NamesListResponse, PagedNamesListResponse}, - Address, NameEntry, NameId, NymName, + Address, NameId, NymName, RegisteredName, }; use crate::{ - error::Result, state::{self, names::PagedLoad}, + Result, }; -pub fn query_id(deps: Deps, name_id: NameId) -> Result { - let name = state::names::load_id(deps.storage, name_id)?; - Ok(NameEntry { name_id, name }) +pub fn query_id(deps: Deps, name_id: NameId) -> Result { + state::names::load_id(deps.storage, name_id) } pub fn query_owner(deps: Deps, owner: String) -> Result { @@ -26,9 +25,8 @@ pub fn query_address(deps: Deps, address: Address) -> Result Ok(NamesListResponse::new(names)) } -pub fn query_name(deps: Deps, name: NymName) -> Result { - state::names::load_name_entry(deps.storage, &name) - .map(|(name_id, name)| NameEntry::new(name_id, name)) +pub fn query_name(deps: Deps, name: NymName) -> Result { + state::names::load_name(deps.storage, &name) } pub fn query_all_paged( @@ -44,6 +42,11 @@ pub fn query_all_paged( Ok(PagedNamesListResponse::new(names, limit, start_next_after)) } +pub fn query_current_signing_nonce(deps: Deps<'_>, address: String) -> Result { + let address = deps.api.addr_validate(&address)?; + state::get_signing_nonce(deps.storage, address) +} + pub fn query_config(deps: Deps) -> Result { let config = state::load_config(deps.storage)?; Ok(config.into()) diff --git a/contracts/name-service/src/integration_tests.rs b/contracts/name-service/src/integration_tests.rs deleted file mode 100644 index b7e68ffdc8..0000000000 --- a/contracts/name-service/src/integration_tests.rs +++ /dev/null @@ -1,445 +0,0 @@ -//! Integration tests using cw-multi-test. - -use cosmwasm_std::Addr; -use nym_name_service_common::{ - response::{ConfigResponse, PagedNamesListResponse}, - Address, NameEntry, NymName, RegisteredName, -}; - -use crate::{ - constants::NAME_DEFAULT_RETRIEVAL_LIMIT, - error::NameServiceError, - test_helpers::{fixture::name_entry, helpers::nyms, test_setup::TestSetup}, -}; - -#[test] -fn instantiate_contract() { - TestSetup::new(); -} - -#[test] -fn query_config() { - assert_eq!( - TestSetup::new().query_config(), - ConfigResponse { - deposit_required: nyms(100), - } - ); -} - -#[test] -fn register_and_query_name() { - let mut setup = TestSetup::new(); - assert_eq!( - setup.query_all(), - PagedNamesListResponse { - names: vec![], - per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, - start_next_after: None, - } - ); - - // Register a first name - let owner = Addr::unchecked("owner"); - let name = NymName::new("steves-server").unwrap(); - let nym_address = Address::new("nym-address"); - assert_eq!(setup.contract_balance(), nyms(0)); - assert_eq!(setup.balance(&owner), nyms(250)); - setup.register(name.clone(), nym_address.clone(), owner.clone()); - - // Deposit is deposited to contract and deducted from owners's balance - assert_eq!(setup.contract_balance(), nyms(100)); - assert_eq!(setup.balance(&owner), nyms(150)); - - // We can query the full name list - assert_eq!( - setup.query_all(), - PagedNamesListResponse { - names: vec![NameEntry { - name_id: 1, - name: RegisteredName { - address: nym_address.clone(), - name: name.clone(), - owner: owner.clone(), - block_height: 12345, - deposit: nyms(100), - }, - }], - per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, - start_next_after: Some(1), - } - ); - - // ... and we can query by id - assert_eq!( - setup.query_id(1), - NameEntry { - name_id: 1, - name: RegisteredName { - address: nym_address.clone(), - name: name.clone(), - owner: owner.clone(), - block_height: 12345, - deposit: nyms(100), - }, - } - ); - - // Register a second name - let owner2 = Addr::unchecked("owner2"); - let name2 = NymName::new("another_server").unwrap(); - let nym_address2 = Address::new("nymAddress2"); - setup.register(name2.clone(), nym_address2.clone(), owner2.clone()); - - assert_eq!(setup.contract_balance(), nyms(200)); - assert_eq!( - setup.query_all(), - PagedNamesListResponse { - names: vec![ - name_entry(1, name, nym_address, owner), - name_entry(2, name2, nym_address2, owner2) - ], - per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, - start_next_after: Some(2), - } - ); -} - -#[test] -fn cant_register_a_name_without_funds() { - let mut setup = TestSetup::new(); - assert_eq!(setup.contract_balance(), nyms(0)); - assert_eq!(setup.balance("owner"), nyms(250)); - setup.register( - NymName::new("my_name").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ); - assert_eq!(setup.contract_balance(), nyms(100)); - assert_eq!(setup.balance("owner"), nyms(150)); - setup.register( - NymName::new("my_name2").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ); - assert_eq!(setup.contract_balance(), nyms(200)); - assert_eq!(setup.balance("owner"), nyms(50)); - let res = setup - .try_register( - NymName::new("my_name3").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ) - .unwrap_err(); - assert_eq!( - res.downcast::().unwrap(), - cosmwasm_std::StdError::Overflow { - source: cosmwasm_std::OverflowError::new( - cosmwasm_std::OverflowOperation::Sub, - "50", - "100" - ) - } - ); -} - -#[test] -fn delete_name() { - let mut setup = TestSetup::new(); - setup.register( - NymName::new("my_name").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ); - assert_eq!(setup.contract_balance(), nyms(100)); - assert_eq!(setup.balance("owner"), nyms(150)); - assert!(!setup.query_all().names.is_empty()); - setup.delete(1, Addr::unchecked("owner")); - - // Deleting the name returns the deposit to the owner - assert_eq!(setup.contract_balance(), nyms(0)); - assert_eq!(setup.balance("owner"), nyms(250)); - assert!(setup.query_all().names.is_empty()); -} - -#[test] -fn only_owner_can_delete_name() { - let mut setup = TestSetup::new(); - assert_eq!(setup.contract_balance(), nyms(0)); - setup.register( - NymName::new("name").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ); - assert_eq!(setup.contract_balance(), nyms(100)); - assert!(!setup.query_all().names.is_empty()); - - let delete_resp = setup - .try_delete(1, Addr::unchecked("not_owner")) - .unwrap_err(); - - assert_eq!(setup.contract_balance(), nyms(100)); - assert_eq!( - delete_resp.downcast::().unwrap(), - NameServiceError::Unauthorized { - sender: Addr::unchecked("not_owner") - } - ); -} - -#[test] -fn cant_delete_name_that_does_not_exist() { - let mut setup = TestSetup::new(); - setup.register( - NymName::new("foo").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ); - assert_eq!(setup.contract_balance(), nyms(100)); - assert!(!setup.query_all().names.is_empty()); - - let delete_resp = setup.try_delete(0, Addr::unchecked("owner")).unwrap_err(); - assert_eq!(setup.contract_balance(), nyms(100)); - assert_eq!( - delete_resp.downcast::().unwrap(), - NameServiceError::NotFound { name_id: 0 } - ); - - let delete_resp = setup.try_delete(2, Addr::unchecked("owner")).unwrap_err(); - assert_eq!(setup.contract_balance(), nyms(100)); - assert_eq!( - delete_resp.downcast::().unwrap(), - NameServiceError::NotFound { name_id: 2 } - ); - - assert!(!setup.query_all().names.is_empty()); - setup.delete(1, Addr::unchecked("owner")); - assert_eq!(setup.contract_balance(), nyms(0)); - assert!(setup.query_all().names.is_empty()); -} - -#[test] -fn cant_register_the_same_name_multiple_times() { - let mut setup = TestSetup::new(); - - setup.register( - NymName::new("name").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ); - let resp = setup - .try_register( - NymName::new("name").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ) - .unwrap_err(); - - assert_eq!( - resp.downcast::().unwrap(), - NameServiceError::NameAlreadyRegistered { - name: NymName::new("name").unwrap() - } - ); -} - -#[test] -fn can_register_multiple_names_for_the_same_nym_address() { - let mut setup = TestSetup::new(); - let name1 = NymName::new("name1").unwrap(); - let name2 = NymName::new("name2").unwrap(); - let address = Address::new("nymaddress"); - let owner = Addr::unchecked("owner"); - - setup.register(name1.clone(), address.clone(), owner.clone()); - setup.register(name2.clone(), address.clone(), owner.clone()); - - assert_eq!( - setup.query_all().names, - vec![ - name_entry(1, name1, address.clone(), owner.clone()), - name_entry(2, name2, address, owner) - ], - ); -} - -#[test] -fn register_multiple_names_and_deleting_by_name() { - let mut setup = TestSetup::new(); - let owner1 = Addr::unchecked("wealthy_owner_1"); - let owner2 = Addr::unchecked("wealthy_owner_2"); - let nym_address1 = Address::new("nymaddress1"); - let nym_address2 = Address::new("nymaddress2"); - let name1 = NymName::new("name1").unwrap(); - let name2 = NymName::new("name2").unwrap(); - let name3 = NymName::new("name3").unwrap(); - let name4 = NymName::new("name4").unwrap(); - let name5 = NymName::new("name5").unwrap(); - - // We register the same address three times, but with different owners - assert_eq!(setup.contract_balance(), nyms(0)); - assert_eq!(setup.balance(&owner1), nyms(1000)); - setup.register(name1.clone(), nym_address1.clone(), owner1.clone()); - setup.register(name2.clone(), nym_address1.clone(), owner1.clone()); - setup.register(name3.clone(), nym_address2.clone(), owner1.clone()); - setup.register(name4.clone(), nym_address1.clone(), owner2.clone()); - setup.register(name5.clone(), nym_address2.clone(), owner2.clone()); - - assert_eq!(setup.contract_balance(), nyms(500)); - assert_eq!(setup.balance(&owner1), nyms(700)); - assert_eq!( - setup.query_all(), - PagedNamesListResponse { - names: vec![ - name_entry(1, name1.clone(), nym_address1.clone(), owner1.clone()), - name_entry(2, name2.clone(), nym_address1.clone(), owner1.clone()), - name_entry(3, name3.clone(), nym_address2.clone(), owner1.clone()), - name_entry(4, name4.clone(), nym_address1.clone(), owner2.clone()), - name_entry(5, name5.clone(), nym_address2.clone(), owner2.clone()), - ], - per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, - start_next_after: Some(5), - } - ); - - setup.delete_name(name1, owner1.clone()); - - assert_eq!(setup.contract_balance(), nyms(400)); - assert_eq!(setup.balance(&owner1), nyms(800)); - assert_eq!( - setup.query_all(), - PagedNamesListResponse { - names: vec![ - name_entry(2, name2, nym_address1.clone(), owner1.clone()), - name_entry(3, name3, nym_address2.clone(), owner1), - name_entry(4, name4, nym_address1, owner2.clone()), - name_entry(5, name5, nym_address2, owner2), - ], - per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, - start_next_after: Some(5), - } - ); -} - -#[test] -fn check_paging() { - let mut setup = TestSetup::new(); - let owner1 = Addr::unchecked("wealthy_owner_1"); - let owner2 = Addr::unchecked("wealthy_owner_2"); - let nym_address1 = Address::new("nymAddress1"); - let nym_address2 = Address::new("nymAddress2"); - let name1 = NymName::new("name1").unwrap(); - let name2 = NymName::new("name2").unwrap(); - let name3 = NymName::new("name3").unwrap(); - let name4 = NymName::new("name4").unwrap(); - let name5 = NymName::new("name5").unwrap(); - - // We register the same address three times, but with different owners - assert_eq!(setup.contract_balance(), nyms(0)); - assert_eq!(setup.balance(&owner1), nyms(1000)); - setup.register(name1.clone(), nym_address1.clone(), owner1.clone()); - setup.register(name2.clone(), nym_address1.clone(), owner1.clone()); - setup.register(name3.clone(), nym_address2.clone(), owner1.clone()); - setup.register(name4.clone(), nym_address1.clone(), owner2.clone()); - setup.register(name5.clone(), nym_address2.clone(), owner2.clone()); - - assert_eq!(setup.contract_balance(), nyms(500)); - assert_eq!(setup.balance(&owner1), nyms(700)); - assert_eq!( - setup.query_all(), - PagedNamesListResponse { - names: vec![ - name_entry(1, name1.clone(), nym_address1.clone(), owner1.clone()), - name_entry(2, name2.clone(), nym_address1.clone(), owner1.clone()), - name_entry(3, name3.clone(), nym_address2.clone(), owner1.clone()), - name_entry(4, name4.clone(), nym_address1.clone(), owner2.clone()), - name_entry(5, name5, nym_address2.clone(), owner2.clone()), - ], - per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, - start_next_after: Some(5), - } - ); - - setup.delete_name(name1, owner1.clone()); - - assert_eq!( - setup.query_all_with_limit(Some(2), None), - PagedNamesListResponse { - names: vec![ - name_entry(2, name2, nym_address1.clone(), owner1.clone()), - name_entry(3, name3.clone(), nym_address2.clone(), owner1.clone()), - ], - per_page: 2, - start_next_after: Some(3), - } - ); - - assert_eq!( - setup.query_all_with_limit(Some(2), Some(2)), - PagedNamesListResponse { - names: vec![ - name_entry(3, name3, nym_address2, owner1), - name_entry(4, name4, nym_address1, owner2), - ], - per_page: 2, - start_next_after: Some(4), - } - ); -} - -#[test] -fn name_id_is_not_resused_when_deleting_and_then_adding_a_new_names() { - let mut setup = TestSetup::new(); - setup.register( - NymName::new("myname1").unwrap(), - Address::new("nymAddress1"), - Addr::unchecked("owner1"), - ); - setup.register( - NymName::new("myname2").unwrap(), - Address::new("nymAddress2"), - Addr::unchecked("owner2"), - ); - setup.register( - NymName::new("myname3").unwrap(), - Address::new("nymAddress3"), - Addr::unchecked("owner3"), - ); - - setup.delete(1, Addr::unchecked("owner1")); - setup.delete(3, Addr::unchecked("owner3")); - - assert_eq!( - setup.query_all().names, - vec![name_entry( - 2, - NymName::new("myname2").unwrap(), - Address::new("nymAddress2"), - Addr::unchecked("owner2") - )] - ); - - setup.register( - NymName::new("myname4").unwrap(), - Address::new("nymAddress4"), - Addr::unchecked("owner4"), - ); - - assert_eq!( - setup.query_all().names, - vec![ - name_entry( - 2, - NymName::new("myname2").unwrap(), - Address::new("nymAddress2"), - Addr::unchecked("owner2") - ), - name_entry( - 4, - NymName::new("myname4").unwrap(), - Address::new("nymAddress4"), - Addr::unchecked("owner4") - ) - ] - ); -} diff --git a/contracts/name-service/src/integration_tests/delete.rs b/contracts/name-service/src/integration_tests/delete.rs new file mode 100644 index 0000000000..feede51739 --- /dev/null +++ b/contracts/name-service/src/integration_tests/delete.rs @@ -0,0 +1,148 @@ +use cosmwasm_std::Addr; +use nym_name_service_common::{response::PagedNamesListResponse, Address, NymName}; +use rstest::rstest; + +use crate::{ + constants::NAME_DEFAULT_RETRIEVAL_LIMIT, + test_helpers::{fixture::new_name, helpers::nyms}, + NameServiceError, +}; + +use super::test_setup::TestSetup; + +#[rstest::fixture] +fn setup() -> TestSetup { + TestSetup::new() +} + +#[rstest] +fn delete_name(mut setup: TestSetup) { + setup.sign_and_register( + &NymName::new("my_name").unwrap(), + &Address::new("address"), + &Addr::unchecked("owner"), + &nyms(100), + ); + assert_eq!(setup.contract_balance(), nyms(100)); + assert_eq!(setup.balance("owner"), nyms(150)); + assert!(!setup.query_all().names.is_empty()); + setup.delete(1, Addr::unchecked("owner")); + + // Deleting the name returns the deposit to the owner + assert_eq!(setup.contract_balance(), nyms(0)); + assert_eq!(setup.balance("owner"), nyms(250)); + assert!(setup.query_all().names.is_empty()); +} + +#[rstest] +fn only_owner_can_delete_name(mut setup: TestSetup) { + assert_eq!(setup.contract_balance(), nyms(0)); + setup.sign_and_register( + &NymName::new("name").unwrap(), + &Address::new("nymAddress"), + &Addr::unchecked("owner"), + &nyms(100), + ); + assert_eq!(setup.contract_balance(), nyms(100)); + assert!(!setup.query_all().names.is_empty()); + + let delete_resp = setup + .try_delete(1, Addr::unchecked("not_owner")) + .unwrap_err(); + + assert_eq!(setup.contract_balance(), nyms(100)); + assert_eq!( + delete_resp.downcast::().unwrap(), + NameServiceError::Unauthorized { + sender: Addr::unchecked("not_owner") + } + ); +} + +#[rstest] +fn cant_delete_name_that_does_not_exist(mut setup: TestSetup) { + setup.sign_and_register( + &NymName::new("foo").unwrap(), + &Address::new("nymAddress"), + &Addr::unchecked("owner"), + &nyms(100), + ); + assert_eq!(setup.contract_balance(), nyms(100)); + assert!(!setup.query_all().names.is_empty()); + + let delete_resp = setup.try_delete(0, Addr::unchecked("owner")).unwrap_err(); + assert_eq!(setup.contract_balance(), nyms(100)); + assert_eq!( + delete_resp.downcast::().unwrap(), + NameServiceError::NotFound { name_id: 0 } + ); + + let delete_resp = setup.try_delete(2, Addr::unchecked("owner")).unwrap_err(); + assert_eq!(setup.contract_balance(), nyms(100)); + assert_eq!( + delete_resp.downcast::().unwrap(), + NameServiceError::NotFound { name_id: 2 } + ); + + assert!(!setup.query_all().names.is_empty()); + setup.delete(1, Addr::unchecked("owner")); + assert_eq!(setup.contract_balance(), nyms(0)); + assert!(setup.query_all().names.is_empty()); +} + +#[rstest] +fn register_multiple_names_and_deleting_by_name(mut setup: TestSetup) { + let owner1 = Addr::unchecked("wealthy_owner_1"); + let owner2 = Addr::unchecked("wealthy_owner_2"); + let address1 = Address::new("address1"); + let address2 = Address::new("address2"); + let name1 = NymName::new("name1").unwrap(); + let name2 = NymName::new("name2").unwrap(); + let name3 = NymName::new("name3").unwrap(); + let name4 = NymName::new("name4").unwrap(); + let name5 = NymName::new("name5").unwrap(); + + // We register the same address three times, but with different owners + assert_eq!(setup.contract_balance(), nyms(0)); + assert_eq!(setup.balance(&owner1), nyms(1000)); + let s1 = setup.sign_and_register(&name1, &address1, &owner1, &nyms(100)); + let s2 = setup.sign_and_register(&name2, &address1, &owner1, &nyms(100)); + let s3 = setup.sign_and_register(&name3, &address2, &owner1, &nyms(100)); + let s4 = setup.sign_and_register(&name4, &address1, &owner2, &nyms(100)); + let s5 = setup.sign_and_register(&name5, &address2, &owner2, &nyms(100)); + + assert_eq!(setup.contract_balance(), nyms(500)); + assert_eq!(setup.balance(&owner1), nyms(700)); + assert_eq!( + setup.query_all(), + PagedNamesListResponse { + names: vec![ + new_name(1, &name1, &address1, &owner1, s1.identity_key()), + new_name(2, &name2, &address1, &owner1, s2.identity_key()), + new_name(3, &name3, &address2, &owner1, s3.identity_key()), + new_name(4, &name4, &address1, &owner2, s4.identity_key()), + new_name(5, &name5, &address2, &owner2, s5.identity_key()), + ], + per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, + start_next_after: Some(5), + } + ); + + setup.delete_name(name1, owner1.clone()); + + assert_eq!(setup.contract_balance(), nyms(400)); + assert_eq!(setup.balance(&owner1), nyms(800)); + assert_eq!( + setup.query_all(), + PagedNamesListResponse { + names: vec![ + new_name(2, &name2, &address1, &owner1, s2.identity_key()), + new_name(3, &name3, &address2, &owner1, s3.identity_key()), + new_name(4, &name4, &address1, &owner2, s4.identity_key()), + new_name(5, &name5, &address2, &owner2, s5.identity_key()), + ], + per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, + start_next_after: Some(5), + } + ); +} diff --git a/contracts/name-service/src/integration_tests/mod.rs b/contracts/name-service/src/integration_tests/mod.rs new file mode 100644 index 0000000000..28905d63c4 --- /dev/null +++ b/contracts/name-service/src/integration_tests/mod.rs @@ -0,0 +1,13 @@ +//! Integration tests using cw-multi-test. + +mod delete; +mod name_id; +mod query; +mod register; +mod test_name; +mod test_setup; + +#[test] +fn instantiate_contract() { + test_setup::TestSetup::new(); +} diff --git a/contracts/name-service/src/integration_tests/name_id.rs b/contracts/name-service/src/integration_tests/name_id.rs new file mode 100644 index 0000000000..39f5a993cf --- /dev/null +++ b/contracts/name-service/src/integration_tests/name_id.rs @@ -0,0 +1,70 @@ +use cosmwasm_std::Addr; +use nym_name_service_common::{Address, NymName}; + +use crate::test_helpers::{fixture::new_name, helpers::nyms}; + +use super::test_setup::TestSetup; + +#[test] +fn name_id_is_not_resused_when_deleting_and_then_adding_a_new_names() { + let mut setup = TestSetup::new(); + setup.sign_and_register( + &NymName::new("myname1").unwrap(), + &Address::new("nymAddress1"), + &Addr::unchecked("owner1"), + &nyms(100), + ); + let s2 = setup.sign_and_register( + &NymName::new("myname2").unwrap(), + &Address::new("nymAddress2"), + &Addr::unchecked("owner2"), + &nyms(100), + ); + setup.sign_and_register( + &NymName::new("myname3").unwrap(), + &Address::new("nymAddress3"), + &Addr::unchecked("owner3"), + &nyms(100), + ); + + setup.delete(1, Addr::unchecked("owner1")); + setup.delete(3, Addr::unchecked("owner3")); + + assert_eq!( + setup.query_all().names, + vec![new_name( + 2, + &NymName::new("myname2").unwrap(), + &Address::new("nymAddress2"), + &Addr::unchecked("owner2"), + s2.identity_key(), + )] + ); + + let s4 = setup.sign_and_register( + &NymName::new("myname4").unwrap(), + &Address::new("nymAddress4"), + &Addr::unchecked("owner4"), + &nyms(100), + ); + + assert_eq!( + setup.query_all().names, + vec![ + new_name( + 2, + &NymName::new("myname2").unwrap(), + &Address::new("nymAddress2"), + &Addr::unchecked("owner2"), + s2.identity_key(), + ), + new_name( + 4, + &NymName::new("myname4").unwrap(), + &Address::new("nymAddress4"), + &Addr::unchecked("owner4"), + s4.identity_key(), + ) + ] + ); +} diff --git a/contracts/name-service/src/integration_tests/query.rs b/contracts/name-service/src/integration_tests/query.rs new file mode 100644 index 0000000000..67069a0a1d --- /dev/null +++ b/contracts/name-service/src/integration_tests/query.rs @@ -0,0 +1,88 @@ +use cosmwasm_std::Addr; +use nym_name_service_common::{ + response::{ConfigResponse, PagedNamesListResponse}, + Address, NymName, +}; + +use crate::{ + constants::NAME_DEFAULT_RETRIEVAL_LIMIT, + test_helpers::{fixture::new_name, helpers::nyms}, +}; + +use super::test_setup::TestSetup; + +#[test] +fn query_config() { + assert_eq!( + TestSetup::new().query_config(), + ConfigResponse { + deposit_required: nyms(100), + } + ); +} + +#[test] +fn check_paging() { + let mut setup = TestSetup::new(); + let owner1 = Addr::unchecked("wealthy_owner_1"); + let owner2 = Addr::unchecked("wealthy_owner_2"); + let address1 = Address::new("nymAddress1"); + let address2 = Address::new("nymAddress2"); + let name1 = NymName::new("name1").unwrap(); + let name2 = NymName::new("name2").unwrap(); + let name3 = NymName::new("name3").unwrap(); + let name4 = NymName::new("name4").unwrap(); + let name5 = NymName::new("name5").unwrap(); + + // We register the same address three times, but with different owners + assert_eq!(setup.contract_balance(), nyms(0)); + assert_eq!(setup.balance(&owner1), nyms(1000)); + let s1 = setup.sign_and_register(&name1, &address1, &owner1, &nyms(100)); + let s2 = setup.sign_and_register(&name2, &address1, &owner1, &nyms(100)); + let s3 = setup.sign_and_register(&name3, &address2, &owner1, &nyms(100)); + let s4 = setup.sign_and_register(&name4, &address1, &owner2, &nyms(100)); + let s5 = setup.sign_and_register(&name5, &address2, &owner2, &nyms(100)); + + assert_eq!(setup.contract_balance(), nyms(500)); + assert_eq!(setup.balance(&owner1), nyms(700)); + assert_eq!( + setup.query_all(), + PagedNamesListResponse { + names: vec![ + new_name(1, &name1, &address1, &owner1, s1.identity_key()), + new_name(2, &name2, &address1, &owner1, s2.identity_key()), + new_name(3, &name3, &address2, &owner1, s3.identity_key()), + new_name(4, &name4, &address1, &owner2, s4.identity_key()), + new_name(5, &name5, &address2, &owner2, s5.identity_key()), + ], + per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, + start_next_after: Some(5), + } + ); + + setup.delete_name(name1, owner1.clone()); + + assert_eq!( + setup.query_all_with_limit(Some(2), None), + PagedNamesListResponse { + names: vec![ + new_name(2, &name2, &address1, &owner1, s2.identity_key()), + new_name(3, &name3, &address2, &owner1, s3.identity_key()), + ], + per_page: 2, + start_next_after: Some(3), + } + ); + + assert_eq!( + setup.query_all_with_limit(Some(2), Some(2)), + PagedNamesListResponse { + names: vec![ + new_name(3, &name3, &address2, &owner1, s3.identity_key()), + new_name(4, &name4, &address1, &owner2, s4.identity_key()), + ], + per_page: 2, + start_next_after: Some(4), + } + ); +} diff --git a/contracts/name-service/src/integration_tests/register.rs b/contracts/name-service/src/integration_tests/register.rs new file mode 100644 index 0000000000..9d6d339dbc --- /dev/null +++ b/contracts/name-service/src/integration_tests/register.rs @@ -0,0 +1,269 @@ +use cosmwasm_std::Addr; +use nym_name_service_common::{ + error::NameServiceError, response::PagedNamesListResponse, Address, NameDetails, NymName, + RegisteredName, +}; +use rstest::rstest; + +use crate::{ + constants::NAME_DEFAULT_RETRIEVAL_LIMIT, + test_helpers::{fixture::new_name, helpers::nyms}, +}; + +use super::test_setup::TestSetup; + +#[rstest::fixture] +fn setup() -> TestSetup { + TestSetup::new() +} + +#[rstest] +fn basic_register(mut setup: TestSetup) { + assert_eq!( + setup.query_all(), + PagedNamesListResponse { + names: vec![], + per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, + start_next_after: None, + } + ); + + // Register a first name + let owner = Addr::unchecked("owner"); + let name = NymName::new("steves-server").unwrap(); + let nym_address = Address::new("nym-address"); + assert_eq!(setup.contract_balance(), nyms(0)); + assert_eq!(setup.balance(&owner), nyms(250)); + assert_eq!(setup.query_signing_nonce(owner.to_string()), 0); + + let reg_name = setup.new_name(&name, &nym_address); + let payload = setup.payload_to_sign(&owner, &nyms(100), ®_name.name); + let reg_name = reg_name.sign(payload); + setup.register(®_name, &owner); + + // Deposit is deposited to contract and deducted from owners's balance + assert_eq!(setup.contract_balance(), nyms(100)); + assert_eq!(setup.balance(&owner), nyms(150)); + + // The signing nonce has been incremented + assert_eq!(setup.query_signing_nonce(owner.to_string()), 1); + + // We can query the full name list + assert_eq!( + setup.query_all(), + PagedNamesListResponse { + names: vec![RegisteredName { + id: 1, + name: NameDetails { + name: name.clone(), + address: nym_address.clone(), + identity_key: reg_name.identity_key().to_string(), + }, + owner: owner.clone(), + block_height: 12345, + deposit: nyms(100), + }], + per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, + start_next_after: Some(1), + } + ); + + // ... and we can query by id + assert_eq!( + setup.query_id(1), + RegisteredName { + id: 1, + name: reg_name.details().clone(), + owner: owner.clone(), + block_height: 12345, + deposit: nyms(100), + } + ); + + // Register a second name + let owner2 = Addr::unchecked("owner2"); + let name2 = NymName::new("another_server").unwrap(); + let nym_address2 = Address::new("nymAddress2"); + let reg_name2 = setup.new_signed_name(&name2, &nym_address2, &owner2, &nyms(100)); + setup.register(®_name2, &owner2); + + assert_eq!(setup.contract_balance(), nyms(200)); + assert_eq!( + setup.query_all(), + PagedNamesListResponse { + names: vec![ + new_name(1, &name, &nym_address, &owner, reg_name.identity_key()), + new_name(2, &name2, &nym_address2, &owner2, reg_name2.identity_key()), + ], + per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, + start_next_after: Some(2), + } + ); +} + +#[rstest] +fn register_fails_when_owner_mismatch(mut setup: TestSetup) { + let owner = Addr::unchecked("owner"); + let name = NymName::new("steves-server").unwrap(); + let nym_address = Address::new("nym-address"); + let reg_name = setup.new_signed_name(&name, &nym_address, &owner, &nyms(100)); + let res = setup + .try_register(®_name, &Addr::unchecked("owner2")) + .unwrap_err(); + assert_eq!( + res.downcast::().unwrap(), + NameServiceError::InvalidEd25519Signature + ); +} + +#[rstest] +fn signing_nonce_is_increased_when_registering(mut setup: TestSetup) { + let owner1 = Addr::unchecked("owner1"); + let owner2 = Addr::unchecked("owner2"); + + assert_eq!(setup.query_signing_nonce(owner1.to_string()), 0); + assert_eq!(setup.query_signing_nonce(owner2.to_string()), 0); + + setup.sign_and_register( + &NymName::new("myname1").unwrap(), + &Address::new("address1"), + &owner1, + &nyms(100), + ); + + assert_eq!(setup.query_signing_nonce(owner1.to_string()), 1); + assert_eq!(setup.query_signing_nonce(owner2.to_string()), 0); + + setup.sign_and_register( + &NymName::new("myname2").unwrap(), + &Address::new("address2"), + &owner2, + &nyms(100), + ); + + assert_eq!(setup.query_signing_nonce(owner1.to_string()), 1); + assert_eq!(setup.query_signing_nonce(owner2.to_string()), 1); + + setup.sign_and_register( + &NymName::new("myname3").unwrap(), + &Address::new("address3"), + &owner2, + &nyms(100), + ); + + assert_eq!(setup.query_signing_nonce(owner1.to_string()), 1); + assert_eq!(setup.query_signing_nonce(owner2.to_string()), 2); +} + +#[rstest] +fn creating_two_names_in_a_row_without_announcing_fails(mut setup: TestSetup) { + let owner = Addr::unchecked("wealthy_owner_1"); + let name1 = NymName::new("steves-server1").unwrap(); + let name2 = NymName::new("steves-server2").unwrap(); + let address1 = Address::new("nymAddress1"); + let address2 = Address::new("nymAddress2"); + let deposit = nyms(100); + + let s1 = setup.new_signed_name(&name1, &address1, &owner, &deposit); + + // This second name will be signed with the same nonce + let s2 = setup.new_signed_name(&name2, &address2, &owner, &deposit); + + // Announce the first service works, and this increments the nonce + setup.register(&s1, &owner); + + // Now the nonce has been incremented, and the signature will not match + let resp: NameServiceError = setup + .try_register(&s2, &owner) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(resp, NameServiceError::InvalidEd25519Signature,); +} + +#[rstest] +fn cant_register_a_name_without_funds(mut setup: TestSetup) { + assert_eq!(setup.contract_balance(), nyms(0)); + assert_eq!(setup.balance("owner"), nyms(250)); + let name1 = setup.new_signed_name( + &NymName::new("my_name").unwrap(), + &Address::new("nymAddress"), + &Addr::unchecked("owner"), + &nyms(100), + ); + setup.register(&name1, &Addr::unchecked("owner")); + assert_eq!(setup.contract_balance(), nyms(100)); + assert_eq!(setup.balance("owner"), nyms(150)); + + let name2 = setup.new_signed_name( + &NymName::new("my_name2").unwrap(), + &Address::new("nymAddress"), + &Addr::unchecked("owner"), + &nyms(100), + ); + setup.register(&name2, &Addr::unchecked("owner")); + assert_eq!(setup.contract_balance(), nyms(200)); + assert_eq!(setup.balance("owner"), nyms(50)); + let name3 = setup.new_signed_name( + &NymName::new("my_name3").unwrap(), + &Address::new("nymAddress"), + &Addr::unchecked("owner"), + &nyms(100), + ); + let res = setup + .try_register(&name3, &Addr::unchecked("owner")) + .unwrap_err(); + assert_eq!( + res.downcast::().unwrap(), + cosmwasm_std::StdError::Overflow { + source: cosmwasm_std::OverflowError::new( + cosmwasm_std::OverflowOperation::Sub, + "50", + "100" + ) + } + ); +} + +#[rstest] +fn cant_register_the_same_name_multiple_times(mut setup: TestSetup) { + let name1 = setup.new_signed_name( + &NymName::new("name").unwrap(), + &Address::new("nymAddress"), + &Addr::unchecked("owner"), + &nyms(100), + ); + setup.register(&name1, &Addr::unchecked("owner")); + let resp = setup + .try_register(&name1, &Addr::unchecked("owner")) + .unwrap_err(); + + assert_eq!( + resp.downcast::().unwrap(), + NameServiceError::NameAlreadyRegistered { + name: NymName::new("name").unwrap() + } + ); +} + +#[rstest] +fn can_register_multiple_names_for_the_same_nym_address(mut setup: TestSetup) { + let name1 = NymName::new("name1").unwrap(); + let name2 = NymName::new("name2").unwrap(); + let address = Address::new("nymaddress"); + let owner = Addr::unchecked("owner"); + + let reg_name1 = setup.new_signed_name(&name1, &address, &owner, &nyms(100)); + setup.register(®_name1, &owner); + let reg_name2 = setup.new_signed_name(&name2, &address, &owner, &nyms(100)); + setup.register(®_name2, &owner); + + assert_eq!( + setup.query_all().names, + vec![ + new_name(1, &name1, &address, &owner, reg_name1.identity_key()), + new_name(2, &name2, &address, &owner, reg_name2.identity_key()), + ], + ); +} +// diff --git a/contracts/name-service/src/integration_tests/test_name.rs b/contracts/name-service/src/integration_tests/test_name.rs new file mode 100644 index 0000000000..f33233a0a0 --- /dev/null +++ b/contracts/name-service/src/integration_tests/test_name.rs @@ -0,0 +1,75 @@ +use nym_contracts_common::{signing::MessageSignature, IdentityKey}; +use nym_crypto::asymmetric::identity; +use nym_name_service_common::{ + signing_types::SignableNameRegisterMsg, Address, NameDetails, NymName, +}; +use rand_chacha::ChaCha20Rng; + +use crate::test_helpers::signing::ed25519_sign_message; + +pub struct TestName { + pub name: NameDetails, + pub keys: identity::KeyPair, + pub rng: ChaCha20Rng, +} + +impl TestName { + pub fn new(rng: &mut ChaCha20Rng, name: NymName, address: Address) -> Self { + let keys = identity::KeyPair::new(rng); + let name = NameDetails { + name, + address, + identity_key: keys.public_key().to_base58_string(), + }; + Self { + name, + keys, + rng: rng.clone(), + } + } + + pub fn identity_key(&self) -> &IdentityKey { + &self.name.identity_key + } + + pub fn details(&self) -> &NameDetails { + &self.name + } + + pub fn sign(self, payload: SignableNameRegisterMsg) -> SignedTestName { + let owner_signature = ed25519_sign_message(payload, self.keys.private_key()); + SignedTestName { + name: self.name, + keys: self.keys, + owner_signature, + } + } +} + +impl From for NameDetails { + fn from(test_name: TestName) -> Self { + test_name.name + } +} + +pub struct SignedTestName { + pub name: NameDetails, + pub keys: identity::KeyPair, + pub owner_signature: MessageSignature, +} + +impl SignedTestName { + pub fn identity_key(&self) -> &IdentityKey { + &self.name.identity_key + } + + pub fn details(&self) -> &NameDetails { + &self.name + } +} + +impl From for NameDetails { + fn from(signed_name: SignedTestName) -> Self { + signed_name.name + } +} diff --git a/contracts/name-service/src/test_helpers/test_setup.rs b/contracts/name-service/src/integration_tests/test_setup.rs similarity index 64% rename from contracts/name-service/src/test_helpers/test_setup.rs rename to contracts/name-service/src/integration_tests/test_setup.rs index 98afbb60a0..264d2d236b 100644 --- a/contracts/name-service/src/test_helpers/test_setup.rs +++ b/contracts/name-service/src/integration_tests/test_setup.rs @@ -1,13 +1,18 @@ use cosmwasm_std::{coins, Addr, Coin, Uint128}; use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor}; +use nym_contracts_common::signing::Nonce; use nym_name_service_common::{ msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, response::{ConfigResponse, PagedNamesListResponse}, - Address, NameEntry, NameId, NymName, + signing_types::{construct_name_register_sign_payload, SignableNameRegisterMsg}, + Address, NameDetails, NameId, NymName, RegisteredName, }; +use rand_chacha::ChaCha20Rng; use serde::de::DeserializeOwned; -use crate::test_helpers::helpers::get_app_attribute; +use crate::test_helpers::helpers::{get_app_attribute, test_rng}; + +use super::test_name::{SignedTestName, TestName}; const DENOM: &str = "unym"; const ADDRESSES: &[&str] = &[ @@ -19,6 +24,7 @@ const WEALTHY_ADDRESSES: &[&str] = &["wealthy_owner_1", "wealthy_owner_2"]; pub struct TestSetup { app: App, addr: Addr, + rng: ChaCha20Rng, } impl Default for TestSetup { @@ -44,7 +50,8 @@ impl TestSetup { let code = ContractWrapper::new(crate::execute, crate::instantiate, crate::query); let code_id = app.store_code(Box::new(code)); let addr = Self::instantiate(&mut app, code_id); - TestSetup { app, addr } + let rng = test_rng(); + TestSetup { app, addr, rng } } fn instantiate(app: &mut App, code_id: u64) -> Addr { @@ -76,7 +83,7 @@ impl TestSetup { self.query(&QueryMsg::Config {}) } - pub fn query_id(&self, name_id: NameId) -> NameEntry { + pub fn query_id(&self, name_id: NameId) -> RegisteredName { self.query(&QueryMsg::NameId { name_id }) } @@ -92,16 +99,48 @@ impl TestSetup { self.query(&QueryMsg::All { limit, start_after }) } + pub fn query_signing_nonce(&self, address: String) -> Nonce { + self.query(&QueryMsg::SigningNonce { address }) + } + + pub fn new_name(&mut self, name: &NymName, address: &Address) -> TestName { + TestName::new(&mut self.rng, name.clone(), address.clone()) + } + + pub fn payload_to_sign( + &mut self, + owner: &Addr, + deposit: &Coin, + name: &NameDetails, + ) -> SignableNameRegisterMsg { + let nonce = self.query_signing_nonce(owner.to_string()); + construct_name_register_sign_payload(nonce, owner.clone(), deposit.clone(), name.clone()) + } + + pub fn new_signed_name( + &mut self, + name: &NymName, + address: &Address, + owner: &Addr, + deposit: &Coin, + ) -> SignedTestName { + let name = self.new_name(name, address); + let payload = self.payload_to_sign(owner, deposit, name.details()); + name.sign(payload) + } + pub fn try_register( &mut self, - name: NymName, - address: Address, - owner: Addr, + name: &SignedTestName, + owner: &Addr, ) -> anyhow::Result { self.app.execute_contract( - owner, + owner.clone(), self.addr.clone(), - &ExecuteMsg::Register { name, address }, + &ExecuteMsg::Register { + name: name.name.clone(), + owner_signature: name.owner_signature.clone(), + }, &[Coin { denom: DENOM.to_string(), amount: Uint128::new(100), @@ -109,8 +148,8 @@ impl TestSetup { ) } - pub fn register(&mut self, name: NymName, address: Address, owner: Addr) -> AppResponse { - let resp = self.try_register(name, address, owner).unwrap(); + pub fn register(&mut self, name: &SignedTestName, owner: &Addr) -> AppResponse { + let resp = self.try_register(name, owner).unwrap(); assert_eq!( get_app_attribute(&resp, "wasm-register", "action"), "register" @@ -118,6 +157,19 @@ impl TestSetup { resp } + // Convenience function for creating a new signed name, and regsitering it + pub fn sign_and_register( + &mut self, + name: &NymName, + address: &Address, + owner: &Addr, + deposit: &Coin, + ) -> SignedTestName { + let signed_name = self.new_signed_name(name, address, owner, deposit); + self.register(&signed_name, owner); + signed_name + } + pub fn try_delete(&mut self, name_id: NameId, owner: Addr) -> anyhow::Result { self.app.execute_contract( owner, diff --git a/contracts/name-service/src/lib.rs b/contracts/name-service/src/lib.rs index c2d5b6c5d6..0f426dd72f 100644 --- a/contracts/name-service/src/lib.rs +++ b/contracts/name-service/src/lib.rs @@ -3,16 +3,15 @@ #![warn(clippy::expect_used)] #![warn(clippy::unwrap_used)] -use crate::error::Result; +pub use nym_name_service_common::error::{NameServiceError, Result}; + use nym_name_service_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response}; -use error::NameServiceError; mod contract; -mod error; mod state; pub mod constants; diff --git a/contracts/name-service/src/state/admin.rs b/contracts/name-service/src/state/admin.rs index e0ec915951..026e353626 100644 --- a/contracts/name-service/src/state/admin.rs +++ b/contracts/name-service/src/state/admin.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Addr, Deps, DepsMut}; use cw_controllers::Admin; -use crate::{constants::ADMIN_KEY, error::Result}; +use crate::{constants::ADMIN_KEY, Result}; const ADMIN: Admin = Admin::new(ADMIN_KEY); diff --git a/contracts/name-service/src/state/config.rs b/contracts/name-service/src/state/config.rs index ed032e670c..aa27617492 100644 --- a/contracts/name-service/src/state/config.rs +++ b/contracts/name-service/src/state/config.rs @@ -3,7 +3,7 @@ use cw_storage_plus::Item; use nym_name_service_common::response::ConfigResponse; use serde::{Deserialize, Serialize}; -use crate::{constants::CONFIG_KEY, error::Result}; +use crate::{constants::CONFIG_KEY, Result}; const CONFIG: Item = Item::new(CONFIG_KEY); diff --git a/contracts/name-service/src/state/mod.rs b/contracts/name-service/src/state/mod.rs index 4120ab91e8..8d86c67201 100644 --- a/contracts/name-service/src/state/mod.rs +++ b/contracts/name-service/src/state/mod.rs @@ -2,7 +2,9 @@ pub mod admin; pub mod config; pub mod name_id_counter; pub mod names; +pub mod nonce; pub(crate) use admin::{assert_admin, set_admin}; pub(crate) use config::{deposit_required, load_config, save_config, Config}; pub(crate) use name_id_counter::next_name_id_counter; +pub(crate) use nonce::{get_signing_nonce, increment_signing_nonce}; diff --git a/contracts/name-service/src/state/name_id_counter.rs b/contracts/name-service/src/state/name_id_counter.rs index bed384e7bb..c45e5169e6 100644 --- a/contracts/name-service/src/state/name_id_counter.rs +++ b/contracts/name-service/src/state/name_id_counter.rs @@ -2,7 +2,7 @@ use cosmwasm_std::Storage; use cw_storage_plus::Item; use nym_name_service_common::NameId; -use crate::{constants::NAME_ID_COUNTER_KEY, error::Result}; +use crate::{constants::NAME_ID_COUNTER_KEY, Result}; const NAME_ID_COUNTER: Item = Item::new(NAME_ID_COUNTER_KEY); @@ -16,33 +16,56 @@ pub(crate) fn next_name_id_counter(store: &mut dyn Storage) -> Result { #[cfg(test)] mod tests { - - use nym_name_service_common::NameEntry; + use cosmwasm_std::Addr; + use nym_name_service_common::RegisteredName; use crate::test_helpers::{ assert::assert_names, - fixture::name_fixture_name, - helpers::{delete_name_id, instantiate_test_contract, register_name}, + helpers::{nyms, test_rng}, + transactions::{delete_name_id, instantiate_test_contract, register_name}, }; #[test] fn get_next_name_id() { let mut deps = instantiate_test_contract(); + let mut rng = test_rng(); - assert_eq!(register_name(deps.as_mut(), &name_fixture_name("foo")), 1); - assert_names( - deps.as_ref(), - &[NameEntry::new(1, name_fixture_name("foo"))], - ); - - assert_eq!(register_name(deps.as_mut(), &name_fixture_name("bar")), 2); - assert_eq!(register_name(deps.as_mut(), &name_fixture_name("baz")), 3); + let (id1, name1) = register_name(deps.as_mut(), &mut rng, "foo", "addr1", "steve"); + let (id2, name2) = register_name(deps.as_mut(), &mut rng, "bar", "addr2", "steve"); + let (id3, name3) = register_name(deps.as_mut(), &mut rng, "baz", "addr3", "steve"); + assert_eq!(id1, 1); + assert_eq!(id2, 2); + assert_eq!(id3, 3); + assert_eq!(name1.name.as_str(), "foo"); + assert_eq!(name1.address.as_str(), "addr1"); + assert_eq!(name2.name.as_str(), "bar"); + assert_eq!(name2.address.as_str(), "addr2"); + assert_eq!(name3.name.as_str(), "baz"); + assert_eq!(name3.address.as_str(), "addr3"); assert_names( deps.as_ref(), &[ - NameEntry::new(1, name_fixture_name("foo")), - NameEntry::new(2, name_fixture_name("bar")), - NameEntry::new(3, name_fixture_name("baz")), + RegisteredName { + id: 1, + name: name1, + owner: Addr::unchecked("steve"), + block_height: 12345, + deposit: nyms(100), + }, + RegisteredName { + id: 2, + name: name2, + owner: Addr::unchecked("steve"), + block_height: 12345, + deposit: nyms(100), + }, + RegisteredName { + id: 3, + name: name3, + owner: Addr::unchecked("steve"), + block_height: 12345, + deposit: nyms(100), + }, ], ); } @@ -50,34 +73,28 @@ mod tests { #[test] fn deleted_name_id_is_not_reused() { let mut deps = instantiate_test_contract(); + let mut rng = test_rng(); // Register two names - assert_eq!(register_name(deps.as_mut(), &name_fixture_name("one")), 1); - assert_eq!(register_name(deps.as_mut(), &name_fixture_name("two")), 2); - assert_names( - deps.as_ref(), - &[ - NameEntry::new(1, name_fixture_name("one")), - NameEntry::new(2, name_fixture_name("two")), - ], - ); + let (_, name1) = register_name(deps.as_mut(), &mut rng, "one", "sdfjkhsdfhr", "steve"); + register_name(deps.as_mut(), &mut rng, "two", "suereljer", "steve"); // Delete the last entry delete_name_id(deps.as_mut(), 2, "steve"); assert_names( deps.as_ref(), - &[NameEntry::new(1, name_fixture_name("one"))], + &[RegisteredName { + id: 1, + name: name1, + owner: Addr::unchecked("steve"), + block_height: 12345, + deposit: nyms(100), + }], ); // Create a third entry. The index should not reuse the previous entry that we just // deleted. - assert_eq!(register_name(deps.as_mut(), &name_fixture_name("two")), 3); - assert_names( - deps.as_ref(), - &[ - NameEntry::new(1, name_fixture_name("one")), - NameEntry::new(3, name_fixture_name("two")), - ], - ); + let (id3, _) = register_name(deps.as_mut(), &mut rng, "three", "ufd", "steve"); + assert_eq!(id3, 3); } } diff --git a/contracts/name-service/src/state/names.rs b/contracts/name-service/src/state/names.rs index 81190687f6..27af9f3407 100644 --- a/contracts/name-service/src/state/names.rs +++ b/contracts/name-service/src/state/names.rs @@ -8,7 +8,7 @@ use crate::{ NAMES_ADDRESS_IDX_NAMESPACE, NAMES_NAME_IDX_NAMESPACE, NAMES_OWNER_IDX_NAMESPACE, NAMES_PK_NAMESPACE, NAME_DEFAULT_RETRIEVAL_LIMIT, NAME_MAX_RETRIEVAL_LIMIT, }, - error::{NameServiceError, Result}, + NameServiceError, Result, }; struct NameIndex<'a> { @@ -28,9 +28,9 @@ impl<'a> IndexList for NameIndex<'a> { fn names<'a>() -> IndexedMap<'a, NameId, RegisteredName, NameIndex<'a>> { let indexes = NameIndex { - name: UniqueIndex::new(|d| d.name.to_string(), NAMES_NAME_IDX_NAMESPACE), + name: UniqueIndex::new(|d| d.name.name.to_string(), NAMES_NAME_IDX_NAMESPACE), address: MultiIndex::new( - |d| d.address.to_string(), + |d| d.name.address.to_string(), NAMES_PK_NAMESPACE, NAMES_ADDRESS_IDX_NAMESPACE, ), @@ -43,19 +43,29 @@ fn names<'a>() -> IndexedMap<'a, NameId, RegisteredName, NameIndex<'a>> { IndexedMap::new(NAMES_PK_NAMESPACE, indexes) } -pub fn save(store: &mut dyn Storage, new_name: &RegisteredName) -> Result { - let name_id = super::next_name_id_counter(store)?; +pub fn save(store: &mut dyn Storage, new_name: &RegisteredName) -> Result<()> { + let name_id = new_name.id; names().save(store, name_id, new_name)?; - Ok(name_id) + Ok(()) } #[cfg(test)] -pub fn save_all(state: &mut dyn Storage, names: &[RegisteredName]) -> Result> { - let mut ids = vec![]; +pub fn save_all(state: &mut dyn Storage, names: &[RegisteredName]) -> Result<()> { for name in names { - ids.push(save(state, name)?); + save(state, name)?; } - Ok(ids) + Ok(()) +} + +pub fn remove_id(store: &mut dyn Storage, name_id: NameId) -> Result<()> { + Ok(names().remove(store, name_id)?) +} + +#[cfg(test)] +pub fn remove_name(store: &mut dyn Storage, name: NymName) -> Result { + let registered_name = load_name(store, &name)?; + remove_id(store, registered_name.id)?; + Ok(registered_name.id) } pub fn has_name_id(store: &dyn Storage, name_id: NameId) -> bool { @@ -66,23 +76,6 @@ pub fn has_name(store: &dyn Storage, name: &NymName) -> bool { load_name(store, name).is_ok() } -// Get the (key, name) entry for a given name -pub fn load_name_entry(store: &dyn Storage, name: &NymName) -> Result<(NameId, RegisteredName)> { - names() - .idx - .name - .range(store, None, None, Order::Ascending) - .find(|entry| { - if let Ok(entry) = entry { - &entry.1.name == name - } else { - false - } - }) - .ok_or(NameServiceError::NameNotFound { name: name.clone() })? - .map_err(NameServiceError::from) -} - pub fn load_id(store: &dyn Storage, name_id: NameId) -> Result { names().load(store, name_id).map_err(|err| match err { StdError::NotFound { .. } => NameServiceError::NotFound { name_id }, @@ -99,45 +92,33 @@ pub fn load_name(store: &dyn Storage, name: &NymName) -> Result .ok_or(NameServiceError::NameNotFound { name: name.clone() }) } -pub fn load_address( - store: &dyn Storage, - address: &Address, -) -> Result> { +pub fn load_address(store: &dyn Storage, address: &Address) -> Result> { names() .idx .address .prefix(address.to_string()) .range(store, None, None, Order::Ascending) .take(MAX_NUMBER_OF_NAMES_FOR_ADDRESS as usize) + .map(|res| res.map(|(_, name)| name)) .collect::>>() .map_err(NameServiceError::from) } -pub fn load_owner(store: &dyn Storage, owner: Addr) -> Result> { +pub fn load_owner(store: &dyn Storage, owner: Addr) -> Result> { names() .idx .owner .prefix(owner) .range(store, None, None, Order::Ascending) .take(MAX_NUMBER_OF_NAMES_PER_OWNER as usize) + .map(|res| res.map(|(_, name)| name)) .collect::>>() .map_err(NameServiceError::from) } -pub fn remove_id(store: &mut dyn Storage, name_id: NameId) -> Result<()> { - Ok(names().remove(store, name_id)?) -} - -#[cfg(test)] -pub fn remove_name(store: &mut dyn Storage, name: NymName) -> Result { - let name_info = load_name_entry(store, &name)?; - remove_id(store, name_info.0)?; - Ok(name_info.0) -} - #[derive(Debug, PartialEq)] pub struct PagedLoad { - pub names: Vec<(NameId, RegisteredName)>, + pub names: Vec, pub limit: usize, pub start_next_after: Option, } @@ -156,9 +137,10 @@ pub fn load_all_paged( let names = names() .range(store, start, None, Order::Ascending) .take(limit) + .map(|res| res.map(|(_, name)| name)) .collect::>>()?; - let start_next_after = names.last().map(|name| name.0); + let start_next_after = names.last().map(|name| name.id); Ok(PagedLoad { names, @@ -169,8 +151,6 @@ pub fn load_all_paged( #[cfg(test)] mod tests { - use std::iter::zip; - use cosmwasm_std::{ testing::{MockApi, MockQuerier}, MemoryStorage, OwnedDeps, @@ -178,11 +158,11 @@ mod tests { use rstest::rstest; use crate::{ - error::NameServiceError, test_helpers::{ fixture::{name_fixture, name_fixture_full}, - helpers::instantiate_test_contract, + transactions::instantiate_test_contract, }, + NameServiceError, }; use super::*; @@ -197,64 +177,55 @@ mod tests { #[rstest::fixture] fn uniq_names() -> Vec { vec![ - name_fixture_full("one", "address_one", "owner_one"), - name_fixture_full("two", "address_two", "owner_two"), - name_fixture_full("three", "address_three", "owner_three"), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_three", "owner_three"), ] } #[rstest::fixture] fn overlapping_addresses() -> Vec { vec![ - name_fixture_full("one", "address_one", "owner_one"), - name_fixture_full("two", "address_two", "owner_two"), - name_fixture_full("three", "address_two", "owner_three"), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_two", "owner_three"), ] } #[rstest::fixture] fn overlapping_owners() -> Vec { vec![ - name_fixture_full("one", "address_one", "owner_one"), - name_fixture_full("two", "address_two", "owner_two"), - name_fixture_full("three", "address_three", "owner_two"), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_three", "owner_two"), ] } - fn assert_not_registered(store: &dyn Storage, names: Vec, ids: Vec) { - let names: Vec<(NameId, RegisteredName)> = zip(ids, names).collect(); + fn assert_not_registered(store: &dyn Storage, names: Vec) { let loaded = load_all_paged(store, None, None).unwrap(); - for (id, name) in &names { - assert!(!has_name_id(store, *id)); - assert!(!has_name(store, &name.name)); - assert!(!loaded.names.iter().any(|(i, _)| i == id)); - assert!(!loaded.names.iter().any(|(_, n)| n == name)); + for name in &names { + assert!(!has_name_id(store, name.id)); + assert!(!has_name(store, name.entry())); + assert!(!loaded.names.iter().any(|l_name| l_name.id == name.id)); + assert!(!loaded.names.iter().any(|l_name| l_name == name)); } } - fn assert_registered(store: &dyn Storage, names: Vec, ids: Vec) { - assert!(names.len() == ids.len()); - let names: Vec<(NameId, RegisteredName)> = zip(ids, names).collect(); + fn assert_registered(store: &dyn Storage, names: Vec) { let loaded = load_all_paged(store, None, None).unwrap(); - for (id, name) in &names { - assert!(has_name_id(store, *id)); - assert!(has_name(store, &name.name)); - assert!(loaded.names.iter().filter(|(i, _)| i == id).count() == 1); - assert!(loaded.names.iter().filter(|(_, n)| n == name).count() == 1); + for name in &names { + assert!(has_name_id(store, name.id)); + assert!(has_name(store, name.entry())); + assert!(loaded.names.iter().filter(|n| n == &name).count() == 1); } } - fn assert_only_these_registered( - store: &dyn Storage, - names: Vec, - ids: Vec, - ) { - let last_id = *ids.last().unwrap(); - let names: Vec<(NameId, RegisteredName)> = zip(ids, names).collect(); - for (id, name) in &names { - assert!(has_name_id(store, *id)); - assert!(has_name(store, &name.name)); + fn assert_only_these_registered(store: &dyn Storage, names: Vec) { + for name in &names { + assert!(has_name_id(store, name.id)); + assert!(has_name(store, name.entry())); } + let last_id = names.last().unwrap().id; assert_eq!( load_all_paged(store, None, None).unwrap(), PagedLoad { @@ -267,102 +238,107 @@ mod tests { #[rstest] fn single_basic_save_works(mut deps: TestDeps) { - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); } #[rstest] fn save_same_name_twice_fails(mut deps: TestDeps) { - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); assert!(matches!( - save(deps.as_mut().storage, &name_fixture()).unwrap_err(), + save(deps.as_mut().storage, &name_fixture(2)).unwrap_err(), NameServiceError::Std(StdError::GenericErr { .. }) )); } + #[rstest] + fn remove_id_works(mut deps: TestDeps) { + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); + assert!(has_name_id(&deps.storage, 1)); + assert!(has_name(&deps.storage, name_fixture(1).entry())); + remove_id(deps.as_mut().storage, 1).unwrap(); + assert!(!has_name_id(&deps.storage, 1)); + assert!(!has_name(&deps.storage, name_fixture(1).entry())); + } + + #[rstest] + fn remove_name_works(mut deps: TestDeps) { + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); + assert!(has_name_id(&deps.storage, 1)); + assert!(has_name(&deps.storage, name_fixture(1).entry())); + remove_name(deps.as_mut().storage, name_fixture(1).name.name).unwrap(); + assert!(!has_name_id(&deps.storage, 1)); + assert!(!has_name(&deps.storage, name_fixture(1).entry())); + } + #[rstest] fn has_name_works(mut deps: TestDeps) { - assert!(!has_name(&deps.storage, &name_fixture().name)); - save(deps.as_mut().storage, &name_fixture()).unwrap(); - assert!(has_name(&deps.storage, &name_fixture().name)); + assert!(!has_name(&deps.storage, name_fixture(1).entry())); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); + assert!(has_name(&deps.storage, name_fixture(1).entry())); } #[rstest] fn has_name_id_works(mut deps: TestDeps) { assert!(!has_name_id(&deps.storage, 1)); - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); assert!(has_name_id(&deps.storage, 1)); } #[rstest] fn has_name_id_with_incorrect_id_fails(mut deps: TestDeps) { assert!(!has_name_id(&deps.storage, 2)); - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); assert!(!has_name_id(&deps.storage, 2)); } - #[rstest] - fn load_name_entry_works(mut deps: TestDeps) { - assert_eq!( - load_name_entry(deps.as_ref().storage, &name_fixture().name).unwrap_err(), - NameServiceError::NameNotFound { - name: name_fixture().name - } - ); - save(deps.as_mut().storage, &name_fixture()).unwrap(); - assert_eq!( - load_name_entry(deps.as_ref().storage, &name_fixture().name).unwrap(), - (1, name_fixture()) - ); - } - #[rstest] fn load_id_works(mut deps: TestDeps) { assert_eq!( load_id(deps.as_ref().storage, 1).unwrap_err(), NameServiceError::NotFound { name_id: 1 } ); - save(deps.as_mut().storage, &name_fixture()).unwrap(); - assert_eq!(load_id(deps.as_ref().storage, 1).unwrap(), name_fixture(),); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); + assert_eq!(load_id(deps.as_ref().storage, 1).unwrap(), name_fixture(1),); } #[rstest] fn load_name_works(mut deps: TestDeps) { assert_eq!( - load_name(deps.as_ref().storage, &name_fixture().name).unwrap_err(), + load_name(deps.as_ref().storage, name_fixture(1).entry()).unwrap_err(), NameServiceError::NameNotFound { - name: name_fixture().name + name: name_fixture(1).name.name, } ); - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); assert_eq!( - load_name(deps.as_ref().storage, &name_fixture().name).unwrap(), - name_fixture(), + load_name(deps.as_ref().storage, name_fixture(1).entry()).unwrap(), + name_fixture(1), ); } #[rstest] fn load_address_works(mut deps: TestDeps) { assert_eq!( - load_address(deps.as_ref().storage, &name_fixture().address).unwrap(), + load_address(deps.as_ref().storage, &name_fixture(1).name.address).unwrap(), vec![], ); - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); assert_eq!( - load_address(deps.as_ref().storage, &name_fixture().address).unwrap(), - vec![(1, name_fixture())], + load_address(deps.as_ref().storage, &name_fixture(1).name.address).unwrap(), + vec![name_fixture(1)], ); } #[rstest] fn load_owner_works(mut deps: TestDeps) { assert_eq!( - load_owner(deps.as_ref().storage, name_fixture().owner).unwrap(), + load_owner(deps.as_ref().storage, name_fixture(1).owner).unwrap(), vec![], ); - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); assert_eq!( - load_owner(deps.as_ref().storage, name_fixture().owner).unwrap(), - vec![(1, name_fixture())], + load_owner(deps.as_ref().storage, name_fixture(1).owner).unwrap(), + vec![name_fixture(1)], ); } @@ -380,60 +356,19 @@ mod tests { assert_eq!( load_all_paged(&deps.storage, None, None).unwrap(), PagedLoad { - names: vec![(1, uniq_names[0].clone())], + names: vec![uniq_names[0].clone()], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(1), } ); } - #[rstest] - fn remove_id_works(mut deps: TestDeps) { - save(deps.as_mut().storage, &name_fixture()).unwrap(); - assert!(has_name_id(&deps.storage, 1)); - assert!(has_name(&deps.storage, &name_fixture().name)); - remove_id(deps.as_mut().storage, 1).unwrap(); - assert!(!has_name_id(&deps.storage, 1)); - assert!(!has_name(&deps.storage, &name_fixture().name)); - } - - #[rstest] - fn remove_name_works(mut deps: TestDeps) { - save(deps.as_mut().storage, &name_fixture()).unwrap(); - assert!(has_name_id(&deps.storage, 1)); - assert!(has_name(&deps.storage, &name_fixture().name)); - remove_name(deps.as_mut().storage, name_fixture().name).unwrap(); - assert!(!has_name_id(&deps.storage, 1)); - assert!(!has_name(&deps.storage, &name_fixture().name)); - } - #[rstest] fn save_set_of_unique_names_works(mut deps: TestDeps, uniq_names: Vec) { - let num = uniq_names.len() as NameId; - let ids = (1..=num).collect::>(); - assert_not_registered(&deps.storage, uniq_names.clone(), ids.clone()); - let saved_ids = save_all(deps.as_mut().storage, &uniq_names).unwrap(); - assert_eq!(saved_ids, ids); - assert_registered(&deps.storage, uniq_names.clone(), ids.clone()); - assert_only_these_registered(&deps.storage, uniq_names, ids); - } - - #[rstest] - fn save_set_of_unique_names_generates_ids(mut deps: TestDeps, uniq_names: Vec) { - let num = uniq_names.len() as NameId; - let ids = save_all(deps.as_mut().storage, &uniq_names).unwrap(); - assert_eq!(ids, (1..=num).collect::>()); - } - - #[rstest] - fn load_name_entry_for_unique_set_works(mut deps: TestDeps, uniq_names: Vec) { + assert_not_registered(&deps.storage, uniq_names.clone()); save_all(deps.as_mut().storage, &uniq_names).unwrap(); - for (id, name) in uniq_names.iter().enumerate() { - assert_eq!( - load_name_entry(deps.as_ref().storage, &name.name).unwrap(), - (id as NameId + 1, name.clone()), - ); - } + assert_registered(&deps.storage, uniq_names.clone()); + assert_only_these_registered(&deps.storage, uniq_names); } #[rstest] @@ -441,7 +376,7 @@ mod tests { save_all(deps.as_mut().storage, &uniq_names).unwrap(); for name in uniq_names { assert_eq!( - load_name(deps.as_ref().storage, &name.name).unwrap(), + load_name(deps.as_ref().storage, name.entry()).unwrap(), name.clone(), ); } @@ -452,17 +387,14 @@ mod tests { mut deps: TestDeps, uniq_names: Vec, ) { - let ids = save_all(deps.as_mut().storage, &uniq_names).unwrap(); - remove_id(deps.as_mut().storage, ids[1]).unwrap(); + save_all(deps.as_mut().storage, &uniq_names).unwrap(); + remove_id(deps.as_mut().storage, 2).unwrap(); assert_eq!( load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - ( - 3, - name_fixture_full("three", "address_three", "owner_three") - ), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(3, "three", "address_three", "owner_three"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -476,16 +408,13 @@ mod tests { uniq_names: Vec, ) { save_all(deps.as_mut().storage, &uniq_names).unwrap(); - remove_name(deps.as_mut().storage, uniq_names[1].name.clone()).unwrap(); + remove_name(deps.as_mut().storage, uniq_names[1].entry().clone()).unwrap(); assert_eq!( load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - ( - 3, - name_fixture_full("three", "address_three", "owner_three") - ), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(3, "three", "address_three", "owner_three"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -498,9 +427,9 @@ mod tests { mut deps: TestDeps, overlapping_addresses: Vec, ) { - let ids = save_all(deps.as_mut().storage, &overlapping_addresses).unwrap(); - assert_registered(&deps.storage, overlapping_addresses.clone(), ids.clone()); - assert_only_these_registered(&deps.storage, overlapping_addresses, ids); + save_all(deps.as_mut().storage, &overlapping_addresses).unwrap(); + assert_registered(&deps.storage, overlapping_addresses.clone()); + assert_only_these_registered(&deps.storage, overlapping_addresses); } #[rstest] @@ -513,9 +442,9 @@ mod tests { load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (2, name_fixture_full("two", "address_two", "owner_two")), - (3, name_fixture_full("three", "address_two", "owner_three")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_two", "owner_three"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -524,8 +453,8 @@ mod tests { assert_eq!( load_address(deps.as_ref().storage, &Address::new("address_two")).unwrap(), vec![ - (2, name_fixture_full("two", "address_two", "owner_two")), - (3, name_fixture_full("three", "address_two", "owner_three")), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_two", "owner_three"), ] ); } @@ -535,14 +464,14 @@ mod tests { mut deps: TestDeps, overlapping_addresses: Vec, ) { - let ids = save_all(deps.as_mut().storage, &overlapping_addresses).unwrap(); - remove_id(deps.as_mut().storage, ids[1]).unwrap(); + save_all(deps.as_mut().storage, &overlapping_addresses).unwrap(); + remove_id(deps.as_mut().storage, 2).unwrap(); assert_eq!( load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (3, name_fixture_full("three", "address_two", "owner_three")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(3, "three", "address_two", "owner_three"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -556,13 +485,17 @@ mod tests { overlapping_addresses: Vec, ) { save_all(deps.as_mut().storage, &overlapping_addresses).unwrap(); - remove_name(deps.as_mut().storage, overlapping_addresses[1].name.clone()).unwrap(); + remove_name( + deps.as_mut().storage, + overlapping_addresses[1].name.name.clone(), + ) + .unwrap(); assert_eq!( load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (3, name_fixture_full("three", "address_two", "owner_three")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(3, "three", "address_two", "owner_three"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -575,9 +508,9 @@ mod tests { mut deps: TestDeps, overlapping_owners: Vec, ) { - let ids = save_all(deps.as_mut().storage, &overlapping_owners).unwrap(); - assert_registered(&deps.storage, overlapping_owners.clone(), ids.clone()); - assert_only_these_registered(&deps.storage, overlapping_owners, ids); + save_all(deps.as_mut().storage, &overlapping_owners).unwrap(); + assert_registered(&deps.storage, overlapping_owners.clone()); + assert_only_these_registered(&deps.storage, overlapping_owners); } #[rstest] @@ -590,9 +523,9 @@ mod tests { load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (2, name_fixture_full("two", "address_two", "owner_two")), - (3, name_fixture_full("three", "address_three", "owner_two")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_three", "owner_two"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -601,8 +534,8 @@ mod tests { assert_eq!( load_owner(deps.as_ref().storage, Addr::unchecked("owner_two")).unwrap(), vec![ - (2, name_fixture_full("two", "address_two", "owner_two")), - (3, name_fixture_full("three", "address_three", "owner_two")), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_three", "owner_two"), ] ); } @@ -612,16 +545,16 @@ mod tests { mut deps: TestDeps, overlapping_owners: Vec, ) { - let ids = save_all(deps.as_mut().storage, &overlapping_owners).unwrap(); - assert_registered(&deps.storage, overlapping_owners.clone(), ids.clone()); - assert_only_these_registered(&deps.storage, overlapping_owners, ids); + save_all(deps.as_mut().storage, &overlapping_owners).unwrap(); + assert_registered(&deps.storage, overlapping_owners.clone()); + assert_only_these_registered(&deps.storage, overlapping_owners); remove_id(deps.as_mut().storage, 2).unwrap(); assert_eq!( load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (3, name_fixture_full("three", "address_three", "owner_two")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(3, "three", "address_three", "owner_two"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -634,16 +567,16 @@ mod tests { mut deps: TestDeps, overlapping_owners: Vec, ) { - let ids = save_all(deps.as_mut().storage, &overlapping_owners).unwrap(); - assert_registered(&deps.storage, overlapping_owners.clone(), ids.clone()); - assert_only_these_registered(&deps.storage, overlapping_owners.clone(), ids); - remove_name(deps.as_mut().storage, overlapping_owners[1].name.clone()).unwrap(); + save_all(deps.as_mut().storage, &overlapping_owners).unwrap(); + assert_registered(&deps.storage, overlapping_owners.clone()); + assert_only_these_registered(&deps.storage, overlapping_owners.clone()); + remove_name(deps.as_mut().storage, overlapping_owners[1].entry().clone()).unwrap(); assert_eq!( load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (3, name_fixture_full("three", "address_three", "owner_two")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(3, "three", "address_three", "owner_two"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -658,8 +591,8 @@ mod tests { load_all_paged(&deps.storage, Some(2), None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (2, name_fixture_full("two", "address_two", "owner_two")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(2, "two", "address_two", "owner_two"), ], limit: 2, start_next_after: Some(2), @@ -668,10 +601,12 @@ mod tests { assert_eq!( load_all_paged(&deps.storage, Some(1), Some(2)).unwrap(), PagedLoad { - names: vec![( + names: vec![name_fixture_full( 3, - name_fixture_full("three", "address_three", "owner_three") - ),], + "three", + "address_three", + "owner_three" + )], limit: 1, start_next_after: Some(3), } @@ -679,10 +614,12 @@ mod tests { assert_eq!( load_all_paged(&deps.storage, Some(2), Some(2)).unwrap(), PagedLoad { - names: vec![( + names: vec![name_fixture_full( 3, - name_fixture_full("three", "address_three", "owner_three") - ),], + "three", + "address_three", + "owner_three" + )], limit: 2, start_next_after: Some(3), } diff --git a/contracts/name-service/src/state/nonce.rs b/contracts/name-service/src/state/nonce.rs new file mode 100644 index 0000000000..bf865af82a --- /dev/null +++ b/contracts/name-service/src/state/nonce.rs @@ -0,0 +1,71 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::{constants::SIGNING_NONCES_NAMESPACE, Result}; + +use cosmwasm_std::{Addr, Storage}; +use cw_storage_plus::Map; +use nym_contracts_common::signing::Nonce; + +pub const NONCES: Map<'_, Addr, Nonce> = Map::new(SIGNING_NONCES_NAMESPACE); + +pub fn get_signing_nonce(storage: &dyn Storage, address: Addr) -> Result { + let nonce = NONCES.may_load(storage, address)?.unwrap_or(0); + Ok(nonce) +} + +fn update_signing_nonce(storage: &mut dyn Storage, address: Addr, value: Nonce) -> Result<()> { + NONCES + .save(storage, address, &value) + .map_err(|err| err.into()) +} + +pub fn increment_signing_nonce(storage: &mut dyn Storage, address: Addr) -> Result<()> { + // get the current nonce + let nonce = get_signing_nonce(storage, address.clone())?; + + // increment it for the next use + update_signing_nonce(storage, address, nonce + 1) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_helpers::transactions::instantiate_test_contract; + use cosmwasm_std::{ + testing::{MockApi, MockQuerier}, + MemoryStorage, OwnedDeps, + }; + use rstest::rstest; + + type TestDeps = OwnedDeps; + + #[rstest::fixture] + fn deps() -> TestDeps { + instantiate_test_contract() + } + + fn addr(s: &str) -> Addr { + Addr::unchecked(s) + } + + #[rstest] + fn getting_signing_nonce_doesnt_increment_it(deps: TestDeps) { + assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0); + assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0); + } + + #[rstest] + fn increment_works(mut deps: TestDeps) { + assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0); + increment_signing_nonce(&mut deps.storage, addr("gunnar")).unwrap(); + assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 1); + } + + #[rstest] + fn incrementing_is_independent(mut deps: TestDeps) { + increment_signing_nonce(&mut deps.storage, addr("gunnar")).unwrap(); + assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 1); + assert_eq!(get_signing_nonce(&deps.storage, addr("bjorn")).unwrap(), 0); + } +} diff --git a/contracts/name-service/src/test_helpers/assert.rs b/contracts/name-service/src/test_helpers/assert.rs index b9c862906b..1f2475b196 100644 --- a/contracts/name-service/src/test_helpers/assert.rs +++ b/contracts/name-service/src/test_helpers/assert.rs @@ -1,11 +1,12 @@ use cosmwasm_std::{from_binary, testing::mock_env, Addr, Coin, Deps}; +use nym_contracts_common::signing::Nonce; use nym_name_service_common::{ msg::QueryMsg, response::{ConfigResponse, PagedNamesListResponse}, - NameEntry, NameId, + NameId, RegisteredName, }; -use crate::{constants::NAME_DEFAULT_RETRIEVAL_LIMIT, error::NameServiceError}; +use crate::{constants::NAME_DEFAULT_RETRIEVAL_LIMIT, NameServiceError}; pub fn assert_config(deps: Deps, admin: &Addr, deposit_required: Coin) { crate::state::assert_admin(deps, admin).unwrap(); @@ -14,10 +15,10 @@ pub fn assert_config(deps: Deps, admin: &Addr, deposit_required: Coin) { assert_eq!(config, ConfigResponse { deposit_required }); } -pub fn assert_names(deps: Deps, expected_names: &[NameEntry]) { +pub fn assert_names(deps: Deps, expected_names: &[RegisteredName]) { let res = crate::contract::query(deps, mock_env(), QueryMsg::all()).unwrap(); let names: PagedNamesListResponse = from_binary(&res).unwrap(); - let start_next_after = expected_names.iter().last().map(|s| s.name_id); + let start_next_after = expected_names.iter().last().map(|s| s.id); assert_eq!( names, PagedNamesListResponse { @@ -28,16 +29,16 @@ pub fn assert_names(deps: Deps, expected_names: &[NameEntry]) { ); } -pub fn assert_name(deps: Deps, expected_name: &NameEntry) { +pub fn assert_name(deps: Deps, expected_name: &RegisteredName) { let res = crate::contract::query( deps, mock_env(), QueryMsg::NameId { - name_id: expected_name.name_id, + name_id: expected_name.id, }, ) .unwrap(); - let names: NameEntry = from_binary(&res).unwrap(); + let names: RegisteredName = from_binary(&res).unwrap(); assert_eq!(&names, expected_name); } @@ -63,3 +64,16 @@ pub fn assert_not_found(deps: Deps, expected_id: NameId) { } )); } + +pub fn assert_current_nonce(deps: Deps, address: &Addr, expected_nonce: Nonce) { + let res = crate::contract::query( + deps, + mock_env(), + QueryMsg::SigningNonce { + address: address.to_string(), + }, + ) + .unwrap(); + let nonce: Nonce = from_binary(&res).unwrap(); + assert_eq!(nonce, expected_nonce); +} diff --git a/contracts/name-service/src/test_helpers/fixture.rs b/contracts/name-service/src/test_helpers/fixture.rs index 3df82f6086..5c24ece5d8 100644 --- a/contracts/name-service/src/test_helpers/fixture.rs +++ b/contracts/name-service/src/test_helpers/fixture.rs @@ -1,29 +1,103 @@ -use cosmwasm_std::Addr; -use nym_name_service_common::{Address, NameEntry, NameId, NymName, RegisteredName}; +use cosmwasm_std::{Addr, Coin, DepsMut}; +use nym_contracts_common::{signing::MessageSignature, IdentityKeyRef}; +use nym_crypto::asymmetric::identity; +use nym_name_service_common::{Address, NameDetails, NameId, NymName, RegisteredName}; +use rand_chacha::rand_core::{CryptoRng, RngCore}; -use super::helpers::nyms; +use super::{ + helpers::nyms, + signing::{ed25519_sign_message, name_register_sign_payload}, +}; -pub fn name_fixture_full(name: &str, nym_address: &str, owner: &str) -> RegisteredName { +pub fn new_name( + name_id: NameId, + name: &NymName, + address: &Address, + owner: &Addr, + identity_key: IdentityKeyRef, +) -> RegisteredName { RegisteredName { - name: NymName::new(name).unwrap(), - address: Address::new(nym_address), - owner: Addr::unchecked(owner), + id: name_id, + name: NameDetails { + name: name.clone(), + address: address.clone(), + identity_key: identity_key.to_string(), + }, + owner: owner.clone(), block_height: 12345, deposit: nyms(100), } } -pub fn name_fixture() -> RegisteredName { - name_fixture_full("my-service", "client_id.client_key@gateway_id", "steve") +pub fn name_fixture(id: NameId) -> RegisteredName { + new_name( + id, + &NymName::new("my-service").unwrap(), + &Address::new("client_id.client_key@gateway_id"), + &Addr::unchecked("steve"), + "identity", + ) } -pub fn name_fixture_name(name: &str) -> RegisteredName { - name_fixture_full(name, "client_id.client_key@gateway_id", "steve") +#[allow(unused)] +pub fn name_fixture_with_name(id: NameId, name: &str, address: &str) -> RegisteredName { + new_name( + id, + &NymName::new(name).unwrap(), + &Address::new(address), + &Addr::unchecked("steve"), + "identity", + ) } -pub fn name_entry(name_id: NameId, name: NymName, address: Address, owner: Addr) -> NameEntry { - NameEntry { - name_id, - name: name_fixture_full(name.as_str(), address.as_str(), owner.as_str()), - } +pub fn name_fixture_full(id: NameId, name: &str, address: &str, owner: &str) -> RegisteredName { + new_name( + id, + &NymName::new(name).unwrap(), + &Address::new(address), + &Addr::unchecked(owner), + "identity", + ) +} + +// Create a new name, using a correctly generted identity key +pub fn new_name_details( + rng: &mut R, + name: &str, + nym_address: &str, +) -> (NameDetails, identity::KeyPair) +where + R: RngCore + CryptoRng, +{ + let keypair = identity::KeyPair::new(rng); + ( + NameDetails { + name: NymName::new(name).unwrap(), + address: Address::new(nym_address), + identity_key: keypair.public_key().to_base58_string(), + }, + keypair, + ) +} + +// Create a new service, with a correctly generated identity key, and sign it +pub fn new_name_details_with_sign( + deps: DepsMut<'_>, + rng: &mut R, + name: &str, + nym_address: &str, + owner: &str, + deposit: Coin, +) -> (NameDetails, MessageSignature) +where + R: RngCore + CryptoRng, +{ + // Service + let (name, keypair) = new_name_details(rng, name, nym_address); + + // Sign + let sign_msg = name_register_sign_payload(deps.as_ref(), owner, name.clone(), deposit); + let owner_signature = ed25519_sign_message(sign_msg, keypair.private_key()); + + (name, owner_signature) } diff --git a/contracts/name-service/src/test_helpers/helpers.rs b/contracts/name-service/src/test_helpers/helpers.rs index 0bd4f2ef5f..145802e061 100644 --- a/contracts/name-service/src/test_helpers/helpers.rs +++ b/contracts/name-service/src/test_helpers/helpers.rs @@ -1,19 +1,16 @@ -use cosmwasm_std::{ - coin, coins, - testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}, - Coin, DepsMut, Event, MemoryStorage, OwnedDeps, Response, -}; +use cosmwasm_std::{Coin, Event, Response}; use cw_multi_test::AppResponse; -use nym_name_service_common::{ - events::{NameEventType, NAME_ID}, - msg::{ExecuteMsg, InstantiateMsg}, - NameId, NymName, RegisteredName, -}; +use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; pub fn nyms(amount: u64) -> Coin { Coin::new(amount.into(), "unym") } +pub fn test_rng() -> ChaCha20Rng { + let dummy_seed = [42u8; 32]; + ChaCha20Rng::from_seed(dummy_seed) +} + pub fn get_event_types(response: &Response, event_type: &str) -> Vec { response .events @@ -55,53 +52,3 @@ pub fn get_app_attribute(response: &AppResponse, event_type: &str, key: &str) -> .value .clone() } - -#[allow(unused)] -pub fn get_app_attributes(response: &AppResponse, event_type: &str, key: &str) -> Vec { - get_app_event_types(response, event_type) - .iter() - .map(|ev| { - ev.attributes - .iter() - .find(|attr| attr.key == key) - .unwrap() - .value - .clone() - }) - .collect::>() -} - -pub fn instantiate_test_contract() -> OwnedDeps { - let mut deps = mock_dependencies(); - let msg = InstantiateMsg { - deposit_required: coin(100, "unym"), - }; - let env = mock_env(); - let info = mock_info("creator", &[]); - let res = crate::instantiate(deps.as_mut(), env, info, msg).unwrap(); - assert_eq!(res.messages.len(), 0); - deps -} - -pub fn register_name(deps: DepsMut<'_>, name: &RegisteredName) -> NameId { - let msg: ExecuteMsg = name.clone().into(); - let info = mock_info(name.owner.as_str(), &coins(100, "unym")); - let res = crate::execute(deps, mock_env(), info, msg).unwrap(); - let name_id: NameId = get_attribute(&res, &NameEventType::Register.to_string(), NAME_ID) - .parse() - .unwrap(); - name_id -} - -pub fn delete_name_id(deps: DepsMut<'_>, name_id: NameId, owner: &str) { - let msg = ExecuteMsg::DeleteId { name_id }; - let info = mock_info(owner, &[]); - crate::execute(deps, mock_env(), info, msg).unwrap(); -} - -#[allow(unused)] -pub fn delete_name(deps: DepsMut<'_>, name: NymName, owner: &str) { - let msg = ExecuteMsg::DeleteName { name }; - let info = mock_info(owner, &[]); - crate::execute(deps, mock_env(), info, msg).unwrap(); -} diff --git a/contracts/name-service/src/test_helpers/mod.rs b/contracts/name-service/src/test_helpers/mod.rs index b1e4a18e01..ee0717f4e4 100644 --- a/contracts/name-service/src/test_helpers/mod.rs +++ b/contracts/name-service/src/test_helpers/mod.rs @@ -1,4 +1,5 @@ pub mod assert; pub mod fixture; pub mod helpers; -pub mod test_setup; +pub mod signing; +pub mod transactions; diff --git a/contracts/name-service/src/test_helpers/signing.rs b/contracts/name-service/src/test_helpers/signing.rs new file mode 100644 index 0000000000..d7431fc7ed --- /dev/null +++ b/contracts/name-service/src/test_helpers/signing.rs @@ -0,0 +1,39 @@ +use cosmwasm_std::{Addr, Coin, Deps}; +use nym_contracts_common::signing::{ + MessageSignature, SignableMessage, SigningAlgorithm, SigningPurpose, +}; +use nym_crypto::asymmetric::identity; +use nym_name_service_common::{ + signing_types::{construct_name_register_sign_payload, SignableNameRegisterMsg}, + NameDetails, +}; +use serde::Serialize; + +use crate::state; + +pub fn name_register_sign_payload( + deps: Deps<'_>, + owner: &str, + name: NameDetails, + deposit: Coin, +) -> SignableNameRegisterMsg { + let owner = Addr::unchecked(owner); + let nonce = state::get_signing_nonce(deps.storage, owner.clone()).unwrap(); + construct_name_register_sign_payload(nonce, owner, deposit, name) +} + +pub fn ed25519_sign_message( + message: SignableMessage, + private_key: &identity::PrivateKey, +) -> MessageSignature { + match message.algorithm { + SigningAlgorithm::Ed25519 => { + let plaintext = message.to_plaintext().unwrap(); + let signature = private_key.sign(&plaintext); + MessageSignature::from(signature.to_bytes().as_ref()) + } + SigningAlgorithm::Secp256k1 => { + unimplemented!() + } + } +} diff --git a/contracts/name-service/src/test_helpers/transactions.rs b/contracts/name-service/src/test_helpers/transactions.rs new file mode 100644 index 0000000000..6458ad5b3e --- /dev/null +++ b/contracts/name-service/src/test_helpers/transactions.rs @@ -0,0 +1,72 @@ +use cosmwasm_std::{ + coin, + testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}, + DepsMut, MemoryStorage, OwnedDeps, +}; +use nym_name_service_common::{ + events::{NameEventType, NAME_ID}, + msg::{ExecuteMsg, InstantiateMsg}, + NameDetails, NameId, NymName, +}; +use rand_chacha::rand_core::{CryptoRng, RngCore}; + +use super::helpers::{get_attribute, nyms}; + +pub fn instantiate_test_contract() -> OwnedDeps { + let mut deps = mock_dependencies(); + let msg = InstantiateMsg { + deposit_required: coin(100, "unym"), + }; + let env = mock_env(); + let info = mock_info("creator", &[]); + let res = crate::instantiate(deps.as_mut(), env, info, msg).unwrap(); + assert_eq!(res.messages.len(), 0); + deps +} + +pub fn register_name( + mut deps: DepsMut<'_>, + rng: &mut R, + name: &str, + nym_address: &str, + owner: &str, +) -> (NameId, NameDetails) +where + R: RngCore + CryptoRng, +{ + let deposit = nyms(100); + let (name, owner_signature) = super::fixture::new_name_details_with_sign( + deps.branch(), + rng, + name, + nym_address, + owner, + deposit.clone(), + ); + + // Register + let msg = ExecuteMsg::Register { + name: name.clone(), + owner_signature, + }; + let info = mock_info(owner, &[deposit]); + let res = crate::execute(deps, mock_env(), info, msg).unwrap(); + + let name_id: NameId = get_attribute(&res, &NameEventType::Register.to_string(), NAME_ID) + .parse() + .unwrap(); + (name_id, name) +} + +pub fn delete_name_id(deps: DepsMut<'_>, name_id: NameId, owner: &str) { + let msg = ExecuteMsg::DeleteId { name_id }; + let info = mock_info(owner, &[]); + crate::execute(deps, mock_env(), info, msg).unwrap(); +} + +#[allow(unused)] +pub fn delete_name(deps: DepsMut<'_>, name: NymName, owner: &str) { + let msg = ExecuteMsg::DeleteName { name }; + let info = mock_info(owner, &[]); + crate::execute(deps, mock_env(), info, msg).unwrap(); +} diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index be35e9c30a..aeae804662 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -40,8 +40,7 @@ # Community Resources - [Nym DevRel AMAs](community-resources/ama.md) -- [Community Applications](community-resources/community-applications.md) -- [Community Guides](community-resources/community-guides.md) +- [Community Applications and Guides](community-resources/community-applications-and-guides.md) - [Change Service Grantee Information](info-request.md) - [Rewards FAQ](community-resources/rewards-faq.md) --- diff --git a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md new file mode 100644 index 0000000000..ba00922658 --- /dev/null +++ b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md @@ -0,0 +1,116 @@ +# Community Applications + +We love seeing our developer community create applications using Nym. If you would like to share your application with the community, please submit a pull request to the `main` branch of the `nymtech/dev-portal` [repository](https://github.com/nymtech/dev-portal). + + + +## Pastenym + +>A [pastebin](https://pastebin.com) inspired project, offer a solution for sharing text with Nym products to offer full anonymity, even on metadata level. + +* [Github](https://github.com/notrustverify/pastenym) +* [Deployed App](https://pastenym.ch) + + + + +## Nostr-Nym + +> [Nostr-nym](https://github.com/notrustverify/nostr-nym) offer a solution to use [Nostr](https://nostr.how/en/what-is-nostr) protocol by giving the possibility to run a relay on mixnet. By using a nostr client compatible with the mixnet, users can protect their privacy to be able to use Nostr has the want, without being observed. + +* [Github](https://github.com/notrustverify/nostr-nym) +* [Deployed App](https://nostrnym.pnproxy.org/) +* [Documentation](https://blog.notrustverify.ch/nostr-relay-on-nym) + + + + +## Spook + +> Ethereum RPC request mixer uses the Nym network mixing service to anonymize RPC requests to the Ethereum network without revealing sensitive data and metadata. +* [Github](https://github.com/EdenBlockVC/spook) + + + + +## Ethereum Transaction Broadcaster + +> Ethereum Transaction Broadcaster that uses the Nym Mixnet to provide privacy and anonymity for transactions on the Ethereum network command-line interface. + +* [Github](https://github.com/noot/nym-ethtx) + + + + +## NymDrive + +> An open-source, decentralized, E2E encrypted, privacy friendly alternative to Google Drive/Dropbox, allowing for file encryption and decryption using the Nym Mixnet. +* [Github](https://github.com/saleel/nymdrive) +* [Demo](https://www.youtube.com/watch?v=5Rx73nw8NYI) +* [Presentation](https://docs.google.com/presentation/d/1MpvIK32Mx9VKLVfMTcvbeyrsKHHUsTvDQ-3n31dR0NE/edit#slide=id.p) + + + + +## Nym Dashboard + +> Developed by No Trust Verify, this dashboard is a great tool to get information about the mixnet, gateways and mixnodes. +* [Deployed App](https://status.notrustverify.ch/d/CW3L7dVVk/nym-mixnet?orgId=1) + + + + +## Is Nym Up + +> Explore whether we're up through IsNymUp, a tool that helps check the heath of the Nym network as well as some mixnet related statistics! +* [Deployed App](https://isnymup.com/) + + + + +## DarkFi over Nym + +> DarkFi leverages Nym's mixnet as a pluggable transport for IRCD, their p2p IRC variant. Users can anonymously connect to peers over the network, ensuring secure and private communication within the DarkFi ecosystem. +* [Github](https://github.com/darkrenaissance/darkfi) +* [Documentation](https://darkrenaissance.github.io/darkfi/clients/nym_outbound.html) + + + + +## Nymstr email + +> Experience secure and private email communication with ease using Nymstr email, which enables seamless transmission of emails over a SOCKS5 proxy and our NYM mixnet! +* [Github](https://github.com/dial0ut/nymstr-email) + + + + +## Minibolt + +> Anonymize your p2p inventory messages and mempool for your Bitcoin & Lightning full nodes on consumer PCs! +* [Github](https://github.com/minibolt-guide/minibolt) +* [Documentation](https://v2.minibolt.info/bonus-guides/system/nym-mixnet#proxying-bitcoin-core) + + + +

+ +# Community Guides + +We aren't the only ones writing documentation: the Nym developer community is also a great source of guides and resources, some of which we've included here. + +## No Trust Verify + +>No Trust Verify is a project that aims to build open-source, privacy-enhancing technologies that make it easier to use the Internet securely and anonymously. Their focus is on providing tools and services that make it simple for developers to create decentralized applications (dApps) that respect users' privacy. + +* [Awesome Nym list](https://notrustverify.github.io/awesome-nym/) ([GitHub](https://github.com/notrustverify/awesome-nym)) +* A lot of guides can be found on the [NTV Blog](https://blog.notrustverify.ch/) + + +## The Way of the NYMJA + +by Pineapple Proxy🍍 + +>Born out of a study group from Nym's Shipyard Academy, Pineapple Proxy has emerged as a cluster of motivated and skilled individuals who see the new internet taking shape. With vibecare at the heart of their approach, this zesty collective is on a mission to make privacy convenient for everyone via content, new tools, events, and novel experiences. They believe in collective intelligence, empathy, and collaboration as the means by which privacy will become a meaningful reality. +* [Website](https://pnproxy.org/welcome.html) ([GitHub](https://github.com/Pineapple-Proxy-DAO/web)) + diff --git a/documentation/dev-portal/src/community-resources/community-applications.md b/documentation/dev-portal/src/community-resources/community-applications.md deleted file mode 100644 index 9eada01337..0000000000 --- a/documentation/dev-portal/src/community-resources/community-applications.md +++ /dev/null @@ -1,61 +0,0 @@ -# Community Applications - -We love seeing our developer community create applications using Nym. If you would like to share your application with the community, please submit a pull request to the `main` branch of the `nymtech/dev-portal` [repository](https://github.com/nymtech/dev-portal). - - - -## Pastenym - ->A [pastebin](https://pastebin.com) inspired project, offer a solution for sharing text with Nym products to offer full anonymity, even on metadata level. - -* [Github](https://github.com/notrustverify/pastenym) -* [Deployed Website App](https://pastenym.ch) - - - - - -## Nostr-Nym - -> [Nostr-nym](https://github.com/notrustverify/nostr-nym) offer a solution to use [Nostr](https://nostr.how/) protocol by giving the possibility to run a relay on mixnet. By using a nostr client compatible with the mixnet, users can protect their privacy to be able to use Nostr has the want, without being observed. - -* [Github](https://github.com/notrustverify/nostr-nym) -* Deployed Website App coming soon - - - - - - -## Spook - -> Ethereum RPC request mixer uses the Nym network mixing service to anonymize RPC requests to the Ethereum network without revealing sensitive data and metadata. -* [Github](https://github.com/EdenBlockVC/spook) - - - - - -## Ethereum Transaction Broadcaster - -> Ethereum Transaction Broadcaster that uses the Nym Mixnet to provide privacy and anonymity for transactions on the Ethereum network command-line interface. - -* [Github](https://github.com/noot/nym-ethtx) - - - - - -## NymDrive - -> An open-source, decentralized, E2E encrypted, privacy friendly alternative to Google Drive/Dropbox, allowing for file encryption and decryption using the Nym Mixnet. -* [Github](https://github.com/saleel/nymdrive) -* [Demo](https://www.youtube.com/watch?v=5Rx73nw8NYI) -* [Presentation](https://docs.google.com/presentation/d/1MpvIK32Mx9VKLVfMTcvbeyrsKHHUsTvDQ-3n31dR0NE/edit#slide=id.p) - - - - - - - diff --git a/documentation/dev-portal/src/community-resources/community-guides.md b/documentation/dev-portal/src/community-resources/community-guides.md deleted file mode 100644 index 7a5973f6f2..0000000000 --- a/documentation/dev-portal/src/community-resources/community-guides.md +++ /dev/null @@ -1,17 +0,0 @@ -# Community Guides - -We aren't the only ones writing documentation: the Nym developer community is also a great source of guides and resources, some of which we've included here. - -## No Trust Verify - ->No Trust Verify is a project that aims to build open-source, privacy-enhancing technologies that make it easier to use the Internet securely and anonymously. Their focus is on providing tools and services that make it simple for developers to create decentralized applications (dApps) that respect users' privacy. - -* [Awesome Nym list](https://notrustverify.github.io/awesome-nym/) ([GitHub](https://github.com/notrustverify/awesome-nym)) -* A lot of guides can be found on the [NTV Blog](https://blog.notrustverify.ch/) - -## The Way of the NYMJA - -by Pineapple Proxy🍍 - ->Born out of a study group from Nym's Shipyard Academy, Pineapple Proxy has emerged as a cluster of motivated and skilled individuals who see the new internet taking shape. With vibecare at the heart of their approach, this zesty collective is on a mission to make privacy convenient for everyone via content, new tools, events, and novel experiences. They believe in collective intelligence, empathy, and collaboration as the means by which privacy will become a meaningful reality. -* [Website](https://pnproxy.org/welcome.html) ([GitHub](https://github.com/Pineapple-Proxy-DAO/web)) diff --git a/documentation/dev-portal/src/images/profile_picture/darkfi_over_nym_pp.png b/documentation/dev-portal/src/images/profile_picture/darkfi_over_nym_pp.png new file mode 100644 index 0000000000..9ae44d85c1 Binary files /dev/null and b/documentation/dev-portal/src/images/profile_picture/darkfi_over_nym_pp.png differ diff --git a/documentation/dev-portal/src/images/profile_picture/minibolt_pp.png b/documentation/dev-portal/src/images/profile_picture/minibolt_pp.png new file mode 100644 index 0000000000..ed059e6055 Binary files /dev/null and b/documentation/dev-portal/src/images/profile_picture/minibolt_pp.png differ diff --git a/documentation/dev-portal/src/images/profile_picture/nym_dashboard_pp.svg b/documentation/dev-portal/src/images/profile_picture/nym_dashboard_pp.svg new file mode 100644 index 0000000000..e91f3abdb4 --- /dev/null +++ b/documentation/dev-portal/src/images/profile_picture/nym_dashboard_pp.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + diff --git a/documentation/dev-portal/src/images/profile_picture/nymstr_email_pp.png b/documentation/dev-portal/src/images/profile_picture/nymstr_email_pp.png new file mode 100644 index 0000000000..404b9e2529 Binary files /dev/null and b/documentation/dev-portal/src/images/profile_picture/nymstr_email_pp.png differ diff --git a/documentation/dev-portal/src/integrations/mixnet-integration.md b/documentation/dev-portal/src/integrations/mixnet-integration.md index 43fea3e7e9..e4a328bf24 100644 --- a/documentation/dev-portal/src/integrations/mixnet-integration.md +++ b/documentation/dev-portal/src/integrations/mixnet-integration.md @@ -36,7 +36,7 @@ In order to ensure uptime and reliability, it is recommended that you run some p * If you're running a purely P2P application, then just integrating clients and having some method of sharing addresses should be enough to route your traffic through the mixnet. * If you're wanting to place the mixnet between your users' application instances and a server-based backend, you can use the [network requester](https://nymtech.net/docs/nodes/network-requester-setup.html) service provider binary to proxy these requests to your application backend, with the mixnet 'between' the user and your service, in order to prevent metadata leakage being broadcast to the internet. -* If you're wanting to route RPC requests through the mixnet to a blockchain, you will need to look into setting up some sort of service that does the transaction broadcasting for you. You can find examples of such projects on the [community applications](../community-resources/community-applications.md) page. +* If you're wanting to route RPC requests through the mixnet to a blockchain, you will need to look into setting up some sort of service that does the transaction broadcasting for you. You can find examples of such projects on the [community applications](../community-resources/community-applications-and-guides.md) page. ## Example application traffic flow ### Initialization diff --git a/documentation/dev-portal/src/quickstart/overview.md b/documentation/dev-portal/src/quickstart/overview.md index a9586172ed..b2ac4199c9 100644 --- a/documentation/dev-portal/src/quickstart/overview.md +++ b/documentation/dev-portal/src/quickstart/overview.md @@ -2,9 +2,10 @@ There are multiple options to quickly connect to Nym and see the network in action without the need for any code changes to your application. At most, these involve running Nym as a second process alongside an existing application in order to send traffic through the mixnet. -Demo application: +Demo applications: * a browser-based 'hello world' [chat application](https://chat-demo.nymtech.net). Either open in two browser windows and send messages to yourself, or share with a friend and send messages to each other through the mixnet! - +* a Coconut-scheme based [Credential Library](https://coco-demo.nymtech.net/). This is a WASM implementation of our Coconut libraries which generate raw Coconut credentials. Test it to create and re-randomize your own credentials! +
Proxy traffic with the Nym Socks5 client: * set up a plug-and-play connection with the [NymConnect](./nymconnect-gui.md) GUI for proxying Telegram, Electrum, Keybase or Blockstream Green traffic through the mixnet (~2 minutes). * [Download and run](./socks-proxy.md) the Nym Socks5 client via the CLI, for other desktop applications with SOCKS5 connection options (~30 minutes). diff --git a/documentation/dev-portal/src/tutorials/simple-service-provider/sending-message.md b/documentation/dev-portal/src/tutorials/simple-service-provider/sending-message.md index a1a5f67ae9..d0083f0a01 100644 --- a/documentation/dev-portal/src/tutorials/simple-service-provider/sending-message.md +++ b/documentation/dev-portal/src/tutorials/simple-service-provider/sending-message.md @@ -12,7 +12,7 @@ Simply fill in the fields in your browser and click `Send`. In your browser you -This small project can be used as a template to start conceptualizing and developing more complex PEApps. Stay tuned for more soon, and if you're searching for inspiration check out the [community apps](../../community-resources/community-applications.md) list! +This small project can be used as a template to start conceptualizing and developing more complex PEApps. Stay tuned for more soon, and if you're searching for inspiration check out the [community apps](../../community-resources/community-applications-and-guides.md) list! diff --git a/documentation/dev-portal/theme/css/general.css b/documentation/dev-portal/theme/css/general.css index a06db43965..84dbfa3371 100644 --- a/documentation/dev-portal/theme/css/general.css +++ b/documentation/dev-portal/theme/css/general.css @@ -62,7 +62,8 @@ h3:target::before, h4:target::before, h5:target::before, h6:target::before { - display: inline-block; + /* display "none" in order to avoid default artifacts on the community-applications-and-guides page */ + display: none; content: "»"; margin-left: -30px; width: 30px; diff --git a/documentation/docs/src/sdk/rust.md b/documentation/docs/src/sdk/rust.md index 1408d85faa..9e60d40efd 100644 --- a/documentation/docs/src/sdk/rust.md +++ b/documentation/docs/src/sdk/rust.md @@ -50,7 +50,7 @@ As seen in the example above, the `mixnet::MixnetClientBuilder::new()` function If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform the actions taken automatically above (`examples/manually_handle_keys_and_config.rs`) ```rust,noplayground -{{#include ../../../../sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs}} +{{#include ../../../../sdk/rust/nym-sdk/examples/manually_handle_storage.rs}} ``` ### Anonymous replies with SURBs @@ -102,4 +102,4 @@ The following code shows how you can use the SDK to create and use a [credential {{#include ../../../../sdk/rust/nym-sdk/examples/bandwidth.rs}} ``` -You can read more about Coconut credentials (also referred to as `zk-Nym`) [here](../cococnut.md). +You can read more about Coconut credentials (also referred to as `zk-Nym`) [here](../coconut.md). diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 8430e0b7e5..15775ec757 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -22,7 +22,6 @@ pub(crate) mod init; pub(crate) mod node_details; pub(crate) mod run; pub(crate) mod sign; -pub(crate) mod upgrade; #[derive(Subcommand)] pub(crate) enum Commands { @@ -38,9 +37,6 @@ pub(crate) enum Commands { /// Sign text to prove ownership of this mixnode Sign(sign::Sign), - /// Try to upgrade the gateway - Upgrade(upgrade::Upgrade), - /// Generate shell completions Completions(ArgShell), @@ -71,7 +67,6 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box node_details::execute(m).await?, Commands::Run(m) => run::execute(m).await?, Commands::Sign(m) => sign::execute(m)?, - Commands::Upgrade(m) => upgrade::execute(&m).await, Commands::Completions(s) => s.generate(&mut crate::Cli::command(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::command(), bin_name), } diff --git a/gateway/src/commands/upgrade.rs b/gateway/src/commands/upgrade.rs deleted file mode 100644 index c726971b58..0000000000 --- a/gateway/src/commands/upgrade.rs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::config::Config; -use clap::Args; -use nym_bin_common::version_checker::Version; -use std::fmt::Display; -use std::process; - -#[derive(Args, Clone)] -pub struct Upgrade { - /// Id of the nym-gateway we want to upgrade - #[clap(long)] - id: String, -} - -#[allow(dead_code)] -fn fail_upgrade(from_version: D1, to_version: D2) -> ! { - print_failed_upgrade(from_version, to_version); - process::exit(1) -} - -fn print_start_upgrade(from: D1, to: D2) { - eprintln!( - "\n==================\nTrying to upgrade gateway from {} to {} ...", - from, to - ); -} - -fn print_failed_upgrade(from: D1, to: D2) { - eprintln!( - "Upgrade from {} to {} failed!\n==================\n", - from, to - ); -} - -fn print_successful_upgrade(from: D1, to: D2) { - eprintln!( - "Upgrade from {} to {} was successful!\n==================\n", - from, to - ); -} - -fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! { - eprintln!( - "Cannot perform upgrade from {} to {}. Your version is too old to perform the upgrade.!", - config_version, package_version - ); - process::exit(1) -} - -fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! { - eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, current_version); - process::exit(1) -} - -fn parse_config_version(config: &Config) -> Version { - let version = Version::parse(&config.gateway.version).unwrap_or_else(|err| { - eprintln!("failed to parse client version! - {err}"); - process::exit(1) - }); - - if version.is_prerelease() || !version.build.is_empty() { - eprintln!( - "Trying to upgrade from a non-released version {}. This is not supported!", - version - ); - process::exit(1) - } - - version -} - -fn parse_package_version() -> Version { - let version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); - - // technically this is not a correct way of checking it as a released version might contain valid build identifiers - // however, we are not using them ourselves at the moment and hence it should be fine. - // if we change our mind, we could easily tweak this code - if version.is_prerelease() || !version.build.is_empty() { - eprintln!( - "Trying to upgrade to a non-released version {}. This is not supported!", - version - ); - process::exit(1) - } - - version -} - -fn minor_0_12_upgrade( - config: Config, - _args: &Upgrade, - config_version: &Version, - package_version: &Version, -) -> Config { - let to_version = if package_version.major == 0 && package_version.minor == 12 { - package_version.clone() - } else { - Version::new(0, 12, 0) - }; - - print_start_upgrade(config_version, &to_version); - - let upgraded_config = config.with_custom_version(to_version.to_string()); - - upgraded_config - .save_to_default_location() - .unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {err}"); - print_failed_upgrade(config_version, &to_version); - process::exit(1); - }); - - print_successful_upgrade(config_version, to_version); - - upgraded_config -} - -fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) { - loop { - let config_version = parse_config_version(&config); - - if config_version == package_version { - eprintln!("You're using the most recent version!"); - return; - } - - config = match config_version.major { - 0 => match config_version.minor { - 9 | 10 => outdated_upgrade(&config_version, &package_version), - 11 => minor_0_12_upgrade(config, args, &config_version, &package_version), - _ => unsupported_upgrade(&config_version, &package_version), - }, - _ => unsupported_upgrade(&config_version, &package_version), - } - } -} - -pub async fn execute(args: &Upgrade) { - let package_version = parse_package_version(); - - let existing_config = Config::read_from_default_path(&args.id).unwrap_or_else(|err| { - eprintln!("failed to load existing config file! - {err}"); - process::exit(1) - }); - - if existing_config.gateway.version.is_empty() { - eprintln!("the existing configuration file does not seem to contain version number."); - process::exit(1); - } - - do_upgrade(existing_config, args, package_version) -} diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index 16cf17aa2b..d3c7cd040d 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -159,11 +159,6 @@ impl Config { self } - pub fn with_custom_version>(mut self, version: S) -> Self { - self.gateway.version = version.into(); - self - } - pub fn get_statistics_service_url(&self) -> Url { self.gateway.statistics_service_url.clone() } diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index 6582fe2c62..b8c8f84894 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -20,7 +20,6 @@ mod init; mod node_details; mod run; mod sign; -mod upgrade; #[derive(Subcommand)] pub(crate) enum Commands { @@ -36,9 +35,6 @@ pub(crate) enum Commands { /// Sign text to prove ownership of this mixnode Sign(sign::Sign), - /// Try to upgrade the mixnode - Upgrade(upgrade::Upgrade), - /// Show details of this mixnode NodeDetails(node_details::NodeDetails), @@ -67,7 +63,6 @@ pub(crate) async fn execute(args: Cli) -> anyhow::Result<()> { Commands::Init(m) => init::execute(&m), Commands::Run(m) => run::execute(&m).await?, Commands::Sign(m) => sign::execute(&m)?, - Commands::Upgrade(m) => upgrade::execute(&m)?, Commands::NodeDetails(m) => node_details::execute(&m)?, Commands::Completions(s) => s.generate(&mut crate::Cli::command(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::command(), bin_name), diff --git a/mixnode/src/commands/upgrade.rs b/mixnode/src/commands/upgrade.rs deleted file mode 100644 index 5c8a95d313..0000000000 --- a/mixnode/src/commands/upgrade.rs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::commands::try_upgrade_v1_1_21_config; -use crate::config::Config; -use clap::Args; -use nym_bin_common::version_checker::Version; -use std::fmt::Display; -use std::process; - -#[derive(Args)] -pub(crate) struct Upgrade { - /// Id of the nym-mixnode we want to upgrade - #[clap(long)] - id: String, -} - -#[allow(dead_code)] -fn fail_upgrade(from_version: D1, to_version: D2) -> ! { - print_failed_upgrade(from_version, to_version); - process::exit(1) -} - -fn print_start_upgrade(from: D1, to: D2) { - println!("\n==================\nTrying to upgrade mixnode from {from} to {to} ..."); -} - -fn print_failed_upgrade(from: D1, to: D2) { - eprintln!("Upgrade from {from} to {to} failed!\n==================\n"); -} - -fn print_successful_upgrade(from: D1, to: D2) { - println!("Upgrade from {from} to {to} was successful!\n==================\n"); -} - -fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! { - eprintln!( - "Cannot perform upgrade from {config_version} to {package_version}. Your version is too old to perform the upgrade.!" - ); - process::exit(1) -} - -fn unsupported_upgrade(config_version: &Version, package_version: &Version) -> ! { - eprintln!("Cannot perform upgrade from {config_version} to {package_version}. Please let the developers know about this issue if you expected it to work!"); - process::exit(1) -} - -fn parse_config_version(config: &Config) -> Version { - let version = Version::parse(&config.mixnode.version).unwrap_or_else(|err| { - eprintln!("failed to parse client version! - {err}"); - process::exit(1) - }); - - if version.is_prerelease() || !version.build.is_empty() { - eprintln!( - "Trying to upgrade from a non-released version {version}. This is not supported!" - ); - process::exit(1) - } - - version -} - -fn parse_package_version() -> Version { - let version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); - - // technically this is not a correct way of checking it as a released version might contain valid build identifiers - // however, we are not using them ourselves at the moment and hence it should be fine. - // if we change our mind, we could easily tweak this code - if version.is_prerelease() || !version.build.is_empty() { - eprintln!("Trying to upgrade to a non-released version {version}. This is not supported!"); - process::exit(1) - } - - version -} - -fn minor_0_12_upgrade( - config: Config, - _args: &Upgrade, - config_version: &Version, - package_version: &Version, -) -> Config { - let to_version = if package_version.major == 0 && package_version.minor == 12 { - package_version.clone() - } else { - Version::new(0, 12, 0) - }; - - print_start_upgrade(config_version, &to_version); - - let upgraded_config = config.with_custom_version(to_version.to_string()); - - upgraded_config - .save_to_default_location() - .unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {err}"); - print_failed_upgrade(config_version, &to_version); - process::exit(1); - }); - - print_successful_upgrade(config_version, to_version); - - upgraded_config -} - -fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) { - loop { - let config_version = parse_config_version(&config); - - if config_version == package_version { - println!("You're using the most recent version!"); - return; - } - - config = match config_version.major { - 0 => match config_version.minor { - 9 | 10 => outdated_upgrade(&config_version, &package_version), - 11 => minor_0_12_upgrade(config, args, &config_version, &package_version), - _ => unsupported_upgrade(&config_version, &package_version), - }, - _ => unsupported_upgrade(&config_version, &package_version), - } - } -} - -pub(crate) fn execute(args: &Upgrade) -> anyhow::Result<()> { - try_upgrade_v1_1_21_config(&args.id)?; - - let package_version = parse_package_version(); - - let existing_config = Config::read_from_default_path(&args.id).unwrap_or_else(|err| { - eprintln!("failed to load existing config file! - {err}"); - process::exit(1) - }); - - if existing_config.mixnode.version.is_empty() { - eprintln!("the existing configuration file does not seem to contain version number."); - process::exit(1); - } - - do_upgrade(existing_config, args, package_version); - Ok(()) -} diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index 02ce8d875a..ec4077deab 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -147,11 +147,6 @@ impl Config { self } - pub fn with_custom_version>(mut self, version: S) -> Self { - self.mixnode.version = version.into(); - self - } - pub fn get_nym_api_endpoints(&self) -> Vec { self.mixnode.nym_api_urls.clone() } diff --git a/nym-api/src/nym_contract_cache/cache/data.rs b/nym-api/src/nym_contract_cache/cache/data.rs index 235afdb630..27285db41a 100644 --- a/nym-api/src/nym_contract_cache/cache/data.rs +++ b/nym-api/src/nym_contract_cache/cache/data.rs @@ -6,7 +6,7 @@ use nym_mixnet_contract_common::{ families::FamilyHead, GatewayBond, IdentityKey, Interval, MixId, MixNodeDetails, RewardingParams, }; -use nym_name_service_common::NameEntry; +use nym_name_service_common::RegisteredName; use nym_service_provider_directory_common::Service; use std::collections::HashSet; @@ -26,7 +26,7 @@ pub(crate) struct ValidatorCacheData { pub(crate) mix_to_family: Cache>, pub(crate) service_providers: Cache>, - pub(crate) registered_names: Cache>, + pub(crate) registered_names: Cache>, } impl ValidatorCacheData { diff --git a/nym-api/src/nym_contract_cache/cache/mod.rs b/nym-api/src/nym_contract_cache/cache/mod.rs index 13e2783e47..2eac4f0c61 100644 --- a/nym-api/src/nym_contract_cache/cache/mod.rs +++ b/nym-api/src/nym_contract_cache/cache/mod.rs @@ -5,7 +5,7 @@ use nym_mixnet_contract_common::{ families::FamilyHead, GatewayBond, IdentityKey, Interval, MixId, MixNodeBond, MixNodeDetails, RewardingParams, }; -use nym_name_service_common::NameEntry; +use nym_name_service_common::RegisteredName; use nym_service_provider_directory_common::Service; use rocket::fairing::AdHoc; use std::{ @@ -53,7 +53,7 @@ impl NymContractCache { current_interval: Interval, mix_to_family: Vec<(IdentityKey, FamilyHead)>, services: Option>, - names: Option>, + names: Option>, ) { match time::timeout(Duration::from_millis(100), self.inner.write()).await { Ok(mut cache) => { @@ -303,7 +303,7 @@ impl NymContractCache { } } - pub(crate) async fn names(&self) -> Cache> { + pub(crate) async fn names(&self) -> Cache> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.registered_names.clone(), Err(err) => { diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 2ac9e0852a..0cb8b27caf 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3564,8 +3564,12 @@ name = "nym-name-service-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-contracts-common", "schemars", "serde", + "thiserror", ] [[package]] diff --git a/nym-connect/desktop/src-tauri/src/tasks.rs b/nym-connect/desktop/src-tauri/src/tasks.rs index 5e6f8b6861..d6ac1b0918 100644 --- a/nym-connect/desktop/src-tauri/src/tasks.rs +++ b/nym-connect/desktop/src-tauri/src/tasks.rs @@ -30,19 +30,7 @@ pub enum Socks5ExitStatusMessage { Failed(Box), } -/// The main SOCKS5 client task. It loads the configuration from file determined by the `id`. -pub async fn start_nym_socks5_client( - id: &str, -) -> Result<( - Socks5ControlMessageSender, - nym_task::StatusReceiver, - ExitStatusReceiver, - GatewayEndpointConfig, -)> { - log::info!("Loading config from file: {id}"); - let mut config = Config::read_from_default_path(id) - .tap_err(|_| log::warn!("Failed to load configuration file"))?; - +fn override_config_from_env(config: &mut Config) { // Disable both the loop cover traffic that runs in the background as well as the Poisson // process that injects cover traffic into the traffic stream. if std::env::var("NYM_CONNECT_DISABLE_COVER").is_ok() { @@ -62,6 +50,22 @@ pub async fn start_nym_socks5_client( log::warn!("Disabling per-hop delay"); config.core.base.set_no_per_hop_delays(); } +} + +/// The main SOCKS5 client task. It loads the configuration from file determined by the `id`. +pub async fn start_nym_socks5_client( + id: &str, +) -> Result<( + Socks5ControlMessageSender, + nym_task::StatusReceiver, + ExitStatusReceiver, + GatewayEndpointConfig, +)> { + log::info!("Loading config from file: {id}"); + let mut config = Config::read_from_default_path(id) + .tap_err(|_| log::warn!("Failed to load configuration file"))?; + + override_config_from_env(&mut config); log::trace!("Configuration used: {:#?}", config); diff --git a/nym-wallet/CHANGELOG.md b/nym-wallet/CHANGELOG.md index 82a2dd9da7..f0d62aec2d 100644 --- a/nym-wallet/CHANGELOG.md +++ b/nym-wallet/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +## [v1.2.6] (2023-07-18) + +- [wallet] bugfix: don't send funds for pledge decrease simulation ([#3676]) + +[#3676]: https://github.com/nymtech/nym/pull/3676 + ## [v1.2.5] (2023-07-04) - Wallet - add "Test my node" in the Node Settings and show its results ([#2314]) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 70502fc84a..fa2722e281 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -3086,8 +3086,12 @@ name = "nym-name-service-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-contracts-common", "schemars", "serde", + "thiserror", ] [[package]] diff --git a/nym-wallet/package.json b/nym-wallet/package.json index f14d6dd227..e93b887fcf 100644 --- a/nym-wallet/package.json +++ b/nym-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/nym-wallet-app", - "version": "1.2.5", + "version": "1.2.6", "main": "index.js", "license": "MIT", "scripts": { @@ -123,4 +123,4 @@ "webpack-favicons": "^1.3.8", "webpack-merge": "^5.8.0" } -} +} \ No newline at end of file diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 06757de614..a04ffabe0d 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym_wallet" -version = "1.2.5" +version = "1.2.6" description = "Nym Native Wallet" authors = ["Nym Technologies SA"] license = "" diff --git a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs index 0f12ff80c4..332f0eeb43 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs @@ -124,11 +124,9 @@ pub async fn simulate_update_pledge( ); simulate_mixnet_operation( ExecuteMsg::DecreasePledge { - decrease_by: guard - .attempt_convert_to_base_coin(dec_delta.clone())? - .into(), + decrease_by: guard.attempt_convert_to_base_coin(dec_delta)?.into(), }, - Some(dec_delta), + None, &state, ) .await diff --git a/nym-wallet/src-tauri/tauri.conf.json b/nym-wallet/src-tauri/tauri.conf.json index 45ba45f846..2986866ccb 100644 --- a/nym-wallet/src-tauri/tauri.conf.json +++ b/nym-wallet/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-wallet", - "version": "1.2.5" + "version": "1.2.6" }, "build": { "distDir": "../dist", @@ -14,7 +14,13 @@ "active": true, "targets": "all", "identifier": "net.nymtech.wallet", - "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], "resources": [], "externalBin": [], "copyright": "Copyright © 2021-2023 Nym Technologies SA", @@ -39,7 +45,9 @@ }, "updater": { "active": true, - "endpoints": ["https://nymtech.net/.wellknown/wallet/updater.json"], + "endpoints": [ + "https://nymtech.net/.wellknown/wallet/updater.json" + ], "dialog": true, "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IENCNzQ2M0E5N0VFODE2NApSV1JrZ2U2WE9rYTNETTg1OTBKdE5uWUEra0hML2syOVUvQ2lxZmFZRzZ1T3NWbGM0eVRzUTVhVwo=" }, @@ -67,4 +75,4 @@ "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" } } -} +} \ No newline at end of file diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 8226b7425a..5b80088b30 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -23,6 +23,7 @@ log = { workspace = true } pretty_env_logger = "0.4.0" publicsuffix = "1.5" # Can't update this until bip updates to support newer idna version rand = "0.7.3" +regex = "1.8.4" reqwest = { version = "0.11.11", features = ["json"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/service-providers/network-requester/src/allowed_hosts/hosts.rs b/service-providers/network-requester/src/allowed_hosts/hosts.rs index b68159d59e..5c48369d0d 100644 --- a/service-providers/network-requester/src/allowed_hosts/hosts.rs +++ b/service-providers/network-requester/src/allowed_hosts/hosts.rs @@ -128,10 +128,29 @@ impl HostsStore { log::trace!("Loading from storefile: {}", filename.as_ref().display()); let file = File::open(filename)?; let reader = BufReader::new(&file); - Ok(reader + let hosts = reader .lines() - .map(|line| Host::from(line.expect("failed to read input file line!"))) - .collect()) + .filter_map(|line| { + let line = line.expect("failed to read input file line!"); + trim_comment(&line) + }) + .map(Host::from) + .collect(); + dbg!(&hosts); + Ok(hosts) + } +} + +fn trim_comment(line: &str) -> Option { + if let Some(content) = line.split('#').next() { + let trim_content = content.trim().to_string(); + if trim_content.is_empty() { + None + } else { + Some(trim_content) + } + } else { + None } } @@ -145,4 +164,42 @@ mod constructor_tests { let store = HostsStore::new(temp_file); assert_eq!(store.data.domains.len(), 0); } + + #[test] + fn trim_comments() { + let entries = vec![ + "# keybase", + "keybaseapi.com", + "", + "gist.githubusercontent.com", + " ", + "# healthcheck # foo", + "nymtech.net", + "# blockstream green wallet", + "blockstream.info", + "greenaddress.it", + "91.108.56.0/22", + "2001:b28:f23d::/48", + "2001:67c:4e8::/48", + "# nym matrix server", + "# monero desktop - mainnet", + ]; + let filtered_entries: Vec<_> = entries + .iter() + .filter_map(|line| trim_comment(line)) + .collect(); + assert_eq!( + filtered_entries, + vec![ + "keybaseapi.com", + "gist.githubusercontent.com", + "nymtech.net", + "blockstream.info", + "greenaddress.it", + "91.108.56.0/22", + "2001:b28:f23d::/48", + "2001:67c:4e8::/48", + ] + ); + } } diff --git a/service-providers/network-requester/src/allowed_hosts/standard_list.rs b/service-providers/network-requester/src/allowed_hosts/standard_list.rs index 66f529f57f..3b873827e3 100644 --- a/service-providers/network-requester/src/allowed_hosts/standard_list.rs +++ b/service-providers/network-requester/src/allowed_hosts/standard_list.rs @@ -4,6 +4,7 @@ use crate::allowed_hosts::group::HostsGroup; use crate::allowed_hosts::host::Host; use nym_task::TaskClient; +use regex::Regex; use std::sync::Arc; use std::time::Duration; use tokio::sync::{RwLock, RwLockReadGuard}; @@ -11,11 +12,20 @@ use tokio::sync::{RwLock, RwLockReadGuard}; const STANDARD_LIST_URL: &str = "https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt"; +fn remove_comments(text: String) -> String { + if let Ok(regex) = Regex::new(r"#.*\n") { + regex.replace_all(&text, "").into_owned() + } else { + log::warn!("Failed to strip comments from standard allowed list"); + text + } +} + /// Fetch the standard allowed list from nymtech.net pub(crate) async fn fetch() -> Vec { log::info!("Refreshing standard allowed hosts"); - get_standard_allowed_list() - .await + let text = get_standard_allowed_list().await; + remove_comments(text) .split_whitespace() .map(Into::into) .collect()