diff --git a/.github/workflows/basic.yaml b/.github/workflows/basic.yaml index ccf2a2086..d0df1cd1d 100644 --- a/.github/workflows/basic.yaml +++ b/.github/workflows/basic.yaml @@ -29,9 +29,14 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Cache build artifacts - uses: useblacksmith/rust-cache@v3 + uses: useblacksmith/rust-cache@v3.0.1 + id: cache with: - shared-key: "cache" + shared-key: "cache-tests" + + - name: Log crates.toml + if: steps.cache.outputs.cache-hit == 'true' + run: cat /home/runner/.cargo/.crates.toml - name: Run tests uses: actions-rs/cargo@v1 @@ -54,12 +59,27 @@ jobs: profile: minimal toolchain: 1.78.0 target: wasm32-unknown-unknown + default: true override: true + - name: Install cosmwasm-check compatible toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.75.0 + target: wasm32-unknown-unknown + default: false + override: false + - name: Cache build artifacts - uses: useblacksmith/rust-cache@v3 + id: cache + uses: useblacksmith/rust-cache@v3.0.1 with: - shared-key: "cache" + shared-key: "cache-cosmwasm-compilation" + + - name: Log crates.toml + if: steps.cache.outputs.cache-hit == 'true' + run: cat /home/runner/.cargo/.crates.toml - name: Build wasm release run: | @@ -69,17 +89,24 @@ jobs: (cd $C && cargo build --release --lib --target wasm32-unknown-unknown --locked) done + - name: Build ITS release + working-directory: ./interchain-token-service + run: cargo build --release --target wasm32-unknown-unknown --locked + + # cosmwasm-check v1.3.x is used to check for compatibility with wasmvm v1.3.x used by Axelar + # Older rust toolchain is required to install cosmwasm-check v1.3.x - name: Install cosmwasm-check uses: actions-rs/cargo@v1 with: command: install - args: --version 1.5.5 --locked cosmwasm-check + toolchain: 1.75.0 + args: --version 1.3.4 --locked cosmwasm-check - name: Check wasm contracts run: cosmwasm-check ./target/wasm32-unknown-unknown/release/*.wasm - lints: - name: Lints + ampd-compilation: + name: Ampd Release Compilation runs-on: blacksmith-16vcpu-ubuntu-2204 steps: - uses: actions/checkout@v4 @@ -89,8 +116,41 @@ jobs: with: profile: minimal toolchain: 1.78.0 + target: wasm32-unknown-unknown + override: true + + - name: Install protoc + uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache build artifacts + id: cache + uses: useblacksmith/rust-cache@v3.0.1 + with: + shared-key: "cache-ampd-compilation" + + - name: Log crates.toml + if: steps.cache.outputs.cache-hit == 'true' + run: cat /home/runner/.cargo/.crates.toml + + - name: Build ampd + working-directory: ./ampd + run: cargo build --release --locked + + lints: + name: Lints + runs-on: blacksmith-16vcpu-ubuntu-2204 + steps: + - uses: actions/checkout@v4 + + - name: Install nightly toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + components: rustfmt override: true - components: rustfmt, clippy - name: Install protoc uses: arduino/setup-protoc@v2 @@ -98,9 +158,14 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Cache build artifacts - uses: useblacksmith/rust-cache@v3 + uses: useblacksmith/rust-cache@v3.0.1 + id: cache with: - shared-key: "cache" + shared-key: "cache-lints" + + - name: Log crates.toml + if: steps.cache.outputs.cache-hit == 'true' + run: cat /home/runner/.cargo/.crates.toml - name: Install cargo-sort uses: baptiste0928/cargo-install@v2 @@ -113,6 +178,14 @@ jobs: command: fmt args: --all -- --check + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.78.0 + components: clippy + override: true + - name: Run cargo sort uses: actions-rs/cargo@v1 with: diff --git a/.github/workflows/build-ampd-and-push-to-r2.yaml b/.github/workflows/build-ampd-and-push-to-r2.yaml deleted file mode 100644 index 985b64414..000000000 --- a/.github/workflows/build-ampd-and-push-to-r2.yaml +++ /dev/null @@ -1,65 +0,0 @@ -name: Upload ampd contracts to Cloudflare R2 bucket - -on: - push: - branches: - - main - workflow_dispatch: - inputs: - branch: - description: Github branch to checkout for compilation - required: true - default: main - type: string - - -jobs: - compile-and-upload: - name: Compile contracts and push to R2 - runs-on: ubuntu-22.04 - permissions: - contents: write - packages: write - id-token: write - steps: - - name: Determine branch - id: get-branch-name - run: | - if [ "${{ github.event_name }}" == "push" ]; then - branch="main" - else - branch="${{ inputs.branch }}" - fi - echo "branch=$branch" >> $GITHUB_OUTPUT - - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: "0" - path: axelar-amplifier - submodules: recursive - ref: ${{ steps.get-branch-name.outputs.branch }} - - - name: Compile amplifier contracts - id: compile-contracts - run: | - cd axelar-amplifier - docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ - --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/optimizer:0.16.0 - - commit_hash=$(git rev-parse --short HEAD) - cd .. - mkdir -p ./artifacts/$commit_hash/ - cp -R axelar-amplifier/artifacts/* ./artifacts/$commit_hash/ - echo "wasm-directory=./artifacts" >> $GITHUB_OUTPUT - - - uses: ryand56/r2-upload-action@latest - with: - r2-account-id: ${{ secrets.R2_ACCOUNT_ID }} - r2-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_CF }} - r2-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_CF }} - r2-bucket: ${{ secrets.R2_BUCKET }} - source-dir: ${{ steps.compile-contracts.outputs.wasm-directory }} - destination-dir: ./pre-releases/ampd/contracts/ diff --git a/.github/workflows/build-ampd-main.yaml b/.github/workflows/build-ampd-main.yaml index 18bc7d1aa..f273aca28 100644 --- a/.github/workflows/build-ampd-main.yaml +++ b/.github/workflows/build-ampd-main.yaml @@ -1,4 +1,4 @@ -name: Amplifier - Build main branch +name: ampd (push to main) - Build and push image to ECR on: push: diff --git a/.github/workflows/build-ampd-release.yaml b/.github/workflows/build-ampd-release.yaml index 35be5321a..bd5000733 100644 --- a/.github/workflows/build-ampd-release.yaml +++ b/.github/workflows/build-ampd-release.yaml @@ -1,4 +1,4 @@ -name: Amplifier - Build Release +name: ampd - Build and release binary and image on: workflow_dispatch: diff --git a/.github/workflows/build-contracts-and-push-to-r2.yaml b/.github/workflows/build-contracts-and-push-to-r2.yaml new file mode 100644 index 000000000..09e890dfd --- /dev/null +++ b/.github/workflows/build-contracts-and-push-to-r2.yaml @@ -0,0 +1,164 @@ +name: Amplifier wasm contracts - Upload wasm binaries to Cloudflare R2 bucket + +on: + push: + branches: + - main + tags: + - '*-v[0-9]+.[0-9]+.[0-9]+' + workflow_dispatch: + inputs: + ref: + description: Github branch or tag to checkout for compilation + required: true + default: main + type: string + + +jobs: + compile-and-upload: + name: Compile contracts and push to R2 + runs-on: ubuntu-22.04 + permissions: + contents: write + packages: write + id-token: write + steps: + - name: Get tag + id: get-tag + run: | + echo "github_ref=$GITHUB_REF" + if [[ $GITHUB_REF == refs/tags/* ]]; then + echo "tag=${GITHUB_REF#refs/tags/}" + echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + input_ref="${{ github.event.inputs.ref }}" + if [[ $input_ref =~ ^([a-zA-Z-]+)-v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + echo "tag=$input_ref" + echo "tag=$input_ref" >> $GITHUB_OUTPUT + else + echo "tag=" + echo "tag=" >> $GITHUB_OUTPUT + fi + else + echo "tag=" + echo "tag=" >> $GITHUB_OUTPUT + fi + + + - name: Check for release information from tag + id: check-release + run: | + tag="${{ steps.get-tag.outputs.tag }}" + is_release="false" + + if [[ $tag =~ ^([a-zA-Z-]+)-v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + is_release="true" + crate_name="${BASH_REMATCH[1]}" + crate_version="${BASH_REMATCH[2]}" + + echo "Is release: $is_release" + echo "Crate Name: $crate_name" + echo "Crate Version: $crate_version" + + echo "is-release=$is_release" >> $GITHUB_OUTPUT + echo "crate-name=$crate_name" >> $GITHUB_OUTPUT + echo "crate-version=$crate_version" >> $GITHUB_OUTPUT + else + echo "Is release: $is_release" + echo "Not a release tag. Skipping crate name and version extraction." + echo "is-release=$is_release" >> $GITHUB_OUTPUT + fi + + + - name: Determine checkout ref + id: get-checkout-ref + run: | + if [[ $GITHUB_REF == refs/tags/* ]]; then + echo "ref=$GITHUB_REF" >> $GITHUB_OUTPUT + elif [ "${{ github.event_name }}" == "push" ]; then + echo "ref=main" >> $GITHUB_OUTPUT + else + echo "ref=${{ inputs.ref }}" >> $GITHUB_OUTPUT + fi + + + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: "0" + path: axelar-amplifier + submodules: recursive + ref: ${{ steps.get-checkout-ref.outputs.ref }} + + + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v4 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + + + - name: Compile all amplifier contracts + id: compile-contracts + run: | + cd axelar-amplifier + docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/optimizer:0.16.0 + + commit_hash=$(git rev-parse --short HEAD) + cd .. + mkdir -p ./$commit_hash/ + cp -R axelar-amplifier/artifacts/* ./$commit_hash/ + echo "wasm-directory=./$commit_hash" >> $GITHUB_OUTPUT + + + - name: Prepare and sign release artifacts + if: steps.check-release.outputs.is-release == 'true' + id: prepare-release + run: | + cd ${{ steps.compile-contracts.outputs.wasm-directory }} + crate_name="${{ steps.check-release.outputs.crate-name }}" + crate_version="${{ steps.check-release.outputs.crate-version }}" + wasm_file=$(find . -name "${crate_name//-/_}.wasm") + checksum_file=$(find . -name "checksums.txt") + + if [ -z "$wasm_file" ]; then + echo "Error: Could not find .wasm file for $crate_name" + exit 1 + fi + + mkdir -p "../${crate_version}" + cp "$wasm_file" "../${crate_version}/${crate_name}.wasm" + cp "$checksum_file" "../${crate_version}/" + + gpg --armor --detach-sign ../${crate_version}/${crate_name}.wasm + gpg --armor --detach-sign ../${crate_version}/checksums.txt + + echo "release-artifacts-dir=./${crate_version}" >> $GITHUB_OUTPUT + echo "r2-destination-dir=./releases/cosmwasm/${crate_name}" >> $GITHUB_OUTPUT + + + - uses: ryand56/r2-upload-action@latest + if: steps.check-release.outputs.is-release == 'true' + with: + r2-account-id: ${{ secrets.R2_ACCOUNT_ID }} + r2-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_CF }} + r2-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_CF }} + r2-bucket: ${{ secrets.R2_BUCKET }} + source-dir: ${{ steps.prepare-release.outputs.release-artifacts-dir }} + destination-dir: ${{ steps.prepare-release.outputs.r2-destination-dir }} + + + - uses: ryand56/r2-upload-action@latest + if: steps.check-release.outputs.is-release != 'true' + with: + r2-account-id: ${{ secrets.R2_ACCOUNT_ID }} + r2-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_CF }} + r2-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_CF }} + r2-bucket: ${{ secrets.R2_BUCKET }} + source-dir: ${{ steps.compile-contracts.outputs.wasm-directory }} + destination-dir: ./pre-releases/cosmwasm/ diff --git a/.github/workflows/codecov.yaml b/.github/workflows/codecov.yaml index 95e88d5c6..51a41df6d 100644 --- a/.github/workflows/codecov.yaml +++ b/.github/workflows/codecov.yaml @@ -31,9 +31,14 @@ jobs: run: sudo apt-get install libclang-dev - name: Cache build artifacts - uses: useblacksmith/rust-cache@v3 + uses: useblacksmith/rust-cache@v3.0.1 + id: cache with: - shared-key: "cache" + shared-key: "cache-codecov" + + - name: Log crates.toml + if: steps.cache.outputs.cache-hit == 'true' + run: cat /home/runner/.cargo/.crates.toml - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6b762fd79..941444712 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,4 +1,4 @@ -name: Release +name: Update and tag release version on: workflow_dispatch: @@ -37,16 +37,16 @@ jobs: run: | binary="${{ github.event.inputs.binary-to-release }}" declare -A binaries_data=( - ["ampd"]="ampd,major-ampd,minor-ampd," - ["router"]="router,/(major-router)|(major-contracts)|(major-connection-router)/,/(minor-router)|(minor-contracts)|(minor-connection-router)/,contracts/router packages" - ["gateway"]="gateway,/(major-gateway)|(major-contracts)/,/(minor-gateway)|(minor-contracts)/,contracts/gateway packages" - ["multisig"]="multisig,/(major-multisig)|(major-contracts)/,/(minor-multisig)|(minor-contracts)/,contracts/multisig packages" - ["multisig-prover"]="multisig-prover,/(major-multisig-prover)|(major-contracts)/,/(minor-multisig-prover)|(minor-contracts)/,contracts/multisig-prover packages" - ["nexus-gateway"]="nexus-gateway,/(major-nexus-gateway)|(major-contracts)/,/(minor-nexus-gateway)|(minor-contracts)/,contracts/nexus-gateway packages" - ["rewards"]="rewards,/(major-rewards)|(major-contracts)/,/(minor-rewards)|(minor-contracts)/,contracts/rewards packages" - ["service-registry"]="service-registry,/(major-service-registry)|(major-contracts)/,/(minor-service-registry)|(minor-contracts)/,contracts/service-registry packages" - ["voting-verifier"]="voting-verifier,/(major-voting-verifier)|(major-contracts)/,/(minor-voting-verifier)|(minor-contracts)/,contracts/voting-verifier packages" - ["coordinator"]="coordinator,/(major-coordinator)|(major-contracts)/,/(minor-coordinator)|(minor-contracts)/,contracts/coordinator packages" + ["ampd"]="ampd,/(major)|(major-ampd)/,/(minor)|(minor-ampd)/,ampd packages" + ["router"]="router,/(major)|(major-router)|(major-contracts)|(major-connection-router)/,/(minor)|(minor-router)|(minor-contracts)|(minor-connection-router)/,contracts/router packages" + ["gateway"]="gateway,/(major)|(major-gateway)|(major-contracts)/,/(minor)|(minor-gateway)|(minor-contracts)/,contracts/gateway packages" + ["multisig"]="multisig,/(major)|(major-multisig)|(major-contracts)/,/(minor)|(minor-multisig)|(minor-contracts)/,contracts/multisig packages" + ["multisig-prover"]="multisig-prover,/(major)|(major-multisig-prover)|(major-contracts)/,/(minor)|(minor-multisig-prover)|(minor-contracts)/,contracts/multisig-prover packages" + ["nexus-gateway"]="nexus-gateway,/(major)|(major-nexus-gateway)|(major-contracts)/,/(minor)|(minor-nexus-gateway)|(minor-contracts)/,contracts/nexus-gateway packages" + ["rewards"]="rewards,/(major)|(major-rewards)|(major-contracts)/,/(minor)|(minor-rewards)|(minor-contracts)/,contracts/rewards packages" + ["service-registry"]="service-registry,/(major)|(major-service-registry)|(major-contracts)/,/(minor)|(minor-service-registry)|(minor-contracts)/,contracts/service-registry packages" + ["voting-verifier"]="voting-verifier,/(major)|(major-voting-verifier)|(major-contracts)/,/(minor)|(minor-voting-verifier)|(minor-contracts)/,contracts/voting-verifier packages" + ["coordinator"]="coordinator,/(major)|(major-coordinator)|(major-contracts)/,/(minor)|(minor-coordinator)|(minor-contracts)/,contracts/coordinator packages" ) if [[ -n "${binaries_data[$binary]}" ]]; then diff --git a/Cargo.lock b/Cargo.lock index ae6e7099f..4e3f5cb3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,9 +126,101 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "alloy-primitives" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb3ead547f4532bc8af961649942f0b9c16ee9226e26caa3f38420651cc0bf4" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "getrandom", + "hex-literal", + "itoa", + "k256", + "keccak-asm", + "proptest", + "rand", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" +dependencies = [ + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b40397ddcdcc266f59f959770f601ce1280e699a91fc1862f29cef91707cd09" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error", + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.72", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck 0.5.0", + "indexmap 2.3.0", + "proc-macro-error", + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.72", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" +dependencies = [ + "const-hex", + "dunce", + "heck 0.5.0", + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.72", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-types" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91ca40fa20793ae9c3841b83e74569d1cc9af29a2f5237314fd3452d51e38c7" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + [[package]] name = "ampd" -version = "0.5.0" +version = "1.0.0" dependencies = [ "async-trait", "axelar-wasm-std", @@ -144,7 +236,6 @@ dependencies = [ "dirs", "ecdsa", "ed25519 2.2.3", - "ed25519-dalek", "elliptic-curve", "enum-display-derive", "error-stack", @@ -160,7 +251,7 @@ dependencies = [ "humantime-serde", "itertools 0.11.0", "k256", - "mockall", + "mockall 0.11.4", "move-core-types", "multisig", "num-traits", @@ -173,7 +264,7 @@ dependencies = [ "router-api", "serde", "serde_json", - "serde_with 3.8.2", + "serde_with 3.9.0", "service-registry", "sha3", "starknet-core", @@ -251,9 +342,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -266,33 +357,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -311,9 +402,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" dependencies = [ "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -323,8 +414,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" dependencies = [ "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -334,11 +425,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3a13b34da09176a8baba701233fdffbaa7c1b1192ce031a3da4e55ce1f1a56" dependencies = [ "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-relations", - "ark-serialize", + "ark-serialize 0.4.2", "ark-snark", - "ark-std", + "ark-std 0.4.0", "blake2", "derivative", "digest 0.10.7", @@ -351,10 +442,10 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ - "ark-ff", + "ark-ff 0.4.2", "ark-poly", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", "itertools 0.10.5", @@ -362,26 +453,54 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "digest 0.10.7", "itertools 0.10.5", "num-bigint 0.4.6", "num-traits", "paste", - "rustc_version", + "rustc_version 0.4.0", "zeroize", ] +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote 1.0.36", + "syn 1.0.109", +] + [[package]] name = "ark-ff-asm" version = "0.4.2" @@ -392,6 +511,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "quote 1.0.36", + "syn 1.0.109", +] + [[package]] name = "ark-ff-macros" version = "0.4.2" @@ -413,11 +544,11 @@ checksum = "20ceafa83848c3e390f1cbf124bc3193b3e639b3f02009e0e290809a501b95fc" dependencies = [ "ark-crypto-primitives", "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-poly", "ark-relations", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -426,9 +557,9 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", ] @@ -439,8 +570,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00796b6efc05a3f48225e59cb6a2cda78881e7c390872d5786aaf112f31fb4f0" dependencies = [ - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", "tracing", ] @@ -451,8 +582,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3975a01b0a6e3eae0f72ec7ca8598a6620fc72fa5981f6f5cca33b7cd788f633" dependencies = [ "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", ] [[package]] @@ -462,7 +603,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive", - "ark-std", + "ark-std 0.4.0", "digest 0.10.7", "num-bigint 0.4.6", ] @@ -484,10 +625,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84d3cc6833a335bb8a600241889ead68ee89a3cf8448081fb7694c0fe503da63" dependencies = [ - "ark-ff", + "ark-ff 0.4.2", "ark-relations", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand", ] [[package]] @@ -502,9 +653,9 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" [[package]] name = "arrayvec" @@ -593,18 +744,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -615,7 +766,7 @@ checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" dependencies = [ "futures", "pharos", - "rustc_version", + "rustc_version 0.4.0", ] [[package]] @@ -626,7 +777,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -643,8 +794,10 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axelar-wasm-std" -version = "0.1.0" +version = "1.0.0" dependencies = [ + "alloy-primitives", + "axelar-wasm-std-derive", "bs58 0.5.1", "cosmwasm-schema", "cosmwasm-std", @@ -671,13 +824,13 @@ dependencies = [ [[package]] name = "axelar-wasm-std-derive" -version = "0.1.0" +version = "1.0.0" dependencies = [ "axelar-wasm-std", "error-stack", "quote 1.0.36", "report", - "syn 2.0.68", + "syn 2.0.72", "thiserror", ] @@ -696,7 +849,7 @@ dependencies = [ "headers", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.30", "itoa", "matchit 0.7.3", "memchr", @@ -728,9 +881,9 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "hyper 1.4.0", + "hyper 1.4.1", "hyper-util", "itoa", "matchit 0.7.3", @@ -778,7 +931,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", @@ -932,9 +1085,9 @@ dependencies = [ [[package]] name = "bip32" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" +checksum = "aa13fae8b6255872fd86f7faf4b41168661d7d78609f7bfe6771b85c6739a15b" dependencies = [ "bs58 0.5.1", "hmac", @@ -1083,9 +1236,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62dc83a094a71d43eeadd254b1ec2d24cb6a0bb6cadce00df51f0db594711a32" +checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874" dependencies = [ "cc", "glob", @@ -1154,9 +1307,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "serde", @@ -1182,9 +1335,9 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "bytemuck" -version = "1.16.1" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" +checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" [[package]] name = "byteorder" @@ -1194,9 +1347,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" dependencies = [ "serde", ] @@ -1227,7 +1380,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver", + "semver 1.0.23", "serde", "serde_json", "thiserror", @@ -1244,9 +1397,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.104" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" +checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549" [[package]] name = "cfg-if" @@ -1266,7 +1419,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1281,9 +1434,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.8" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", @@ -1291,9 +1444,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", @@ -1304,25 +1457,25 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck 0.5.0", "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "client" -version = "0.1.0" +version = "1.0.0" dependencies = [ "cosmwasm-std", "error-stack", @@ -1353,9 +1506,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "colored" @@ -1443,10 +1596,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "coordinator" -version = "0.2.0" +version = "1.0.0" dependencies = [ "axelar-wasm-std", - "axelar-wasm-std-derive", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", @@ -1454,6 +1606,7 @@ dependencies = [ "cw2 1.1.2", "error-stack", "integration-tests", + "msgs-derive", "multisig", "report", "router-api", @@ -1686,7 +1839,7 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "rustc_version", + "rustc_version 0.4.0", "subtle", "zeroize", ] @@ -1699,7 +1852,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -1734,6 +1887,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-storage-macro" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "853c8ebf6d20542ea0dc57519ee458e7ee0caa3a1848beced2c603153d3f4dbe" +dependencies = [ + "syn 1.0.109", +] + [[package]] name = "cw-storage-plus" version = "0.15.1" @@ -1752,6 +1914,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" dependencies = [ "cosmwasm-std", + "cw-storage-macro", "schemars", "serde", ] @@ -1766,7 +1929,7 @@ dependencies = [ "cosmwasm-std", "cw2 0.15.1", "schemars", - "semver", + "semver 1.0.23", "serde", "thiserror", ] @@ -1781,7 +1944,7 @@ dependencies = [ "cosmwasm-std", "cw2 1.1.2", "schemars", - "semver", + "semver 1.0.23", "serde", "thiserror", ] @@ -1809,7 +1972,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.2.0", "schemars", - "semver", + "semver 1.0.23", "serde", "thiserror", ] @@ -1826,12 +1989,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core 0.20.9", - "darling_macro 0.20.9", + "darling_core 0.20.10", + "darling_macro 0.20.10", ] [[package]] @@ -1850,16 +2013,16 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2 1.0.86", "quote 1.0.36", "strsim 0.11.1", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -1875,13 +2038,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core 0.20.9", + "darling_core 0.20.10", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -1962,13 +2125,13 @@ dependencies = [ [[package]] name = "der_derive" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -2012,10 +2175,16 @@ dependencies = [ "convert_case", "proc-macro2 1.0.86", "quote 1.0.36", - "rustc_version", - "syn 2.0.68", + "rustc_version 0.4.0", + "syn 2.0.72", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "difference" version = "2.0.0" @@ -2099,7 +2268,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -2116,9 +2285,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "dunce" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dyn-clone" @@ -2185,9 +2354,7 @@ dependencies = [ "curve25519-dalek 4.1.3", "ed25519 2.2.3", "rand_core 0.6.4", - "serde", "sha2 0.10.8", - "signature 2.2.0", "subtle", "zeroize", ] @@ -2294,7 +2461,7 @@ dependencies = [ "once_cell", "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -2321,7 +2488,7 @@ checksum = "27a72baa257b5e0e2de241967bc5ee8f855d6072351042688621081d66b2a76b" dependencies = [ "anyhow", "eyre", - "rustc_version", + "rustc_version 0.4.0", ] [[package]] @@ -2407,8 +2574,8 @@ dependencies = [ "regex", "serde", "serde_json", - "syn 2.0.68", - "toml 0.8.14", + "syn 2.0.72", + "toml 0.8.19", "walkdir", ] @@ -2425,7 +2592,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", "serde_json", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -2443,7 +2610,7 @@ dependencies = [ "ethabi", "generic-array", "k256", - "num_enum 0.7.2", + "num_enum 0.7.3", "once_cell", "open-fastrlp", "rand", @@ -2451,7 +2618,7 @@ dependencies = [ "serde", "serde_json", "strum 0.26.3", - "syn 2.0.68", + "syn 2.0.72", "tempfile", "thiserror", "tiny-keccak", @@ -2509,7 +2676,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "events" -version = "0.1.0" +version = "1.0.0" dependencies = [ "axelar-wasm-std", "base64 0.21.7", @@ -2522,19 +2689,19 @@ dependencies = [ [[package]] name = "events-derive" -version = "0.1.0" +version = "1.0.0" dependencies = [ "error-stack", "events", "quote 1.0.36", "serde", "serde_json", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] name = "evm-gateway" -version = "0.1.0" +version = "1.0.0" dependencies = [ "axelar-wasm-std", "cosmwasm-std", @@ -2566,9 +2733,9 @@ dependencies = [ "aes", "aes-gcm", "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-secp256r1", - "ark-serialize", + "ark-serialize 0.4.2", "auto_ops", "base64ct", "bech32", @@ -2647,10 +2814,10 @@ dependencies = [ "ark-bls12-381", "ark-bn254", "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-groth16", "ark-relations", - "ark-serialize", + "ark-serialize 0.4.2", "ark-snark", "blst", "byte-slice-cast", @@ -2685,6 +2852,17 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + [[package]] name = "ff" version = "0.13.0" @@ -2758,18 +2936,18 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flagset" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb3aa5e95cf9aabc17f060cfa0ced7b83f042390760ca53bf09df9968acaa1" +checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec" dependencies = [ "serde", ] [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" dependencies = [ "crc32fast", "miniz_oxide", @@ -2889,7 +3067,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -2943,10 +3121,9 @@ dependencies = [ [[package]] name = "gateway" -version = "0.2.3" +version = "1.0.0" dependencies = [ "axelar-wasm-std", - "axelar-wasm-std-derive", "client", "cosmwasm-schema", "cosmwasm-std", @@ -2956,6 +3133,7 @@ dependencies = [ "error-stack", "gateway-api", "itertools 0.11.0", + "rand", "report", "router-api", "serde", @@ -2966,9 +3144,13 @@ dependencies = [ [[package]] name = "gateway-api" -version = "0.1.0" +version = "1.0.0" dependencies = [ + "axelar-wasm-std", "cosmwasm-schema", + "cosmwasm-std", + "error-stack", + "msgs-derive", "router-api", ] @@ -3044,6 +3226,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "goldie" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa70c42797cac60e6182e00f33f629212e02ba80d67e8a976f6168b57568d78e" +dependencies = [ + "anyhow", + "once_cell", + "pretty_assertions", + "serde", + "serde_json", + "upon", + "yansi 1.0.1", +] + [[package]] name = "group" version = "0.13.0" @@ -3069,7 +3266,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.2.6", + "indexmap 2.3.0", "slab", "tokio", "tokio-util", @@ -3238,9 +3435,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http 1.1.0", @@ -3255,7 +3452,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -3295,9 +3492,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.29" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", @@ -3319,15 +3516,15 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -3346,7 +3543,7 @@ dependencies = [ "futures", "headers", "http 0.2.12", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-rustls 0.22.1", "rustls-native-certs 0.5.0", "tokio", @@ -3363,7 +3560,7 @@ checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ "ct-logs", "futures-util", - "hyper 0.14.29", + "hyper 0.14.30", "log", "rustls 0.19.1", "rustls-native-certs 0.5.0", @@ -3380,7 +3577,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ "http 0.2.12", - "hyper 0.14.29", + "hyper 0.14.30", "log", "rustls 0.20.9", "rustls-native-certs 0.6.3", @@ -3397,7 +3594,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.29", + "hyper 0.14.30", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", @@ -3409,7 +3606,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.29", + "hyper 0.14.30", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -3417,15 +3614,15 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.4.0", + "http-body 1.0.1", + "hyper 1.4.1", "pin-project-lite", "tokio", ] @@ -3558,9 +3755,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -3603,7 +3800,7 @@ dependencies = [ [[package]] name = "integration-tests" -version = "0.1.0" +version = "1.0.0" dependencies = [ "axelar-wasm-std", "coordinator", @@ -3628,6 +3825,26 @@ dependencies = [ "voting-verifier", ] +[[package]] +name = "interchain-token-service" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "axelar-wasm-std", + "cosmwasm-schema", + "cosmwasm-std", + "error-stack", + "goldie", + "report", + "router-api", + "schemars", + "serde", + "serde_json", + "strum 0.25.0", + "thiserror", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -3645,9 +3862,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -3759,7 +3976,7 @@ dependencies = [ "futures-timer", "futures-util", "globset", - "hyper 0.14.29", + "hyper 0.14.30", "jsonrpsee-types", "parking_lot", "rand", @@ -3778,7 +3995,7 @@ version = "0.16.2" source = "git+https://github.com/wlmyng/jsonrpsee.git?rev=b1b300784795f6a64d0fcdf8f03081a9bc38bde8#b1b300784795f6a64d0fcdf8f03081a9bc38bde8" dependencies = [ "async-trait", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-rustls 0.23.2", "jsonrpsee-core", "jsonrpsee-types", @@ -3810,7 +4027,7 @@ dependencies = [ "futures-channel", "futures-util", "http 0.2.12", - "hyper 0.14.29", + "hyper 0.14.30", "jsonrpsee-core", "jsonrpsee-types", "serde", @@ -3884,6 +4101,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422fbc7ff2f2f5bdffeb07718e5a5324dca72b0c9293d50df4026652385e3314" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -4018,13 +4245,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ + "hermit-abi", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -4037,8 +4265,23 @@ dependencies = [ "downcast", "fragile", "lazy_static", - "mockall_derive", - "predicates", + "mockall_derive 0.11.4", + "predicates 2.1.5", + "predicates-tree", +] + +[[package]] +name = "mockall" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43766c2b5203b10de348ffe19f7e54564b64f3d6018ff7648d1e2d6d3a0f0a48" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive 0.12.1", + "predicates 3.1.2", "predicates-tree", ] @@ -4054,6 +4297,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "mockall_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cbce79ec385a1d4f54baa90a76401eb15d9cab93685f62e7e9f942aa00ae2" +dependencies = [ + "cfg-if", + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.72", +] + [[package]] name = "move-abstract-interpreter" version = "0.1.0" @@ -4303,7 +4558,7 @@ source = "git+https://github.com/mystenlabs/sui?tag=mainnet-v1.26.2#f531168c7452 dependencies = [ "enum-compat-util", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -4364,6 +4619,19 @@ dependencies = [ "smallvec", ] +[[package]] +name = "msgs-derive" +version = "1.0.0" +dependencies = [ + "axelar-wasm-std", + "cosmwasm-std", + "error-stack", + "itertools 0.11.0", + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.72", +] + [[package]] name = "msim-macros" version = "0.1.0" @@ -4438,10 +4706,9 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "multisig" -version = "0.4.0" +version = "1.0.0" dependencies = [ "axelar-wasm-std", - "axelar-wasm-std-derive", "cosmwasm-crypto", "cosmwasm-schema", "cosmwasm-std", @@ -4454,8 +4721,11 @@ dependencies = [ "enum-display-derive", "error-stack", "getrandom", + "goldie", + "hex", "itertools 0.11.0", "k256", + "msgs-derive", "report", "rewards", "router-api", @@ -4468,11 +4738,10 @@ dependencies = [ [[package]] name = "multisig-prover" -version = "0.6.0" +version = "1.0.0" dependencies = [ "anyhow", "axelar-wasm-std", - "axelar-wasm-std-derive", "bcs", "coordinator", "cosmwasm-schema", @@ -4489,9 +4758,11 @@ dependencies = [ "gateway", "gateway-api", "generic-array", + "goldie", "hex", "itertools 0.11.0", "k256", + "msgs-derive", "multisig", "prost 0.12.6", "report", @@ -4520,7 +4791,7 @@ dependencies = [ "tap", "tokio", "tracing", - "uuid 1.9.1", + "uuid 1.10.0", ] [[package]] @@ -4558,7 +4829,7 @@ dependencies = [ "fastcrypto-tbls", "hashbrown 0.12.3", "impl-trait-for-tuples", - "indexmap 2.2.6", + "indexmap 2.3.0", "mysten-util-mem-derive", "once_cell", "parking_lot", @@ -4625,17 +4896,17 @@ dependencies = [ [[package]] name = "nexus-gateway" -version = "0.3.0" +version = "1.0.0" dependencies = [ "axelar-wasm-std", - "axelar-wasm-std-derive", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", "cw2 1.1.2", "error-stack", "hex", - "mockall", + "mockall 0.11.4", + "msgs-derive", "report", "router-api", "schemars", @@ -4817,11 +5088,11 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ - "num_enum_derive 0.7.2", + "num_enum_derive 0.7.3", ] [[package]] @@ -4833,26 +5104,26 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] name = "num_enum_derive" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] name = "object" -version = "0.36.1" +version = "0.36.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" dependencies = [ "memchr", ] @@ -4946,7 +5217,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -5059,7 +5330,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -5163,9 +5434,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" dependencies = [ "memchr", "thiserror", @@ -5174,9 +5445,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" +checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" dependencies = [ "pest", "pest_generator", @@ -5184,22 +5455,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" +checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" dependencies = [ "pest", "pest_meta", "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] name = "pest_meta" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" +checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" dependencies = [ "once_cell", "pest", @@ -5223,7 +5494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", - "indexmap 2.2.6", + "indexmap 2.3.0", ] [[package]] @@ -5233,7 +5504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ "futures", - "rustc_version", + "rustc_version 0.4.0", ] [[package]] @@ -5266,7 +5537,7 @@ dependencies = [ "phf_shared", "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -5295,7 +5566,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -5362,9 +5633,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "predicates" @@ -5380,22 +5654,42 @@ dependencies = [ "regex", ] +[[package]] +name = "predicates" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +dependencies = [ + "anstyle", + "predicates-core", +] + [[package]] name = "predicates-core" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" [[package]] name = "predicates-tree" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" dependencies = [ "predicates-core", "termtree", ] +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi 0.5.1", +] + [[package]] name = "prettyplease" version = "0.1.25" @@ -5413,7 +5707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2 1.0.86", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -5656,7 +5950,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -5853,14 +6147,14 @@ checksum = "a25d631e41bfb5fdcde1d4e2215f62f7f0afa3ff11e26563765bd6ea1d229aeb" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags 2.6.0", ] @@ -5893,14 +6187,14 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -5927,7 +6221,7 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "report" -version = "0.1.0" +version = "1.0.0" dependencies = [ "error-stack", "eyre", @@ -5950,7 +6244,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-rustls 0.24.2", "ipnet", "js-sys", @@ -5979,10 +6273,9 @@ dependencies = [ [[package]] name = "rewards" -version = "0.4.0" +version = "1.0.0" dependencies = [ "axelar-wasm-std", - "axelar-wasm-std-derive", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", @@ -5990,6 +6283,7 @@ dependencies = [ "cw2 1.1.2", "error-stack", "itertools 0.11.0", + "msgs-derive", "report", "router-api", "thiserror", @@ -6068,9 +6362,9 @@ dependencies = [ [[package]] name = "roaring" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7699249cc2c7d71939f30868f47e9d7add0bdc030d90ee10bfd16887ff8bb1c8" +checksum = "8f4b84ba6e838ceb47b41de5194a60244fac43d9fe03b71dbe8c5a201081d6d1" dependencies = [ "bytemuck", "byteorder", @@ -6089,10 +6383,9 @@ dependencies = [ [[package]] name = "router" -version = "0.3.3" +version = "1.0.0" dependencies = [ "axelar-wasm-std", - "axelar-wasm-std-derive", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", @@ -6104,25 +6397,27 @@ dependencies = [ "hex", "integration-tests", "itertools 0.11.0", - "mockall", + "mockall 0.12.1", + "msgs-derive", "rand", "report", "router-api", "serde_json", + "thiserror", ] [[package]] name = "router-api" -version = "0.1.0" +version = "1.0.0" dependencies = [ "axelar-wasm-std", - "axelar-wasm-std-derive", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", "error-stack", "flagset", "hex", + "msgs-derive", "rand", "report", "schemars", @@ -6154,6 +6449,36 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ruint" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp", + "num-bigint 0.4.6", + "num-traits", + "parity-scale-codec 3.6.12", + "primitive-types 0.12.2", + "proptest", + "rand", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rust-ini" version = "0.18.0" @@ -6182,13 +6507,22 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.23", ] [[package]] @@ -6381,7 +6715,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", "serde_derive_internals", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -6446,9 +6780,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", "core-foundation", @@ -6459,14 +6793,23 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.23" @@ -6476,6 +6819,15 @@ dependencies = [ "serde", ] +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "send_wrapper" version = "0.4.0" @@ -6490,9 +6842,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -6538,13 +6890,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -6555,17 +6907,18 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] name = "serde_json" -version = "1.0.119" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8eddb61f0697cc3989c5d64b452f5488e2b8a60fd7d5076a3045076ffef8cb0" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.3.0", "itoa", + "memchr", "ryu", "serde", ] @@ -6599,14 +6952,14 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -6641,19 +6994,19 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.8.2" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "079f3a42cd87588d924ed95b533f8d30a483388c4e400ab736a7058e34f16169" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.6", + "indexmap 2.3.0", "serde", "serde_derive", "serde_json", - "serde_with_macros 3.8.2", + "serde_with_macros 3.9.0", "time", ] @@ -6663,22 +7016,22 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" dependencies = [ - "darling 0.20.9", + "darling 0.20.10", "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] name = "serde_with_macros" -version = "3.8.2" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc03aad67c1d26b7de277d51c86892e7d9a0110a2fe44bf6b26cc569fba302d6" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ - "darling 0.20.9", + "darling 0.20.10", "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -6695,10 +7048,9 @@ dependencies = [ [[package]] name = "service-registry" -version = "0.4.0" +version = "1.0.0" dependencies = [ "axelar-wasm-std", - "axelar-wasm-std-derive", "coordinator", "cosmwasm-schema", "cosmwasm-std", @@ -6707,6 +7059,7 @@ dependencies = [ "cw2 1.1.2", "error-stack", "integration-tests", + "msgs-derive", "report", "router-api", "schemars", @@ -6782,6 +7135,16 @@ dependencies = [ "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d79b758b7cb2085612b11a235055e485605a5103faccdd633f35bd7aee69dd" +dependencies = [ + "cc", + "cfg-if", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -6830,7 +7193,7 @@ dependencies = [ [[package]] name = "signature-verifier-api" -version = "0.1.0" +version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -6840,9 +7203,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" [[package]] name = "simple_asn1" @@ -7010,7 +7373,7 @@ checksum = "bbc159a1934c7be9761c237333a57febe060ace2bc9e3b337a59a37af206d19f" dependencies = [ "starknet-curve", "starknet-ff", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -7028,7 +7391,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7abf1b44ec5b18d87c1ae5f54590ca9d0699ef4dd5b2ffa66fc97f24613ec585" dependencies = [ - "ark-ff", + "ark-ff 0.4.2", "bigdecimal", "crypto-bigint", "getrandom", @@ -7124,7 +7487,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", "rustversion", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -7137,7 +7500,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", "rustversion", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -7255,7 +7618,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", "sui-enum-compat-util", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -7324,7 +7687,7 @@ dependencies = [ "fastcrypto-tbls", "fastcrypto-zkp", "im", - "indexmap 2.2.6", + "indexmap 2.3.0", "itertools 0.10.5", "jsonrpsee", "lru", @@ -7396,15 +7759,27 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.68" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" +dependencies = [ + "paste", + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.72", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -7482,14 +7857,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand 2.1.0", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7610,12 +7986,12 @@ dependencies = [ "futures", "getrandom", "http 0.2.12", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-proxy", "hyper-rustls 0.22.1", "peg", "pin-project", - "semver", + "semver 1.0.23", "serde", "serde_bytes", "serde_json", @@ -7660,22 +8036,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -7739,9 +8115,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -7754,12 +8130,12 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tofn" -version = "0.2.0" -source = "git+https://github.com/axelarnetwork/tofn.git?branch=update-deps#88285a1ed5f5135aab20ae73e124a8af63087c35" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "255a99b697179124254f23d05c2bc394276ba870791d99a9f5896f7f60d40eb9" dependencies = [ "bincode", "crypto-bigint", - "der 0.7.9", "ecdsa", "ed25519 2.2.3", "ed25519-dalek", @@ -7775,20 +8151,19 @@ dependencies = [ [[package]] name = "tokio" -version = "1.38.0" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -7803,13 +8178,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -7896,21 +8271,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.14", + "toml_edit 0.22.20", ] [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -7921,22 +8296,22 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.3.0", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.3.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.13", + "winnow 0.6.18", ] [[package]] @@ -7954,7 +8329,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-timeout", "percent-encoding", "pin-project", @@ -7981,7 +8356,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-timeout", "percent-encoding", "pin-project", @@ -8068,7 +8443,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", - "uuid 1.9.1", + "uuid 1.10.0", ] [[package]] @@ -8103,7 +8478,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -8222,7 +8597,7 @@ checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -8340,6 +8715,17 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "upon" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fe29601d1624f104fa9a35ea71a5f523dd8bd1cfc8c31f8124ad2b829f013c0" +dependencies = [ + "serde", + "unicode-ident", + "unicode-width", +] + [[package]] name = "url" version = "2.5.2" @@ -8371,9 +8757,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom", "rand", @@ -8421,9 +8807,9 @@ dependencies = [ [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vfs" @@ -8433,10 +8819,10 @@ checksum = "2e4fe92cfc1bad19c19925d5eee4b30584dbbdee4ff10183b261acccbef74e2d" [[package]] name = "voting-verifier" -version = "0.5.0" +version = "1.0.0" dependencies = [ + "alloy-primitives", "axelar-wasm-std", - "axelar-wasm-std-derive", "client", "cosmwasm-schema", "cosmwasm-std", @@ -8445,6 +8831,8 @@ dependencies = [ "cw2 1.1.2", "error-stack", "integration-tests", + "itertools 0.11.0", + "msgs-derive", "multisig", "rand", "report", @@ -8511,7 +8899,7 @@ dependencies = [ "once_cell", "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", "wasm-bindgen-shared", ] @@ -8545,7 +8933,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8640,11 +9028,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -8659,7 +9047,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -8677,7 +9065,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -8697,18 +9094,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -8719,9 +9116,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -8731,9 +9128,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -8743,15 +9140,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -8761,9 +9158,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -8773,9 +9170,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -8785,9 +9182,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -8797,9 +9194,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -8812,9 +9209,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -8840,7 +9237,7 @@ dependencies = [ "js-sys", "log", "pharos", - "rustc_version", + "rustc_version 0.4.0", "send_wrapper 0.6.0", "thiserror", "wasm-bindgen", @@ -8890,6 +9287,18 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yasna" version = "0.5.2" @@ -8901,22 +9310,23 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -8936,5 +9346,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.36", - "syn 2.0.68", + "syn 2.0.72", ] diff --git a/Cargo.toml b/Cargo.toml index e4cb9e75c..037a615e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,48 +1,58 @@ [workspace] -members = ["ampd", "contracts/*", "integration-tests", "packages/*"] +members = ["ampd", "contracts/*", "integration-tests", "interchain-token-service", "packages/*"] resolver = "2" [workspace.package] rust-version = "1.78.0" # be sure there is an optimizer release supporting this version before updating. See https://github.com/CosmWasm/optimizer [workspace.dependencies] -router = { version = "^0.3.3", path = "contracts/router" } +router = { version = "^1.0.0", path = "contracts/router" } cosmwasm-std = "1.5.5" cosmwasm-schema = "1.5.5" -cw-storage-plus = "1.2.0" +cw-storage-plus = { version = "1.2.0", features = ["iterator", "macro"] } cw2 = "1.1.0" ed25519-dalek = { version = "2.1.1", default-features = false } error-stack = { version = "0.4.0", features = ["eyre"] } -events = { version = "^0.1.0", path = "packages/events" } -events-derive = { version = "^0.1.0", path = "packages/events-derive" } -evm-gateway = { version = "^0.1.0", path = "packages/evm-gateway" } -axelar-wasm-std = { version = "^0.1.0", path = "packages/axelar-wasm-std" } -axelar-wasm-std-derive = { version = "^0.1.0", path = "packages/axelar-wasm-std-derive" } -integration-tests = { version = "^0.1.0", path = "integration-tests" } +events = { version = "^1.0.0", path = "packages/events" } +events-derive = { version = "^1.0.0", path = "packages/events-derive" } +evm-gateway = { version = "^1.0.0", path = "packages/evm-gateway" } +axelar-wasm-std = { version = "^1.0.0", path = "packages/axelar-wasm-std" } +axelar-wasm-std-derive = { version = "^1.0.0", path = "packages/axelar-wasm-std-derive" } +integration-tests = { version = "^1.0.0", path = "integration-tests" } itertools = "0.11.0" -voting-verifier = { version = "^0.5.0", path = "contracts/voting-verifier" } -coordinator = { version = "^0.2.0", path = "contracts/coordinator" } -multisig = { version = "^0.4.0", path = "contracts/multisig" } -multisig-prover = { version = "^0.6.0", path = "contracts/multisig-prover" } +voting-verifier = { version = "^1.0.0", path = "contracts/voting-verifier" } +coordinator = { version = "^1.0.0", path = "contracts/coordinator" } +multisig = { version = "^1.0.0", path = "contracts/multisig" } +msgs-derive = { version = "^1.0.0", path = "packages/msgs-derive" } +multisig-prover = { version = "^1.0.0", path = "contracts/multisig-prover" } num-traits = { version = "0.2.14", default-features = false } -service-registry = { version = "^0.4.0", path = "contracts/service-registry" } -gateway = { version = "^0.2.3", path = "contracts/gateway" } -gateway-api = { version = "^0.1.0", path = "packages/gateway-api" } -router-api = { version = "^0.1.0", path = "packages/router-api" } -report = { version = "^0.1.0", path = "packages/report" } -client = { version = "^0.1.0", path = "packages/client" } -rewards = { version = "^0.4.0", path = "contracts/rewards" } -thiserror = "1.0.47" +service-registry = { version = "^1.0.0", path = "contracts/service-registry" } +gateway = { version = "^1.0.0", path = "contracts/gateway" } +gateway-api = { version = "^1.0.0", path = "packages/gateway-api" } +router-api = { version = "^1.0.0", path = "packages/router-api" } +report = { version = "^1.0.0", path = "packages/report" } +client = { version = "^1.0.0", path = "packages/client" } +quote = "1.0.36" +rewards = { version = "^1.0.0", path = "contracts/rewards" } +thiserror = "1.0.61" +mockall = "0.12.1" serde = { version = "1.0.145", default-features = false, features = ["derive"] } serde_json = "1.0.89" schemars = "0.8.10" sha3 = { version = "0.10.8", default-features = false, features = [] } -signature-verifier-api = { version = "^0.1.0", path = "packages/signature-verifier-api" } +signature-verifier-api = { version = "^1.0.0", path = "packages/signature-verifier-api" } +syn = "2.0.68" ethers-contract = { version = "2.0.14", default-features = false, features = ["abigen"] } ethers-core = "2.0.14" tokio = "1.38.0" tokio-stream = "0.1.11" tokio-util = "0.7.11" +tofn = { version = "1.1" } +alloy-primitives = { version = "0.7.6", default-features = false, features = ["std"] } +alloy-sol-types = { version = "0.7.6", default-features = false, features = ["std"] } +strum = { version = "0.25", default-features = false, features = ["derive"] } +interchain-token-service = { version = "^0.1.0", path = "interchain-token-service" } +goldie = { version = "0.5" } [workspace.lints.clippy] arithmetic_side_effects = "deny" diff --git a/README.md b/README.md index 6afdc7caf..ff6c8ed0f 100644 --- a/README.md +++ b/README.md @@ -38,20 +38,21 @@ The basic rules are as follows: ### Compatibility -For the amplifier preview with version numbers < 1.0.0, please refer to the following compatibility table to select versions of +For the amplifier preview with version numbers < 1.0.0, please refer to the following compatibility table to select +versions of contracts and `ampd` that work well together. -| Binary | Version | -|-------------|---------| -| ampd | 0.5.0 | -| coordinator | 0.2.0 | -| gateway | 0.2.3 | -| multisig-prover | 0.6.0 | -| multisig | 0.4.0 | -| nexus-gateway | 0.3.0 | -| rewards | 0.4.0 | -| router | 0.3.3 | -| service-registry | 0.4.0 | -| voting-verifier | 0.5.0 | -| [tofnd](https://github.com/axelarnetwork/tofnd) | TBD | -| [solidity-contracts](https://github.com/axelarnetwork/axelar-gmp-sdk-solidity) | 5.9.0 | +| Binary | Version | +|--------------------------------------------------------------------------------|---------| +| ampd | 0.6.0 | +| coordinator | 0.2.0 | +| gateway | 0.2.3 | +| multisig-prover | 0.6.0 | +| multisig | 0.4.1 | +| nexus-gateway | 0.3.0 | +| rewards | 0.4.0 | +| router | 0.4.0 | +| service-registry | 0.4.1 | +| voting-verifier | 0.5.0 | +| [tofnd](https://github.com/axelarnetwork/tofnd) | 1.0.1 | +| [solidity-contracts](https://github.com/axelarnetwork/axelar-gmp-sdk-solidity) | 5.9.0 | \ No newline at end of file diff --git a/ampd/Cargo.toml b/ampd/Cargo.toml index 18bbeeb8a..13443b043 100644 --- a/ampd/Cargo.toml +++ b/ampd/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "ampd" -version = "0.5.0" +version = "1.0.0" rust-version = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -19,8 +19,7 @@ der = { version = "0.7.9", features = ["derive"] } deref-derive = "0.1.0" dirs = "5.0.1" ecdsa = { version = "0.16.6" } -ed25519 = { version = "2.2.3", features = ["pem"], default-features = false } -ed25519-dalek = { workspace = true } +ed25519 = { version = "2.2.3", default-features = false } enum-display-derive = "0.1.1" error-stack = { workspace = true } ethers-contract = { workspace = true } diff --git a/ampd/Dockerfile b/ampd/Dockerfile index dfada706b..2adea51e4 100644 --- a/ampd/Dockerfile +++ b/ampd/Dockerfile @@ -7,6 +7,7 @@ COPY ./Cargo.lock ./Cargo.lock COPY ./ampd/Cargo.toml ./ampd/Cargo.toml COPY ./packages ./packages COPY ./contracts ./contracts +COPY ./interchain-token-service ./interchain-token-service COPY ./integration-tests ./integration-tests COPY ./.cargo ./.cargo diff --git a/ampd/proto/ampd.proto b/ampd/proto/ampd.proto index 59bb0ebe6..fa477adda 100644 --- a/ampd/proto/ampd.proto +++ b/ampd/proto/ampd.proto @@ -15,9 +15,9 @@ message SubscribeRequest { bool include_block_begin_end = 2; } -message EventBlockBegin { uint64 height = 1; } +message EventBlockBegin {uint64 height = 1;} -message EventBlockEnd { uint64 height = 1; } +message EventBlockEnd {uint64 height = 1;} message Event { string event_type = 1; @@ -32,7 +32,7 @@ message SubscribeResponse { } } -message BroadcastRequest { google.protobuf.Any msg = 1; } +message BroadcastRequest {google.protobuf.Any msg = 1;} message BroadcastResponse {} @@ -47,16 +47,16 @@ message SignRequest { Algorithm algorithm = 3; } -message SignResponse { bytes signature = 1; } +message SignResponse {bytes signature = 1;} -message GetKeyRequest { +message KeyRequest { string key_id = 1; Algorithm algorithm = 2; } -message GetKeyResponse { bytes pub_key = 1; } +message KeyResponse {bytes pub_key = 1;} service Crypto { rpc Sign(SignRequest) returns (SignResponse) {} - rpc GetKey(GetKeyRequest) returns (GetKeyResponse) {} + rpc Key(KeyRequest) returns (KeyResponse) {} } diff --git a/ampd/src/asyncutil/future.rs b/ampd/src/asyncutil/future.rs index 5e0eb672a..a90b4c4b4 100644 --- a/ampd/src/asyncutil/future.rs +++ b/ampd/src/asyncutil/future.rs @@ -1,8 +1,8 @@ -use futures::{Future, FutureExt}; use std::pin::Pin; use std::task::{Context, Poll}; use std::time::Duration; +use futures::{Future, FutureExt}; use tokio::time; pub fn with_retry( @@ -101,7 +101,8 @@ where #[cfg(test)] mod tests { - use std::{future, sync::Mutex}; + use std::future; + use std::sync::Mutex; use tokio::time::Instant; diff --git a/ampd/src/asyncutil/task.rs b/ampd/src/asyncutil/task.rs index 1eae600d4..39d6f424a 100644 --- a/ampd/src/asyncutil/task.rs +++ b/ampd/src/asyncutil/task.rs @@ -1,7 +1,8 @@ -use axelar_wasm_std::error::extend_err; -use error_stack::{Context, Result, ResultExt}; use std::future::Future; use std::pin::Pin; + +use axelar_wasm_std::error::extend_err; +use error_stack::{Context, Result, ResultExt}; use thiserror::Error; use tokio::task::JoinSet; use tokio_util::sync::CancellationToken; @@ -109,10 +110,11 @@ pub struct TaskError; #[cfg(test)] mod test { - use crate::asyncutil::task::{CancellableTask, TaskError, TaskGroup}; use error_stack::report; use tokio_util::sync::CancellationToken; + use crate::asyncutil::task::{CancellableTask, TaskError, TaskGroup}; + #[tokio::test] async fn running_no_tasks_returns_no_error() { let tasks: TaskGroup = TaskGroup::new(); diff --git a/ampd/src/block_height_monitor.rs b/ampd/src/block_height_monitor.rs index bffbb72fa..5a80cf670 100644 --- a/ampd/src/block_height_monitor.rs +++ b/ampd/src/block_height_monitor.rs @@ -1,11 +1,9 @@ -use error_stack::{Result, ResultExt}; use std::time::Duration; + +use error_stack::{Result, ResultExt}; use thiserror::Error; -use tokio::{ - select, - sync::watch::{self, Receiver, Sender}, - time, -}; +use tokio::sync::watch::{self, Receiver, Sender}; +use tokio::{select, time}; use tokio_util::sync::CancellationToken; use tracing::info; @@ -66,7 +64,6 @@ impl BlockHeightMonitor { } } - #[allow(dead_code)] pub fn latest_block_height(&self) -> Receiver { self.latest_height_rx.clone() } @@ -77,16 +74,13 @@ mod tests { use std::convert::TryInto; use std::time::Duration; + use async_trait::async_trait; use mockall::mock; use tendermint::block::Height; - use tokio::test; - use tokio::time; + use tokio::{test, time}; use tokio_util::sync::CancellationToken; - use crate::tm_client; - - use crate::BlockHeightMonitor; - use async_trait::async_trait; + use crate::{tm_client, BlockHeightMonitor}; #[test] #[allow(clippy::cast_possible_truncation)] diff --git a/ampd/src/broadcaster/cosmos.rs b/ampd/src/broadcaster/cosmos.rs index 9f4f8569e..5f0dabeb9 100644 --- a/ampd/src/broadcaster/cosmos.rs +++ b/ampd/src/broadcaster/cosmos.rs @@ -15,7 +15,6 @@ use cosmrs::proto::cosmos::tx::v1beta1::service_client::ServiceClient; use cosmrs::proto::cosmos::tx::v1beta1::{ BroadcastTxRequest, GetTxRequest, GetTxResponse, SimulateRequest, SimulateResponse, }; - use mockall::automock; use tonic::transport::Channel; use tonic::{Response, Status}; @@ -25,7 +24,7 @@ use tonic::{Response, Status}; pub trait BroadcastClient { async fn broadcast_tx(&mut self, request: BroadcastTxRequest) -> Result; async fn simulate(&mut self, request: SimulateRequest) -> Result; - async fn get_tx(&mut self, request: GetTxRequest) -> Result; + async fn tx(&mut self, request: GetTxRequest) -> Result; } #[async_trait] @@ -43,7 +42,7 @@ impl BroadcastClient for ServiceClient { self.simulate(request).await.map(Response::into_inner) } - async fn get_tx(&mut self, request: GetTxRequest) -> Result { + async fn tx(&mut self, request: GetTxRequest) -> Result { self.get_tx(request).await.map(Response::into_inner) } } diff --git a/ampd/src/broadcaster/dec_coin.rs b/ampd/src/broadcaster/dec_coin.rs index ca2da4fff..a3d08a11d 100644 --- a/ampd/src/broadcaster/dec_coin.rs +++ b/ampd/src/broadcaster/dec_coin.rs @@ -186,10 +186,12 @@ impl Display for Denom { #[cfg(test)] mod tests { - use super::DecCoin; - use cosmrs::proto; use std::convert::TryFrom; + use cosmrs::proto; + + use super::DecCoin; + #[test] fn correct_parse() { assert!(DecCoin::new(1000.00, "uaxl").is_ok()) diff --git a/ampd/src/broadcaster/mod.rs b/ampd/src/broadcaster/mod.rs index f0f848cda..27c493fd7 100644 --- a/ampd/src/broadcaster/mod.rs +++ b/ampd/src/broadcaster/mod.rs @@ -4,6 +4,7 @@ use std::time::Duration; use std::{cmp, thread}; use async_trait::async_trait; +use axelar_wasm_std::FnExt; use cosmrs::proto::cosmos::auth::v1beta1::{ BaseAccount, QueryAccountRequest, QueryAccountResponse, }; @@ -16,6 +17,7 @@ use cosmrs::proto::traits::MessageExt; use cosmrs::tendermint::chain::Id; use cosmrs::tx::Fee; use cosmrs::{Amount, Coin, Denom, Gas}; +use dec_coin::DecCoin; use error_stack::{ensure, report, FutureExt, Report, Result, ResultExt}; use futures::TryFutureExt; use k256::sha2::{Digest, Sha256}; @@ -23,19 +25,15 @@ use mockall::automock; use num_traits::{cast, Zero}; use prost::Message; use prost_types::Any; +use report::{LoggableError, ResultCompatExt}; use serde::{Deserialize, Serialize}; use thiserror::Error; use tonic::{Code, Status}; -use tracing::debug; -use tracing::info; +use tracing::{debug, info}; +use tx::Tx; use typed_builder::TypedBuilder; use valuable::Valuable; -use axelar_wasm_std::FnExt; -use dec_coin::DecCoin; -use report::{LoggableError, ResultCompatExt}; -use tx::Tx; - use crate::tofnd; use crate::tofnd::grpc::Multisig; use crate::types::{PublicKey, TMAddress}; @@ -332,7 +330,7 @@ where Ok(Fee::from_amount_and_gas( Coin { - amount: cast((gas_adj.mul(self.config.gas_price.amount)).ceil()) + amount: cast(gas_adj.mul(self.config.gas_price.amount).ceil()) .ok_or(Error::FeeEstimation)?, denom: self.config.gas_price.denom.clone().into(), }, @@ -365,7 +363,7 @@ where let response = self .client - .get_tx(GetTxRequest { + .tx(GetTxRequest { hash: tx_hash.to_string(), }) .await; @@ -445,6 +443,7 @@ enum ConfirmationResult { #[cfg(test)] mod tests { + use cosmrs::bank::MsgSend; use cosmrs::crypto::PublicKey; use cosmrs::proto::cosmos::auth::v1beta1::{BaseAccount, QueryAccountResponse}; use cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceResponse; @@ -452,7 +451,8 @@ mod tests { use cosmrs::proto::cosmos::tx::v1beta1::{GetTxResponse, SimulateResponse}; use cosmrs::proto::traits::MessageExt; use cosmrs::proto::Any; - use cosmrs::{bank::MsgSend, tx::Msg, AccountId, Coin, Denom}; + use cosmrs::tx::Msg; + use cosmrs::{AccountId, Coin, Denom}; use ecdsa::SigningKey; use k256::Secp256k1; use rand::rngs::OsRng; @@ -580,7 +580,7 @@ mod tests { .expect_broadcast_tx() .returning(|_| Ok(TxResponse::default())); client - .expect_get_tx() + .expect_tx() .times((Config::default().tx_fetch_max_retries + 1) as usize) .returning(|_| Err(Status::deadline_exceeded("time out"))); @@ -612,7 +612,7 @@ mod tests { client .expect_broadcast_tx() .returning(|_| Ok(TxResponse::default())); - client.expect_get_tx().times(1).returning(|_| { + client.expect_tx().times(1).returning(|_| { Ok(GetTxResponse { tx_response: Some(TxResponse { code: 32, @@ -808,7 +808,7 @@ mod tests { client .expect_broadcast_tx() .returning(|_| Ok(TxResponse::default())); - client.expect_get_tx().returning(|_| { + client.expect_tx().returning(|_| { Ok(GetTxResponse { tx_response: Some(TxResponse { code: 0, diff --git a/ampd/src/broadcaster/tx.rs b/ampd/src/broadcaster/tx.rs index 48fee8f98..bcea228e8 100644 --- a/ampd/src/broadcaster/tx.rs +++ b/ampd/src/broadcaster/tx.rs @@ -1,12 +1,10 @@ use core::fmt::Debug; use std::future::Future; +use cosmrs::proto::cosmos::tx::v1beta1::TxRaw; use cosmrs::tendermint::chain::Id; -use cosmrs::{ - proto::cosmos::tx::v1beta1::TxRaw, - tx::{BodyBuilder, Fee, SignDoc, SignerInfo}, - Any, Coin, -}; +use cosmrs::tx::{BodyBuilder, Fee, SignDoc, SignerInfo}; +use cosmrs::{Any, Coin}; use error_stack::{Context, Result, ResultExt}; use report::ResultCompatExt; use thiserror::Error; @@ -98,24 +96,21 @@ where #[cfg(test)] mod tests { + use cosmrs::bank::MsgSend; + use cosmrs::bip32::secp256k1::elliptic_curve::rand_core::OsRng; + use cosmrs::crypto::secp256k1::SigningKey; + use cosmrs::proto::cosmos::tx::v1beta1::TxRaw; use cosmrs::proto::Any; - use cosmrs::{ - bank::MsgSend, - bip32::secp256k1::elliptic_curve::rand_core::OsRng, - crypto::secp256k1::SigningKey, - proto::cosmos::tx::v1beta1::TxRaw, - tendermint::chain::Id, - tx::{BodyBuilder, Fee, Msg, SignDoc, SignerInfo}, - AccountId, Coin, - }; + use cosmrs::tendermint::chain::Id; + use cosmrs::tx::{BodyBuilder, Fee, Msg, SignDoc, SignerInfo}; + use cosmrs::{AccountId, Coin}; use error_stack::Result; use k256::ecdsa; use k256::sha2::{Digest, Sha256}; use tokio::test; - use crate::types::PublicKey; - use super::{Error, Tx, DUMMY_CHAIN_ID}; + use crate::types::PublicKey; #[test] async fn sign_with_should_produce_the_correct_tx() { diff --git a/ampd/src/commands/mod.rs b/ampd/src/commands/mod.rs index c00e47911..5f4d091d2 100644 --- a/ampd/src/commands/mod.rs +++ b/ampd/src/commands/mod.rs @@ -1,14 +1,11 @@ use clap::Subcommand; +use cosmrs::proto::cosmos::auth::v1beta1::query_client::QueryClient as AuthQueryClient; +use cosmrs::proto::cosmos::bank::v1beta1::query_client::QueryClient as BankQueryClient; use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; -use cosmrs::proto::cosmos::{ - auth::v1beta1::query_client::QueryClient as AuthQueryClient, - bank::v1beta1::query_client::QueryClient as BankQueryClient, - tx::v1beta1::service_client::ServiceClient, -}; +use cosmrs::proto::cosmos::tx::v1beta1::service_client::ServiceClient; use cosmrs::proto::Any; use cosmrs::AccountId; -use error_stack::Result; -use error_stack::ResultExt; +use error_stack::{Result, ResultExt}; use serde::{Deserialize, Serialize}; use valuable::Valuable; @@ -16,8 +13,7 @@ use crate::broadcaster::Broadcaster; use crate::config::Config as AmpdConfig; use crate::tofnd::grpc::{Multisig, MultisigClient}; use crate::types::{PublicKey, TMAddress}; -use crate::{broadcaster, Error}; -use crate::{tofnd, PREFIX}; +use crate::{broadcaster, tofnd, Error, PREFIX}; pub mod bond_verifier; pub mod daemon; diff --git a/ampd/src/commands/register_public_key.rs b/ampd/src/commands/register_public_key.rs index 960d6885c..b31f45f1a 100644 --- a/ampd/src/commands/register_public_key.rs +++ b/ampd/src/commands/register_public_key.rs @@ -1,24 +1,21 @@ use std::convert::{TryFrom, TryInto}; -use cosmrs::{cosmwasm::MsgExecuteContract, tx::Msg}; +use cosmrs::cosmwasm::MsgExecuteContract; +use cosmrs::tx::Msg; use error_stack::{Result, ResultExt}; -use multisig::{key::PublicKey, msg::ExecuteMsg}; +use multisig::key::PublicKey; +use multisig::msg::ExecuteMsg; use report::ResultCompatExt; use sha3::{Digest, Keccak256}; use tracing::info; use valuable::Valuable; -use crate::{ - commands::{broadcast_tx, verifier_pub_key}, - config::Config, - handlers, - tofnd::{ - self, - grpc::{Multisig, MultisigClient}, - }, - types::TMAddress, - Error, PREFIX, -}; +use crate::commands::{broadcast_tx, verifier_pub_key}; +use crate::config::Config; +use crate::tofnd::grpc::{Multisig, MultisigClient}; +use crate::tofnd::{self}; +use crate::types::TMAddress; +use crate::{handlers, Error, PREFIX}; #[derive(clap::ValueEnum, Clone, Debug, Valuable, Copy)] enum KeyType { @@ -52,7 +49,7 @@ pub struct Args { pub async fn run(config: Config, args: Args) -> Result, Error> { let pub_key = verifier_pub_key(config.tofnd_config.clone()).await?; - let multisig_address = get_multisig_address(&config)?; + let multisig_address = multisig_address(&config)?; let tofnd_config = config.tofnd_config.clone(); @@ -108,7 +105,7 @@ pub async fn run(config: Config, args: Args) -> Result, Error> { ))) } -fn get_multisig_address(config: &Config) -> Result { +fn multisig_address(config: &Config) -> Result { config .handlers .iter() diff --git a/ampd/src/commands/verifier_address.rs b/ampd/src/commands/verifier_address.rs index a4afb66a3..d3b9f70f8 100644 --- a/ampd/src/commands/verifier_address.rs +++ b/ampd/src/commands/verifier_address.rs @@ -4,8 +4,7 @@ use report::ResultCompatExt; use crate::commands::verifier_pub_key; use crate::tofnd::Config as TofndConfig; -use crate::Error; -use crate::PREFIX; +use crate::{Error, PREFIX}; pub async fn run(config: TofndConfig) -> Result, Error> { verifier_pub_key(config) diff --git a/ampd/src/config.rs b/ampd/src/config.rs index e97311dad..dfca06d65 100644 --- a/ampd/src/config.rs +++ b/ampd/src/config.rs @@ -1,13 +1,13 @@ use std::net::{Ipv4Addr, SocketAddrV4}; -use std::time::Duration; use serde::{Deserialize, Serialize}; -use crate::broadcaster; use crate::commands::ServiceRegistryConfig; -use crate::handlers::{self, config::deserialize_handler_configs}; +use crate::handlers::config::deserialize_handler_configs; +use crate::handlers::{self}; use crate::tofnd::Config as TofndConfig; use crate::url::Url; +use crate::{broadcaster, event_processor}; #[derive(Deserialize, Serialize, Debug, PartialEq)] #[serde(default)] @@ -15,9 +15,7 @@ pub struct Config { pub health_check_bind_addr: SocketAddrV4, pub tm_jsonrpc: Url, pub tm_grpc: Url, - pub event_buffer_cap: usize, - #[serde(with = "humantime_serde")] - pub event_stream_timeout: Duration, + pub event_processor: event_processor::Config, pub broadcast: broadcaster::Config, #[serde(deserialize_with = "deserialize_handler_configs")] pub handlers: Vec, @@ -33,8 +31,7 @@ impl Default for Config { broadcast: broadcaster::Config::default(), handlers: vec![], tofnd_config: TofndConfig::default(), - event_buffer_cap: 100000, - event_stream_timeout: Duration::from_secs(15), + event_processor: event_processor::Config::default(), service_registry: ServiceRegistryConfig::default(), health_check_bind_addr: SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 3000), } @@ -51,17 +48,14 @@ mod tests { use std::time::Duration; use cosmrs::AccountId; - use router_api::ChainName; + use super::Config; use crate::evm::finalizer::Finalization; - use crate::handlers::config::Chain; - use crate::handlers::config::Config as HandlerConfig; + use crate::handlers::config::{Chain, Config as HandlerConfig}; use crate::types::TMAddress; use crate::url::Url; - use super::Config; - const PREFIX: &str = "axelar"; #[test] diff --git a/ampd/src/event_processor.rs b/ampd/src/event_processor.rs index 889fc7457..204b0c64f 100644 --- a/ampd/src/event_processor.rs +++ b/ampd/src/event_processor.rs @@ -7,12 +7,12 @@ use error_stack::{Context, Result, ResultExt}; use events::Event; use futures::StreamExt; use report::LoggableError; +use serde::{Deserialize, Serialize}; use thiserror::Error; use tokio::time::timeout; use tokio_stream::Stream; use tokio_util::sync::CancellationToken; -use tracing::info; -use tracing::warn; +use tracing::{info, warn}; use valuable::Valuable; use crate::asyncutil::future::{self, RetryPolicy}; @@ -36,6 +36,27 @@ pub enum Error { Tasks(#[from] TaskError), } +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +pub struct Config { + #[serde(with = "humantime_serde")] + pub retry_delay: Duration, + pub retry_max_attempts: u64, + #[serde(with = "humantime_serde")] + pub stream_timeout: Duration, + pub stream_buffer_size: usize, +} + +impl Default for Config { + fn default() -> Self { + Self { + retry_delay: Duration::from_secs(1), + retry_max_attempts: 3, + stream_timeout: Duration::from_secs(15), + stream_buffer_size: 100000, + } + } +} + /// Let the `handler` consume events from the `event_stream`. The token is checked for cancellation /// at the end of each consumed block or when the `event_stream` times out. If the token is cancelled or the /// `event_stream` is closed, the function returns @@ -44,7 +65,7 @@ pub async fn consume_events( handler: H, broadcaster: B, event_stream: S, - stream_timeout: Duration, + event_processor_config: Config, token: CancellationToken, ) -> Result<(), Error> where @@ -55,12 +76,20 @@ where { let mut event_stream = Box::pin(event_stream); loop { - let stream_status = retrieve_next_event(&mut event_stream, stream_timeout) - .await - .change_context(Error::EventStream)?; + let stream_status = + retrieve_next_event(&mut event_stream, event_processor_config.stream_timeout) + .await + .change_context(Error::EventStream)?; if let StreamStatus::Active(event) = &stream_status { - handle_event(&handler, &broadcaster, event).await?; + handle_event( + &handler, + &broadcaster, + event, + event_processor_config.retry_delay, + event_processor_config.retry_max_attempts, + ) + .await?; } if let StreamStatus::Active(Event::BlockEnd(height)) = &stream_status { @@ -77,7 +106,13 @@ where } } -async fn handle_event(handler: &H, broadcaster: &B, event: &Event) -> Result<(), Error> +async fn handle_event( + handler: &H, + broadcaster: &B, + event: &Event, + handle_sleep_duration: Duration, + handle_max_attempts: u64, +) -> Result<(), Error> where H: EventHandler, B: BroadcasterClient, @@ -85,10 +120,9 @@ where // if handlers run into errors we log them and then move on to the next event match future::with_retry( || handler.handle(event), - // TODO: make timeout and max_attempts configurable RetryPolicy::RepeatConstant { - sleep: Duration::from_secs(1), - max_attempts: 3, + sleep: handle_sleep_duration, + max_attempts: handle_max_attempts, }, ) .await @@ -148,6 +182,8 @@ enum StreamStatus { #[cfg(test)] mod tests { + use std::time::Duration; + use async_trait::async_trait; use cosmrs::bank::MsgSend; use cosmrs::tx::Msg; @@ -156,15 +192,24 @@ mod tests { use events::Event; use futures::stream; use mockall::mock; - use std::time::Duration; use tokio::time::timeout; use tokio_util::sync::CancellationToken; use crate::event_processor; - use crate::{ - event_processor::{consume_events, Error, EventHandler}, - queue::queued_broadcaster::MockBroadcasterClient, - }; + use crate::event_processor::{consume_events, Config, Error, EventHandler}; + use crate::queue::queued_broadcaster::MockBroadcasterClient; + + pub fn setup_event_config( + retry_delay_value: Duration, + stream_timeout_value: Duration, + ) -> Config { + Config { + retry_delay: retry_delay_value, + retry_max_attempts: 3, + stream_timeout: stream_timeout_value, + stream_buffer_size: 100000, + } + } #[tokio::test] async fn stop_when_stream_closes() { @@ -181,6 +226,7 @@ mod tests { .returning(|_| Ok(vec![])); let broadcaster = MockBroadcasterClient::new(); + let event_config = setup_event_config(Duration::from_secs(1), Duration::from_secs(1000)); let result_with_timeout = timeout( Duration::from_secs(1), @@ -189,7 +235,7 @@ mod tests { handler, broadcaster, stream::iter(events), - Duration::from_secs(1000), + event_config, CancellationToken::new(), ), ) @@ -210,6 +256,7 @@ mod tests { handler.expect_handle().times(1).returning(|_| Ok(vec![])); let broadcaster = MockBroadcasterClient::new(); + let event_config = setup_event_config(Duration::from_secs(1), Duration::from_secs(1000)); let result_with_timeout = timeout( Duration::from_secs(1), @@ -218,7 +265,7 @@ mod tests { handler, broadcaster, stream::iter(events), - Duration::from_secs(1000), + event_config, CancellationToken::new(), ), ) @@ -240,6 +287,7 @@ mod tests { .returning(|_| Err(report!(EventHandlerError::Failed))); let broadcaster = MockBroadcasterClient::new(); + let event_config = setup_event_config(Duration::from_secs(1), Duration::from_secs(1000)); let result_with_timeout = timeout( Duration::from_secs(3), @@ -248,7 +296,7 @@ mod tests { handler, broadcaster, stream::iter(events), - Duration::from_secs(1000), + event_config, CancellationToken::new(), ), ) @@ -269,6 +317,7 @@ mod tests { .once() .returning(|_| Ok(vec![dummy_msg(), dummy_msg()])); + let event_config = setup_event_config(Duration::from_secs(1), Duration::from_secs(1000)); let mut broadcaster = MockBroadcasterClient::new(); broadcaster .expect_broadcast() @@ -282,7 +331,7 @@ mod tests { handler, broadcaster, stream::iter(events), - Duration::from_secs(1000), + event_config, CancellationToken::new(), ), ) @@ -306,6 +355,7 @@ mod tests { handler.expect_handle().times(4).returning(|_| Ok(vec![])); let broadcaster = MockBroadcasterClient::new(); + let event_config = setup_event_config(Duration::from_secs(1), Duration::from_secs(1000)); let token = CancellationToken::new(); token.cancel(); @@ -317,7 +367,7 @@ mod tests { handler, broadcaster, stream::iter(events), - Duration::from_secs(1000), + event_config, token, ), ) @@ -332,6 +382,7 @@ mod tests { let handler = MockEventHandler::new(); let broadcaster = MockBroadcasterClient::new(); + let event_config = setup_event_config(Duration::from_secs(1), Duration::from_secs(0)); let token = CancellationToken::new(); token.cancel(); @@ -343,7 +394,7 @@ mod tests { handler, broadcaster, stream::pending::>(), // never returns any items so it can time out - Duration::from_secs(0), + event_config, token, ), ) diff --git a/ampd/src/event_sub.rs b/ampd/src/event_sub.rs index c743ef4ab..512b0ee4c 100644 --- a/ampd/src/event_sub.rs +++ b/ampd/src/event_sub.rs @@ -1,24 +1,20 @@ use std::iter; use std::time::Duration; -use error_stack::ResultExt; -use error_stack::{FutureExt, Report, Result}; +use error_stack::{FutureExt, Report, Result, ResultExt}; +use events::Event; use futures::TryStreamExt; use mockall::automock; use tendermint::block; use thiserror::Error; -use tokio::select; use tokio::sync::broadcast::{self, Sender}; -use tokio::time; +use tokio::{select, time}; use tokio_stream::wrappers::errors::BroadcastStreamRecvError; use tokio_stream::wrappers::BroadcastStream; use tokio_stream::Stream; use tokio_util::sync::CancellationToken; use tracing::info; -use events::Event; - -use crate::asyncutil::future::{self, RetryPolicy}; use crate::tm_client::TmClient; #[automock] @@ -128,20 +124,13 @@ impl EventPublisher { } async fn events(&self, block_height: block::Height) -> Result, EventSubError> { - let block_results = future::with_retry( - || { - self.tm_client.block_results(block_height).change_context( - EventSubError::EventQuery { - block: block_height, - }, - ) - }, - RetryPolicy::RepeatConstant { - sleep: Duration::from_secs(1), - max_attempts: 3, - }, - ) - .await?; + let block_results = self + .tm_client + .block_results(block_height) + .change_context(EventSubError::EventQuery { + block: block_height, + }) + .await?; let begin_block_events = block_results.begin_block_events.into_iter().flatten(); let tx_events = block_results diff --git a/ampd/src/evm/finalizer.rs b/ampd/src/evm/finalizer.rs index b5dedda23..f47cdbf47 100644 --- a/ampd/src/evm/finalizer.rs +++ b/ampd/src/evm/finalizer.rs @@ -116,13 +116,12 @@ where #[cfg(test)] mod tests { + use ethers_core::abi::Hash; + use ethers_core::types::{Block, U64}; + use tokio::test; + use crate::evm::finalizer::{pick, ConfirmationHeightFinalizer, Finalization, Finalizer}; use crate::evm::json_rpc::MockEthereumClient; - use ethers_core::{ - abi::Hash, - types::{Block, U64}, - }; - use tokio::test; #[test] async fn latest_finalized_block_height_should_work() { diff --git a/ampd/src/evm/json_rpc.rs b/ampd/src/evm/json_rpc.rs index 3046d726d..983ef7eb0 100644 --- a/ampd/src/evm/json_rpc.rs +++ b/ampd/src/evm/json_rpc.rs @@ -1,8 +1,6 @@ use async_trait::async_trait; -use ethers_core::{ - types::{Block, BlockNumber, TransactionReceipt, H256, U64}, - utils::serialize, -}; +use ethers_core::types::{Block, BlockNumber, TransactionReceipt, H256, U64}; +use ethers_core::utils::serialize; use ethers_providers::{JsonRpcClient, ProviderError}; use mockall::automock; diff --git a/ampd/src/evm/verifier.rs b/ampd/src/evm/verifier.rs index e14722b7e..b29c702f0 100644 --- a/ampd/src/evm/verifier.rs +++ b/ampd/src/evm/verifier.rs @@ -1,9 +1,8 @@ +use axelar_wasm_std::voting::Vote; use ethers_contract::EthLogDecode; use ethers_core::types::{Log, TransactionReceipt, H256}; -use num_traits::cast; - -use axelar_wasm_std::voting::Vote; use evm_gateway::{IAxelarAmplifierGatewayEvents, WeightedSigners}; +use num_traits::cast; use crate::handlers::evm_verify_msg::Message; use crate::handlers::evm_verify_verifier_set::VerifierSetConfirmation; @@ -51,7 +50,7 @@ fn has_failed(tx_receipt: &TransactionReceipt) -> bool { tx_receipt.status == Some(0u64.into()) } -fn get_event<'a>( +fn event<'a>( gateway_address: &EVMAddress, tx_receipt: &'a TransactionReceipt, log_index: u32, @@ -84,7 +83,7 @@ where return Vote::FailedOnChain; } - match get_event(gateway_address, tx_receipt, expected_event_index) { + match event(gateway_address, tx_receipt, expected_event_index) { Some(event) if tx_receipt.transaction_hash == expected_transaction_hash && to_verify == event => { @@ -121,29 +120,22 @@ mod tests { use axelar_wasm_std::voting::Vote; use cosmwasm_std::Uint128; use ethers_contract::EthEvent; - use ethers_core::{ - abi::{encode, Token}, - types::{Log, TransactionReceipt, H256}, - }; - use evm_gateway::{ - i_axelar_amplifier_gateway::{ContractCallFilter, SignersRotatedFilter}, - WeightedSigners, - }; - use multisig::{ - key::KeyType, - test::common::{build_verifier_set, ecdsa_test_data}, - }; + use ethers_core::abi::{encode, Token}; + use ethers_core::types::{Log, TransactionReceipt, H256}; + use evm_gateway::i_axelar_amplifier_gateway::{ContractCallFilter, SignersRotatedFilter}; + use evm_gateway::WeightedSigners; + use multisig::key::KeyType; + use multisig::test::common::{build_verifier_set, ecdsa_test_data}; use super::{verify_message, verify_verifier_set}; - use crate::{ - handlers::{evm_verify_msg::Message, evm_verify_verifier_set::VerifierSetConfirmation}, - types::{EVMAddress, Hash}, - }; + use crate::handlers::evm_verify_msg::Message; + use crate::handlers::evm_verify_verifier_set::VerifierSetConfirmation; + use crate::types::{EVMAddress, Hash}; #[test] fn should_not_verify_verifier_set_if_tx_id_does_not_match() { let (gateway_address, tx_receipt, mut verifier_set) = - get_matching_verifier_set_and_tx_receipt(); + matching_verifier_set_and_tx_receipt(); verifier_set.tx_id = Hash::random(); assert_eq!( @@ -155,7 +147,7 @@ mod tests { #[test] fn should_not_verify_verifier_set_if_tx_failed() { let (gateway_address, mut tx_receipt, verifier_set) = - get_matching_verifier_set_and_tx_receipt(); + matching_verifier_set_and_tx_receipt(); tx_receipt.status = Some(0u64.into()); assert_eq!( @@ -166,7 +158,7 @@ mod tests { #[test] fn should_not_verify_verifier_set_if_gateway_address_does_not_match() { - let (_, tx_receipt, verifier_set) = get_matching_verifier_set_and_tx_receipt(); + let (_, tx_receipt, verifier_set) = matching_verifier_set_and_tx_receipt(); let gateway_address = EVMAddress::random(); assert_eq!( @@ -178,7 +170,7 @@ mod tests { #[test] fn should_not_verify_verifier_set_if_log_index_does_not_match() { let (gateway_address, tx_receipt, mut verifier_set) = - get_matching_verifier_set_and_tx_receipt(); + matching_verifier_set_and_tx_receipt(); verifier_set.event_index = 0; assert_eq!( @@ -200,7 +192,7 @@ mod tests { #[test] fn should_not_verify_verifier_set_if_verifier_set_does_not_match() { let (gateway_address, tx_receipt, mut verifier_set) = - get_matching_verifier_set_and_tx_receipt(); + matching_verifier_set_and_tx_receipt(); verifier_set.verifier_set.threshold = Uint128::from(50u64); assert_eq!( @@ -211,8 +203,7 @@ mod tests { #[test] fn should_verify_verifier_set_if_correct() { - let (gateway_address, tx_receipt, verifier_set) = - get_matching_verifier_set_and_tx_receipt(); + let (gateway_address, tx_receipt, verifier_set) = matching_verifier_set_and_tx_receipt(); assert_eq!( verify_verifier_set(&gateway_address, &tx_receipt, &verifier_set), @@ -222,7 +213,7 @@ mod tests { #[test] fn should_not_verify_msg_if_tx_id_does_not_match() { - let (gateway_address, tx_receipt, mut msg) = get_matching_msg_and_tx_receipt(); + let (gateway_address, tx_receipt, mut msg) = matching_msg_and_tx_receipt(); msg.tx_id = Hash::random(); assert_eq!( @@ -233,7 +224,7 @@ mod tests { #[test] fn should_not_verify_msg_if_tx_failed() { - let (gateway_address, mut tx_receipt, msg) = get_matching_msg_and_tx_receipt(); + let (gateway_address, mut tx_receipt, msg) = matching_msg_and_tx_receipt(); tx_receipt.status = Some(0u64.into()); assert_eq!( @@ -244,7 +235,7 @@ mod tests { #[test] fn should_not_verify_msg_if_gateway_address_does_not_match() { - let (_, tx_receipt, msg) = get_matching_msg_and_tx_receipt(); + let (_, tx_receipt, msg) = matching_msg_and_tx_receipt(); let gateway_address = EVMAddress::random(); assert_eq!( @@ -255,7 +246,7 @@ mod tests { #[test] fn should_not_verify_msg_if_log_index_does_not_match() { - let (gateway_address, tx_receipt, mut msg) = get_matching_msg_and_tx_receipt(); + let (gateway_address, tx_receipt, mut msg) = matching_msg_and_tx_receipt(); msg.event_index = 0; assert_eq!( @@ -276,7 +267,7 @@ mod tests { #[test] fn should_not_verify_msg_if_msg_does_not_match() { - let (gateway_address, tx_receipt, mut msg) = get_matching_msg_and_tx_receipt(); + let (gateway_address, tx_receipt, mut msg) = matching_msg_and_tx_receipt(); msg.source_address = EVMAddress::random(); assert_eq!( @@ -287,7 +278,7 @@ mod tests { #[test] fn should_verify_msg_if_correct() { - let (gateway_address, tx_receipt, msg) = get_matching_msg_and_tx_receipt(); + let (gateway_address, tx_receipt, msg) = matching_msg_and_tx_receipt(); assert_eq!( verify_message(&gateway_address, &tx_receipt, &msg), @@ -295,7 +286,7 @@ mod tests { ); } - fn get_matching_verifier_set_and_tx_receipt( + fn matching_verifier_set_and_tx_receipt( ) -> (EVMAddress, TransactionReceipt, VerifierSetConfirmation) { let tx_id = Hash::random(); let log_index = 1; @@ -334,7 +325,7 @@ mod tests { (gateway_address, tx_receipt, verifier_set) } - fn get_matching_msg_and_tx_receipt() -> (EVMAddress, TransactionReceipt, Message) { + fn matching_msg_and_tx_receipt() -> (EVMAddress, TransactionReceipt, Message) { let tx_id = Hash::random(); let log_index = 1; let gateway_address = EVMAddress::random(); diff --git a/ampd/src/grpc/client.rs b/ampd/src/grpc/client.rs index 8cff0c2b3..44822fbc9 100644 --- a/ampd/src/grpc/client.rs +++ b/ampd/src/grpc/client.rs @@ -1,7 +1,8 @@ use error_stack::{Report, Result}; use tonic::{codegen, transport}; -use super::proto::{ampd_client::AmpdClient, crypto_client::CryptoClient}; +use super::proto::ampd_client::AmpdClient; +use super::proto::crypto_client::CryptoClient; pub struct Client { pub ampd: AmpdClient, @@ -29,8 +30,9 @@ where mod tests { use std::time::Duration; - use cosmrs::Any; - use cosmrs::{bank::MsgSend, tx::Msg, AccountId}; + use cosmrs::bank::MsgSend; + use cosmrs::tx::Msg; + use cosmrs::{AccountId, Any}; use error_stack::Report; use events::Event; use futures::StreamExt; @@ -39,24 +41,23 @@ mod tests { use mockall::predicate; use rand::rngs::OsRng; use tokio::net::TcpListener; - use tokio::sync::mpsc; - use tokio::{sync::oneshot, test, time}; + use tokio::sync::{mpsc, oneshot}; + use tokio::{test, time}; use tokio_stream::wrappers::errors::BroadcastStreamRecvError; use tokio_stream::wrappers::{ReceiverStream, TcpListenerStream}; use tonic::Code; use url::Url; + use crate::event_sub::MockEventSub; + use crate::grpc; use crate::proto::{ - Algorithm, BroadcastRequest, BroadcastResponse, GetKeyRequest, GetKeyResponse, SignRequest, + Algorithm, BroadcastRequest, BroadcastResponse, KeyRequest, KeyResponse, SignRequest, SignResponse, SubscribeRequest, }; + use crate::queue::queued_broadcaster::MockBroadcasterClient; + use crate::tofnd::grpc::MockMultisig; + use crate::tofnd::{self}; use crate::types::PublicKey; - use crate::{ - event_sub::MockEventSub, - grpc, - queue::queued_broadcaster::MockBroadcasterClient, - tofnd::{self, grpc::MockMultisig}, - }; async fn start_server( event_sub: MockEventSub, @@ -79,7 +80,7 @@ mod tests { } #[test] - async fn get_key_should_work() { + async fn key_should_work() { let key_id = "key_id"; let key: PublicKey = SigningKey::random(&mut OsRng).verifying_key().into(); let algorithm = Algorithm::Ed25519; @@ -104,14 +105,14 @@ mod tests { .await .unwrap() .crypto - .get_key(GetKeyRequest { + .key(KeyRequest { key_id: key_id.to_string(), algorithm: algorithm.into(), }) .await .unwrap() .into_inner(), - GetKeyResponse { + KeyResponse { pub_key: key.to_bytes() } ); diff --git a/ampd/src/grpc/server/ampd.rs b/ampd/src/grpc/server/ampd.rs index 966103019..5cbe6a6e3 100644 --- a/ampd/src/grpc/server/ampd.rs +++ b/ampd/src/grpc/server/ampd.rs @@ -1,4 +1,5 @@ -use std::{future, pin::Pin}; +use std::future; +use std::pin::Pin; use async_trait::async_trait; use events::Event; @@ -6,7 +7,8 @@ use futures::{Stream, StreamExt, TryStreamExt}; use tonic::{Request, Response, Status}; use super::proto; -use crate::{event_sub::EventSub, queue::queued_broadcaster::BroadcasterClient}; +use crate::event_sub::EventSub; +use crate::queue::queued_broadcaster::BroadcasterClient; impl From for proto::subscribe_response::Event { fn from(event: Event) -> Self { @@ -126,8 +128,9 @@ mod tests { use std::collections::HashMap; use std::time::Duration; - use cosmrs::Any; - use cosmrs::{bank::MsgSend, tx::Msg, AccountId}; + use cosmrs::bank::MsgSend; + use cosmrs::tx::Msg; + use cosmrs::{AccountId, Any}; use error_stack::Report; use events::Event; use serde_json::Map; @@ -138,11 +141,12 @@ mod tests { use tokio_stream::StreamExt; use tonic::Code; - use crate::queue::queued_broadcaster; - use crate::{event_sub::MockEventSub, queue::queued_broadcaster::MockBroadcasterClient}; - - use super::proto::{self, ampd_server::Ampd}; + use super::proto::ampd_server::Ampd; + use super::proto::{self}; use super::Server; + use crate::event_sub::MockEventSub; + use crate::queue::queued_broadcaster; + use crate::queue::queued_broadcaster::MockBroadcasterClient; #[test] async fn subscribe_should_return_stream_of_error_when_event_subscriber_fails() { diff --git a/ampd/src/grpc/server/crypto.rs b/ampd/src/grpc/server/crypto.rs index a53ac3e63..48e912616 100644 --- a/ampd/src/grpc/server/crypto.rs +++ b/ampd/src/grpc/server/crypto.rs @@ -2,10 +2,10 @@ use async_trait::async_trait; use k256::sha2::{Digest, Sha256}; use tonic::{Request, Response, Status}; -use crate::tofnd::{self, grpc::Multisig}; -use crate::types::PublicKey; - use super::proto; +use crate::tofnd::grpc::Multisig; +use crate::tofnd::{self}; +use crate::types::PublicKey; impl From for tofnd::Algorithm { fn from(algorithm: proto::Algorithm) -> Self { @@ -67,17 +67,17 @@ where Ok(Response::new(proto::SignResponse { signature })) } - async fn get_key( + async fn key( &self, - req: Request, - ) -> Result, Status> { + req: Request, + ) -> Result, Status> { let req = req.into_inner(); let algorithm = proto::Algorithm::from_i32(req.algorithm) .ok_or(Status::invalid_argument("invalid algorithm"))?; let key = self.key(&req.key_id, algorithm).await?; - Ok(Response::new(proto::GetKeyResponse { + Ok(Response::new(proto::KeyResponse { pub_key: key.to_bytes(), })) } @@ -92,13 +92,14 @@ mod tests { use tokio::test; use tonic::Code; + use super::proto::{self}; + use super::Server; + use crate::proto::crypto_server; + use crate::proto::crypto_server::Crypto; use crate::tofnd; use crate::tofnd::grpc::MockMultisig; use crate::types::PublicKey; - use super::proto::{self, crypto_server::Crypto}; - use super::Server; - #[test] async fn sign_should_return_correct_signature() { let key_id = "key_id"; @@ -158,7 +159,7 @@ mod tests { } #[test] - async fn get_key_should_return_correct_key() { + async fn key_should_return_correct_key() { let key_id = "key_id"; let algorithm = proto::Algorithm::Ecdsa; let key: PublicKey = SigningKey::random(&mut OsRng).verifying_key().into(); @@ -173,27 +174,30 @@ mod tests { .return_once(move |_, _| Ok(key)); let server = Server::new(multisig_client); - let req = tonic::Request::new(proto::GetKeyRequest { + let req = tonic::Request::new(proto::KeyRequest { key_id: key_id.to_string(), algorithm: algorithm.into(), }); - let res = server.get_key(req).await.unwrap().into_inner(); + let res = crypto_server::Crypto::key(&server, req) + .await + .unwrap() + .into_inner(); assert_eq!(res.pub_key, key.to_bytes()); } #[test] - async fn get_key_should_return_error_when_algorithm_is_invalid() { + async fn key_should_return_error_when_algorithm_is_invalid() { let key_id = "key_id"; let multisig_client = MockMultisig::default(); let server = Server::new(multisig_client); - let req = tonic::Request::new(proto::GetKeyRequest { + let req = tonic::Request::new(proto::KeyRequest { key_id: key_id.to_string(), algorithm: 2, }); - let res = server.get_key(req).await.unwrap_err(); + let res = crypto_server::Crypto::key(&server, req).await.unwrap_err(); assert_eq!(res.code(), Code::InvalidArgument); } diff --git a/ampd/src/grpc/server/mod.rs b/ampd/src/grpc/server/mod.rs index e5695026d..bb7483c8a 100644 --- a/ampd/src/grpc/server/mod.rs +++ b/ampd/src/grpc/server/mod.rs @@ -1,10 +1,10 @@ -use tonic::transport::{server::Router, Server}; - -use crate::{ - event_sub::EventSub, queue::queued_broadcaster::BroadcasterClient, tofnd::grpc::Multisig, -}; +use tonic::transport::server::Router; +use tonic::transport::Server; use super::proto; +use crate::event_sub::EventSub; +use crate::queue::queued_broadcaster::BroadcasterClient; +use crate::tofnd::grpc::Multisig; mod ampd; mod crypto; diff --git a/ampd/src/handlers/config.rs b/ampd/src/handlers/config.rs index c42173ed1..2f71a9e66 100644 --- a/ampd/src/handlers/config.rs +++ b/ampd/src/handlers/config.rs @@ -1,6 +1,7 @@ use std::time::Duration; use itertools::Itertools; +use router_api::ChainName; use serde::de::{self, Deserializer}; use serde::{Deserialize, Serialize}; use serde_with::with_prefix; @@ -8,7 +9,6 @@ use serde_with::with_prefix; use crate::evm::finalizer::Finalization; use crate::types::TMAddress; use crate::url::Url; -use router_api::ChainName; #[derive(Debug, Deserialize, Serialize, PartialEq)] pub struct Chain { diff --git a/ampd/src/handlers/errors.rs b/ampd/src/handlers/errors.rs index 876912a3b..bd04d31ef 100644 --- a/ampd/src/handlers/errors.rs +++ b/ampd/src/handlers/errors.rs @@ -10,4 +10,6 @@ pub enum Error { Sign, #[error("failed to get transaction receipts")] TxReceipts, + #[error("unsupported key type {0}")] + KeyType(String), } diff --git a/ampd/src/handlers/evm_verify_msg.rs b/ampd/src/handlers/evm_verify_msg.rs index 5a8fc859a..6fe8ca131 100644 --- a/ampd/src/handlers/evm_verify_msg.rs +++ b/ampd/src/handlers/evm_verify_msg.rs @@ -2,21 +2,21 @@ use std::collections::{HashMap, HashSet}; use std::convert::TryInto; use async_trait::async_trait; +use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; +use axelar_wasm_std::voting::{PollId, Vote}; use cosmrs::cosmwasm::MsgExecuteContract; -use cosmrs::{tx::Msg, Any}; +use cosmrs::tx::Msg; +use cosmrs::Any; use error_stack::ResultExt; use ethers_core::types::{TransactionReceipt, U64}; +use events::Error::EventTypeMismatch; +use events_derive::try_from; use futures::future::join_all; +use router_api::ChainName; use serde::Deserialize; use tokio::sync::watch::Receiver; use tracing::{info, info_span}; use valuable::Valuable; - -use axelar_wasm_std::msg_id::tx_hash_event_index::HexTxHashAndEventIndex; -use axelar_wasm_std::voting::{PollId, Vote}; -use events::Error::EventTypeMismatch; -use events_derive::try_from; -use router_api::ChainName; use voting_verifier::msg::ExecuteMsg; use crate::event_processor::EventHandler; @@ -35,7 +35,7 @@ pub struct Message { pub tx_id: Hash, pub event_index: u32, pub destination_address: String, - pub destination_chain: router_api::ChainName, + pub destination_chain: ChainName, pub source_address: EVMAddress, pub payload_hash: Hash, } @@ -44,7 +44,7 @@ pub struct Message { #[try_from("wasm-messages_poll_started")] struct PollStartedEvent { poll_id: PollId, - source_chain: router_api::ChainName, + source_chain: ChainName, source_gateway_address: EVMAddress, confirmation_height: u64, expires_at: u64, @@ -231,24 +231,22 @@ mod tests { use cosmwasm_std; use error_stack::{Report, Result}; use ethers_providers::ProviderError; - use tendermint::abci; - use tokio::sync::watch; - use tokio::test as async_test; - use events::Error::{DeserializationFailed, EventTypeMismatch}; use events::Event; use router_api::ChainName; + use tendermint::abci; + use tokio::sync::watch; + use tokio::test as async_test; use voting_verifier::events::{PollMetadata, PollStarted, TxEventConfirmation}; + use super::PollStartedEvent; use crate::event_processor::EventHandler; use crate::evm::finalizer::Finalization; use crate::evm::json_rpc::MockEthereumClient; use crate::types::{EVMAddress, Hash, TMAddress}; use crate::PREFIX; - use super::PollStartedEvent; - - fn get_poll_started_event(participants: Vec, expires_at: u64) -> PollStarted { + fn poll_started_event(participants: Vec, expires_at: u64) -> PollStarted { PollStarted::Messages { metadata: PollMetadata { poll_id: "100".parse().unwrap(), @@ -295,8 +293,8 @@ mod tests { #[test] fn should_not_deserialize_incorrect_event() { // incorrect event type - let mut event: Event = get_event( - get_poll_started_event(participants(5, None), 100), + let mut event: Event = to_event( + poll_started_event(participants(5, None), 100), &TMAddress::random(PREFIX), ); match event { @@ -315,8 +313,8 @@ mod tests { )); // invalid field - let mut event: Event = get_event( - get_poll_started_event(participants(5, None), 100), + let mut event: Event = to_event( + poll_started_event(participants(5, None), 100), &TMAddress::random(PREFIX), ); match event { @@ -338,8 +336,8 @@ mod tests { #[test] fn should_deserialize_correct_event() { - let event: Event = get_event( - get_poll_started_event(participants(5, None), 100), + let event: Event = to_event( + poll_started_event(participants(5, None), 100), &TMAddress::random(PREFIX), ); let event: Result = event.try_into(); @@ -359,8 +357,8 @@ mod tests { let voting_verifier_contract = TMAddress::random(PREFIX); let verifier = TMAddress::random(PREFIX); let expiration = 100u64; - let event: Event = get_event( - get_poll_started_event(participants(5, Some(verifier.clone())), expiration), + let event: Event = to_event( + poll_started_event(participants(5, Some(verifier.clone())), expiration), &voting_verifier_contract, ); @@ -384,7 +382,7 @@ mod tests { assert_eq!(handler.handle(&event).await.unwrap(), vec![]); } - fn get_event(event: impl Into, contract_address: &TMAddress) -> Event { + fn to_event(event: impl Into, contract_address: &TMAddress) -> Event { let mut event: cosmwasm_std::Event = event.into(); event.ty = format!("wasm-{}", event.ty); diff --git a/ampd/src/handlers/evm_verify_verifier_set.rs b/ampd/src/handlers/evm_verify_verifier_set.rs index 72c2cd563..6097f85b9 100644 --- a/ampd/src/handlers/evm_verify_verifier_set.rs +++ b/ampd/src/handlers/evm_verify_verifier_set.rs @@ -1,29 +1,28 @@ use std::convert::TryInto; use async_trait::async_trait; +use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; +use axelar_wasm_std::voting::{PollId, Vote}; use cosmrs::cosmwasm::MsgExecuteContract; -use cosmrs::{tx::Msg, Any}; +use cosmrs::tx::Msg; +use cosmrs::Any; use error_stack::ResultExt; use ethers_core::types::{TransactionReceipt, U64}; +use events::Error::EventTypeMismatch; +use events_derive::try_from; use multisig::verifier_set::VerifierSet; +use router_api::ChainName; use serde::Deserialize; use tokio::sync::watch::Receiver; use tracing::{info, info_span}; use valuable::Valuable; - -use axelar_wasm_std::{ - msg_id::tx_hash_event_index::HexTxHashAndEventIndex, - voting::{PollId, Vote}, -}; -use events::Error::EventTypeMismatch; -use events_derive::try_from; -use router_api::ChainName; use voting_verifier::msg::ExecuteMsg; use crate::event_processor::EventHandler; +use crate::evm::finalizer; use crate::evm::finalizer::Finalization; +use crate::evm::json_rpc::EthereumClient; use crate::evm::verifier::verify_verifier_set; -use crate::evm::{finalizer, json_rpc::EthereumClient}; use crate::handlers::errors::Error; use crate::types::{EVMAddress, Hash, TMAddress}; @@ -41,7 +40,7 @@ pub struct VerifierSetConfirmation { struct PollStartedEvent { verifier_set: VerifierSetConfirmation, poll_id: PollId, - source_chain: router_api::ChainName, + source_chain: ChainName, source_gateway_address: EVMAddress, expires_at: u64, confirmation_height: u64, @@ -199,35 +198,32 @@ where #[cfg(test)] mod tests { - use std::{convert::TryInto, str::FromStr}; + use std::convert::TryInto; + use std::str::FromStr; use base64::engine::general_purpose::STANDARD; use base64::Engine; use error_stack::{Report, Result}; use ethers_providers::ProviderError; - - use tendermint::abci; - use tokio::{sync::watch, test as async_test}; - use events::Event; - use multisig::{ - key::KeyType, - test::common::{build_verifier_set, ecdsa_test_data}, - }; + use multisig::key::KeyType; + use multisig::test::common::{build_verifier_set, ecdsa_test_data}; use router_api::ChainName; + use tendermint::abci; + use tokio::sync::watch; + use tokio::test as async_test; use voting_verifier::events::{PollMetadata, PollStarted, VerifierSetConfirmation}; - use crate::{ - event_processor::EventHandler, - evm::{finalizer::Finalization, json_rpc::MockEthereumClient}, - handlers::evm_verify_verifier_set::PollStartedEvent, - types::{Hash, TMAddress}, - PREFIX, - }; + use crate::event_processor::EventHandler; + use crate::evm::finalizer::Finalization; + use crate::evm::json_rpc::MockEthereumClient; + use crate::handlers::evm_verify_verifier_set::PollStartedEvent; + use crate::types::{Hash, TMAddress}; + use crate::PREFIX; #[test] fn should_deserialize_correct_event() { - let event: Event = get_event( + let event: Event = to_event( poll_started_event(participants(5, None), 100), &TMAddress::random(PREFIX), ); @@ -249,7 +245,7 @@ mod tests { let voting_verifier = TMAddress::random(PREFIX); let verifier = TMAddress::random(PREFIX); let expiration = 100u64; - let event: Event = get_event( + let event: Event = to_event( poll_started_event(participants(5, Some(verifier.clone())), expiration), &voting_verifier, ); @@ -297,7 +293,7 @@ mod tests { } } - fn get_event(event: impl Into, contract_address: &TMAddress) -> Event { + fn to_event(event: impl Into, contract_address: &TMAddress) -> Event { let mut event: cosmwasm_std::Event = event.into(); event.ty = format!("wasm-{}", event.ty); diff --git a/ampd/src/handlers/mod.rs b/ampd/src/handlers/mod.rs index 2b3bcf961..32eb5e60e 100644 --- a/ampd/src/handlers/mod.rs +++ b/ampd/src/handlers/mod.rs @@ -19,7 +19,10 @@ mod tests { use crate::types::TMAddress; /// Convert a CosmWasm event into an ABCI event - pub fn get_event(event: impl Into, contract_address: &TMAddress) -> Event { + pub fn into_structured_event( + event: impl Into, + contract_address: &TMAddress, + ) -> Event { let mut event: cosmwasm_std::Event = event.into(); event.ty = format!("wasm-{}", event.ty); diff --git a/ampd/src/handlers/multisig.rs b/ampd/src/handlers/multisig.rs index 1540ab235..12e0ac448 100644 --- a/ampd/src/handlers/multisig.rs +++ b/ampd/src/handlers/multisig.rs @@ -3,27 +3,25 @@ use std::convert::TryInto; use async_trait::async_trait; use cosmrs::cosmwasm::MsgExecuteContract; -use cosmrs::{tx::Msg, Any}; +use cosmrs::tx::Msg; +use cosmrs::Any; use cosmwasm_std::{HexBinary, Uint64}; use ecdsa::VerifyingKey; -use error_stack::ResultExt; +use error_stack::{Report, ResultExt}; +use events_derive; +use events_derive::try_from; use hex::encode; +use multisig::msg::ExecuteMsg; use serde::de::Error as DeserializeError; use serde::{Deserialize, Deserializer}; use tokio::sync::watch::Receiver; use tracing::info; -use events::Error::EventTypeMismatch; -use events_derive; -use events_derive::try_from; -use multisig::msg::ExecuteMsg; - use crate::event_processor::EventHandler; use crate::handlers::errors::Error::{self, DeserializeEvent}; use crate::tofnd::grpc::Multisig; use crate::tofnd::{self, MessageDigest}; -use crate::types::PublicKey; -use crate::types::TMAddress; +use crate::types::{PublicKey, TMAddress}; #[derive(Debug, Deserialize)] #[try_from("wasm-signing_started")] @@ -127,7 +125,12 @@ where msg, expires_at, } = match event.try_into() as error_stack::Result<_, _> { - Err(report) if matches!(report.current_context(), EventTypeMismatch(_)) => { + Err(report) + if matches!( + report.current_context(), + events::Error::EventTypeMismatch(_) + ) => + { return Ok(vec![]); } result => result.change_context(DeserializeEvent)?, @@ -150,13 +153,19 @@ where match pub_keys.get(&self.verifier) { Some(pub_key) => { + let key_type = match pub_key.type_url() { + PublicKey::ED25519_TYPE_URL => tofnd::Algorithm::Ed25519, + PublicKey::SECP256K1_TYPE_URL => tofnd::Algorithm::Ecdsa, + unspported => return Err(Report::from(Error::KeyType(unspported.to_string()))), + }; + let signature = self .signer .sign( self.multisig.to_string().as_str(), msg.clone(), pub_key, - tofnd::Algorithm::Ecdsa, + key_type, ) .await .change_context(Error::Sign)?; @@ -189,6 +198,8 @@ mod test { use cosmwasm_std::{HexBinary, Uint64}; use ecdsa::SigningKey; use error_stack::{Report, Result}; + use multisig::events::Event; + use multisig::types::MsgToSign; use rand::distributions::Alphanumeric; use rand::rngs::OsRng; use rand::Rng; @@ -196,16 +207,10 @@ mod test { use tendermint::abci; use tokio::sync::watch; - use multisig::events::Event::SigningStarted; - use multisig::key::PublicKey; - use multisig::types::MsgToSign; - + use super::*; use crate::broadcaster::MockBroadcaster; - use crate::tofnd; use crate::tofnd::grpc::MockMultisig; - use crate::types; - - use super::*; + use crate::{tofnd, types}; const MULTISIG_ADDRESS: &str = "axelarvaloper1zh9wrak6ke4n6fclj5e8yk397czv430ygs5jz7"; @@ -230,7 +235,7 @@ mod test { fn rand_chain_name() -> ChainName { rand::thread_rng() .sample_iter(&Alphanumeric) - .take(32) + .take(10) .map(char::from) .collect::() .try_into() @@ -240,9 +245,9 @@ mod test { fn signing_started_event() -> events::Event { let pub_keys = (0..10) .map(|_| (rand_account().to_string(), rand_public_key())) - .collect::>(); + .collect::>(); - let poll_started = SigningStarted { + let poll_started = Event::SigningStarted { session_id: Uint64::one(), verifier_set_id: "verifier_set_id".to_string(), pub_keys, @@ -271,9 +276,9 @@ mod test { fn signing_started_event_with_missing_fields(contract_address: &str) -> events::Event { let pub_keys = (0..10) .map(|_| (rand_account().to_string(), rand_public_key())) - .collect::>(); + .collect::>(); - let poll_started = SigningStarted { + let poll_started = Event::SigningStarted { session_id: Uint64::one(), verifier_set_id: "verifier_set_id".to_string(), pub_keys, @@ -299,7 +304,7 @@ mod test { .unwrap() } - fn get_handler( + fn handler( verifier: TMAddress, multisig: TMAddress, signer: MockMultisig, @@ -340,10 +345,10 @@ mod test { let mut event = signing_started_event(); let invalid_pub_key: [u8; 32] = rand::random(); - let mut map: HashMap = HashMap::new(); + let mut map: HashMap = HashMap::new(); map.insert( rand_account().to_string(), - PublicKey::Ecdsa(HexBinary::from(invalid_pub_key.as_slice())), + multisig::key::PublicKey::Ecdsa(HexBinary::from(invalid_pub_key.as_slice())), ); match event { events::Event::Abci { @@ -374,7 +379,7 @@ mod test { async fn should_not_handle_event_with_missing_fields_if_multisig_address_does_not_match() { let client = MockMultisig::default(); - let handler = get_handler( + let handler = handler( rand_account(), TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), client, @@ -396,7 +401,7 @@ mod test { async fn should_error_on_event_with_missing_fields_if_multisig_address_does_match() { let client = MockMultisig::default(); - let handler = get_handler( + let handler = handler( rand_account(), TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), client, @@ -413,7 +418,7 @@ mod test { async fn should_not_handle_event_if_multisig_address_does_not_match() { let client = MockMultisig::default(); - let handler = get_handler(rand_account(), rand_account(), client, 100u64); + let handler = handler(rand_account(), rand_account(), client, 100u64); assert_eq!( handler.handle(&signing_started_event()).await.unwrap(), @@ -428,7 +433,7 @@ mod test { .expect_sign() .returning(move |_, _, _, _| Err(Report::from(tofnd::error::Error::SignFailed))); - let handler = get_handler( + let handler = handler( rand_account(), TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), client, @@ -451,7 +456,7 @@ mod test { let event = signing_started_event(); let signing_started: SigningStartedEvent = ((&event).try_into() as Result<_, _>).unwrap(); let verifier = signing_started.pub_keys.keys().next().unwrap().clone(); - let handler = get_handler( + let handler = handler( verifier, TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), client, @@ -474,7 +479,7 @@ mod test { let event = signing_started_event(); let signing_started: SigningStartedEvent = ((&event).try_into() as Result<_, _>).unwrap(); let verifier = signing_started.pub_keys.keys().next().unwrap().clone(); - let handler = get_handler( + let handler = handler( verifier, TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), client, diff --git a/ampd/src/handlers/sui_verify_msg.rs b/ampd/src/handlers/sui_verify_msg.rs index 86fd3838f..3d60cb2b0 100644 --- a/ampd/src/handlers/sui_verify_msg.rs +++ b/ampd/src/handlers/sui_verify_msg.rs @@ -2,22 +2,24 @@ use std::collections::HashSet; use std::convert::TryInto; use async_trait::async_trait; +use axelar_wasm_std::voting::{PollId, Vote}; use cosmrs::cosmwasm::MsgExecuteContract; -use cosmrs::{tx::Msg, Any}; +use cosmrs::tx::Msg; +use cosmrs::Any; use error_stack::ResultExt; +use events::Error::EventTypeMismatch; +use events::Event; +use events_derive::try_from; use serde::Deserialize; use sui_types::base_types::{SuiAddress, TransactionDigest}; use tokio::sync::watch::Receiver; use tracing::info; - -use axelar_wasm_std::voting::{PollId, Vote}; -use events::{Error::EventTypeMismatch, Event}; -use events_derive::try_from; use voting_verifier::msg::ExecuteMsg; use crate::event_processor::EventHandler; use crate::handlers::errors::Error; -use crate::sui::{json_rpc::SuiClient, verifier::verify_message}; +use crate::sui::json_rpc::SuiClient; +use crate::sui::verifier::verify_message; use crate::types::{Hash, TMAddress}; type Result = error_stack::Result; @@ -155,25 +157,24 @@ mod tests { use cosmwasm_std; use error_stack::{Report, Result}; use ethers_providers::ProviderError; + use events::Event; use sui_types::base_types::{SuiAddress, TransactionDigest}; use tokio::sync::watch; use tokio::test as async_test; - - use events::Event; use voting_verifier::events::{PollMetadata, PollStarted, TxEventConfirmation}; + use super::PollStartedEvent; use crate::event_processor::EventHandler; - use crate::handlers::{errors::Error, tests::get_event}; + use crate::handlers::errors::Error; + use crate::handlers::tests::into_structured_event; use crate::sui::json_rpc::MockSuiClient; use crate::types::{EVMAddress, Hash, TMAddress}; - use super::PollStartedEvent; - const PREFIX: &str = "axelar"; #[test] fn should_deserialize_poll_started_event() { - let event: Result = get_event( + let event: Result = into_structured_event( poll_started_event(participants(5, None), 100), &TMAddress::random(PREFIX), ) @@ -185,7 +186,7 @@ mod tests { // Should not handle event if it is not a poll started event #[async_test] async fn not_poll_started_event() { - let event = get_event( + let event = into_structured_event( cosmwasm_std::Event::new("transfer"), &TMAddress::random(PREFIX), ); @@ -203,7 +204,7 @@ mod tests { // Should not handle event if it is not emitted from voting verifier #[async_test] async fn contract_is_not_voting_verifier() { - let event = get_event( + let event = into_structured_event( poll_started_event(participants(5, None), 100), &TMAddress::random(PREFIX), ); @@ -222,7 +223,7 @@ mod tests { #[async_test] async fn verifier_is_not_a_participant() { let voting_verifier = TMAddress::random(PREFIX); - let event = get_event( + let event = into_structured_event( poll_started_event(participants(5, None), 100), &voting_verifier, ); @@ -251,7 +252,7 @@ mod tests { let voting_verifier = TMAddress::random(PREFIX); let verifier = TMAddress::random(PREFIX); - let event = get_event( + let event = into_structured_event( poll_started_event(participants(5, Some(verifier.clone())), 100), &voting_verifier, ); @@ -274,7 +275,7 @@ mod tests { let voting_verifier = TMAddress::random(PREFIX); let verifier = TMAddress::random(PREFIX); - let event = get_event( + let event = into_structured_event( poll_started_event(participants(5, Some(verifier.clone())), 100), &voting_verifier, ); @@ -302,7 +303,7 @@ mod tests { let voting_verifier = TMAddress::random(PREFIX); let verifier = TMAddress::random(PREFIX); let expiration = 100u64; - let event: Event = get_event( + let event: Event = into_structured_event( poll_started_event(participants(5, Some(verifier.clone())), expiration), &voting_verifier, ); diff --git a/ampd/src/handlers/sui_verify_verifier_set.rs b/ampd/src/handlers/sui_verify_verifier_set.rs index 71c9d70d9..4a159c916 100644 --- a/ampd/src/handlers/sui_verify_verifier_set.rs +++ b/ampd/src/handlers/sui_verify_verifier_set.rs @@ -1,22 +1,21 @@ use std::convert::TryInto; use async_trait::async_trait; +use axelar_wasm_std::msg_id::Base58TxDigestAndEventIndex; +use axelar_wasm_std::voting::{PollId, Vote}; use cosmrs::cosmwasm::MsgExecuteContract; -use cosmrs::{tx::Msg, Any}; -use cosmwasm_std::HexBinary; -use cosmwasm_std::Uint128; +use cosmrs::tx::Msg; +use cosmrs::Any; use error_stack::ResultExt; +use events::Error::EventTypeMismatch; +use events::Event; +use events_derive::try_from; use multisig::verifier_set::VerifierSet; use serde::Deserialize; use sui_types::base_types::{SuiAddress, TransactionDigest}; use tokio::sync::watch::Receiver; use tracing::{info, info_span}; use valuable::Valuable; - -use axelar_wasm_std::msg_id::base_58_event_index::Base58TxDigestAndEventIndex; -use axelar_wasm_std::voting::{PollId, Vote}; -use events::{Error::EventTypeMismatch, Event}; -use events_derive::try_from; use voting_verifier::msg::ExecuteMsg; use crate::event_processor::EventHandler; @@ -25,13 +24,6 @@ use crate::sui::json_rpc::SuiClient; use crate::sui::verifier::verify_verifier_set; use crate::types::TMAddress; -#[allow(dead_code)] -#[derive(Deserialize, Debug)] -pub struct Operators { - pub weights_by_addresses: Vec<(HexBinary, Uint128)>, - pub threshold: Uint128, -} - #[derive(Deserialize, Debug)] pub struct VerifierSetConfirmation { pub tx_id: TransactionDigest, @@ -165,29 +157,26 @@ mod tests { use error_stack::{Report, Result}; use ethers_providers::ProviderError; + use events::Event; + use multisig::key::KeyType; + use multisig::test::common::{build_verifier_set, ecdsa_test_data}; use sui_types::base_types::{SuiAddress, TransactionDigest}; use tokio::sync::watch; use tokio::test as async_test; - - use events::Event; - use multisig::{ - key::KeyType, - test::common::{build_verifier_set, ecdsa_test_data}, - }; use voting_verifier::events::{PollMetadata, PollStarted, VerifierSetConfirmation}; + use super::PollStartedEvent; use crate::event_processor::EventHandler; + use crate::handlers::tests::into_structured_event; use crate::sui::json_rpc::MockSuiClient; + use crate::types::TMAddress; use crate::PREFIX; - use crate::{handlers::tests::get_event, types::TMAddress}; - - use super::PollStartedEvent; #[test] fn should_deserialize_verifier_set_poll_started_event() { let participants = (0..5).map(|_| TMAddress::random(PREFIX)).collect(); - let event: Result = get_event( + let event: Result = into_structured_event( verifier_set_poll_started_event(participants, 100), &TMAddress::random(PREFIX), ) @@ -211,7 +200,7 @@ mod tests { let voting_verifier = TMAddress::random(PREFIX); let verifier = TMAddress::random(PREFIX); let expiration = 100u64; - let event: Event = get_event( + let event: Event = into_structured_event( verifier_set_poll_started_event( vec![verifier.clone()].into_iter().collect(), expiration, diff --git a/ampd/src/health_check.rs b/ampd/src/health_check.rs index ffd366fc6..702de7598 100644 --- a/ampd/src/health_check.rs +++ b/ampd/src/health_check.rs @@ -1,11 +1,13 @@ -use error_stack::{Result, ResultExt}; use std::net::SocketAddrV4; -use thiserror::Error; -use tracing::info; -use axum::{http::StatusCode, routing::get, Json, Router}; +use axum::http::StatusCode; +use axum::routing::get; +use axum::{Json, Router}; +use error_stack::{Result, ResultExt}; use serde::{Deserialize, Serialize}; +use thiserror::Error; use tokio_util::sync::CancellationToken; +use tracing::info; #[derive(Error, Debug)] pub enum Error { @@ -55,11 +57,13 @@ struct Status { #[cfg(test)] mod tests { - use super::*; use std::net::{SocketAddr, TcpListener}; use std::time::Duration; + use tokio::test as async_test; + use super::*; + #[async_test] async fn server_lifecycle() { let bind_address = test_bind_addr(); diff --git a/ampd/src/json_rpc.rs b/ampd/src/json_rpc.rs index 20a76f69e..38df818db 100644 --- a/ampd/src/json_rpc.rs +++ b/ampd/src/json_rpc.rs @@ -2,7 +2,8 @@ use std::fmt::Debug; use error_stack::Report; use ethers_providers::{Http, JsonRpcClient, ProviderError}; -use serde::{de::DeserializeOwned, Serialize}; +use serde::de::DeserializeOwned; +use serde::Serialize; use crate::url::Url; diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index c8646a409..257d857eb 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -1,14 +1,17 @@ use std::time::Duration; +use asyncutil::task::{CancellableTask, TaskError, TaskGroup}; use block_height_monitor::BlockHeightMonitor; -use cosmrs::proto::cosmos::{ - auth::v1beta1::query_client::QueryClient as AuthQueryClient, - bank::v1beta1::query_client::QueryClient as BankQueryClient, - tx::v1beta1::service_client::ServiceClient, -}; +use broadcaster::Broadcaster; +use cosmrs::proto::cosmos::auth::v1beta1::query_client::QueryClient as AuthQueryClient; +use cosmrs::proto::cosmos::bank::v1beta1::query_client::QueryClient as BankQueryClient; +use cosmrs::proto::cosmos::tx::v1beta1::service_client::ServiceClient; use error_stack::{FutureExt, Result, ResultExt}; +use event_processor::EventHandler; +use event_sub::EventSub; use evm::finalizer::{pick, Finalization}; use evm::json_rpc::EthereumClient; +use queue::queued_broadcaster::QueuedBroadcaster; use router_api::ChainName; use starknet_providers::jsonrpc::HttpTransport; use thiserror::Error; @@ -17,12 +20,6 @@ use tokio::signal::unix::{signal, SignalKind}; use tokio::time::interval; use tokio_util::sync::CancellationToken; use tracing::info; - -use asyncutil::task::{CancellableTask, TaskError, TaskGroup}; -use broadcaster::Broadcaster; -use event_processor::EventHandler; -use event_sub::EventSub; -use queue::queued_broadcaster::QueuedBroadcaster; use types::TMAddress; use crate::config::Config; @@ -41,7 +38,7 @@ mod handlers; mod health_check; mod json_rpc; mod queue; -pub mod starknet; +mod starknet; mod sui; mod tm_client; mod tofnd; @@ -64,8 +61,7 @@ async fn prepare_app(cfg: Config) -> Result, Error> { broadcast, handlers, tofnd_config, - event_buffer_cap, - event_stream_timeout, + event_processor, service_registry: _service_registry, health_check_bind_addr, } = cfg; @@ -119,11 +115,11 @@ async fn prepare_app(cfg: Config) -> Result, Error> { broadcaster, multisig_client, broadcast, - event_buffer_cap, + event_processor.stream_buffer_size, block_height_monitor, health_check_server, ) - .configure_handlers(verifier, handlers, event_stream_timeout) + .configure_handlers(verifier, handlers, event_processor) .await } @@ -200,7 +196,7 @@ where mut self, verifier: TMAddress, handler_configs: Vec, - stream_timeout: Duration, + event_processor_config: event_processor::Config, ) -> Result, Error> { for config in handler_configs { let task = match config { @@ -230,7 +226,7 @@ where rpc_client, self.block_height_monitor.latest_block_height(), ), - stream_timeout, + event_processor_config.clone(), ) } handlers::config::Config::EvmVerifierSetVerifier { @@ -259,7 +255,7 @@ where rpc_client, self.block_height_monitor.latest_block_height(), ), - stream_timeout, + event_processor_config.clone(), ) } handlers::config::Config::MultisigSigner { cosmwasm_contract } => self @@ -271,7 +267,7 @@ where self.multisig_client.clone(), self.block_height_monitor.latest_block_height(), ), - stream_timeout, + event_processor_config.clone(), ), handlers::config::Config::SuiMsgVerifier { cosmwasm_contract, @@ -292,7 +288,7 @@ where ), self.block_height_monitor.latest_block_height(), ), - stream_timeout, + event_processor_config.clone(), ), handlers::config::Config::SuiVerifierSetVerifier { cosmwasm_contract, @@ -313,7 +309,24 @@ where ), self.block_height_monitor.latest_block_height(), ), - stream_timeout, + event_processor_config.clone(), + ), + handlers::config::Config::StarknetMsgVerifier { + cosmwasm_contract, + rpc_url, + rpc_timeout: _, + } => self.create_handler_task( + "starknet-msg-verifier", + handlers::starknet_verify_msg::Handler::new( + verifier.clone(), + cosmwasm_contract, + starknet::json_rpc::Client::new_with_transport(HttpTransport::new( + &rpc_url, + )) + .unwrap(), + self.block_height_monitor.latest_block_height(), + ), + event_processor_config.clone(), ), handlers::config::Config::StarknetMsgVerifier { cosmwasm_contract, @@ -330,7 +343,7 @@ where .unwrap(), self.block_height_monitor.latest_block_height(), ), - stream_timeout, + event_processor_config.clone(), ), }; self.event_processor = self.event_processor.add_task(task); @@ -343,7 +356,7 @@ where &mut self, label: L, handler: H, - stream_timeout: Duration, + event_processor_config: event_processor::Config, ) -> CancellableTask> where L: AsRef, @@ -354,7 +367,14 @@ where let sub = self.event_subscriber.subscribe(); CancellableTask::create(move |token| { - event_processor::consume_events(label, handler, broadcaster, sub, stream_timeout, token) + event_processor::consume_events( + label, + handler, + broadcaster, + sub, + event_processor_config, + token, + ) }) } diff --git a/ampd/src/main.rs b/ampd/src/main.rs index a15b409a1..d5881bfb5 100644 --- a/ampd/src/main.rs +++ b/ampd/src/main.rs @@ -4,12 +4,6 @@ use std::path::{Path, PathBuf}; use std::process::ExitCode; use ::config::{Config as cfg, Environment, File, FileFormat, FileSourceFile}; -use clap::{arg, command, Parser, ValueEnum}; -use config::ConfigError; -use error_stack::{Report, ResultExt}; -use tracing::{error, info}; -use valuable::Valuable; - use ampd::commands::{ bond_verifier, daemon, deregister_chain_support, register_chain_support, register_public_key, verifier_address, SubCommand, @@ -17,7 +11,12 @@ use ampd::commands::{ use ampd::config::Config; use ampd::Error; use axelar_wasm_std::FnExt; +use clap::{arg, command, Parser, ValueEnum}; +use config::ConfigError; +use error_stack::{Report, ResultExt}; use report::LoggableError; +use tracing::{error, info}; +use valuable::Valuable; #[derive(Debug, Parser, Valuable)] #[command(version)] diff --git a/ampd/src/queue/msg_queue.rs b/ampd/src/queue/msg_queue.rs index 5f4460da5..d12527e2e 100644 --- a/ampd/src/queue/msg_queue.rs +++ b/ampd/src/queue/msg_queue.rs @@ -44,8 +44,9 @@ impl MsgQueue { #[cfg(test)] mod test { - use cosmrs::Any; - use cosmrs::{bank::MsgSend, tx::Msg, AccountId}; + use cosmrs::bank::MsgSend; + use cosmrs::tx::Msg; + use cosmrs::{AccountId, Any}; use super::MsgQueue; diff --git a/ampd/src/queue/queued_broadcaster.rs b/ampd/src/queue/queued_broadcaster.rs index fa65d214d..b7d34f4a4 100644 --- a/ampd/src/queue/queued_broadcaster.rs +++ b/ampd/src/queue/queued_broadcaster.rs @@ -6,8 +6,7 @@ use thiserror::Error; use tokio::select; use tokio::sync::{mpsc, oneshot}; use tokio::time::Interval; -use tracing::info; -use tracing::warn; +use tracing::{info, warn}; use super::msg_queue::MsgQueue; use crate::broadcaster::Broadcaster; @@ -172,13 +171,15 @@ where #[cfg(test)] mod test { + use cosmrs::bank::MsgSend; use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; - use cosmrs::tx::Fee; - use cosmrs::Any; - use cosmrs::{bank::MsgSend, tx::Msg, AccountId}; + use cosmrs::tx::{Fee, Msg}; + use cosmrs::{AccountId, Any}; use error_stack::Report; + use tokio::sync::mpsc; use tokio::test; - use tokio::time::{interval, Duration}; + use tokio::time::{interval, timeout, Duration, Instant}; + use super::{Error, QueuedBroadcaster}; use crate::broadcaster::{self, MockBroadcaster}; @@ -210,10 +211,13 @@ mod test { } #[test(start_paused = true)] - async fn should_not_broadcast_when_gas_limit_has_not_been_reached() { - let tx_count = 9; + async fn should_broadcast_after_interval_in_low_load() { + let tx_count = 5; // Less than what would exceed batch_gas_limit let batch_gas_limit = 100; let gas_limit = 10; + let interval_duration = Duration::from_secs(5); + + let (tx, mut rx) = mpsc::channel(5); let mut broadcaster = MockBroadcaster::new(); broadcaster @@ -227,55 +231,76 @@ mod test { payer: None, }) }); + broadcaster .expect_broadcast() - .once() + .times(1) .returning(move |msgs| { assert_eq!(msgs.len(), tx_count); - + tx.try_send(()) + .expect("Failed to send broadcast completion signal"); Ok(TxResponse::default()) }); - let mut broadcast_interval = interval(Duration::from_secs(5)); - // get rid of tick on startup + let mut broadcast_interval = interval(interval_duration); + broadcast_interval.tick().await; let queued_broadcaster = QueuedBroadcaster::new(broadcaster, batch_gas_limit, tx_count, broadcast_interval); let client = queued_broadcaster.client(); - let handle = tokio::spawn(queued_broadcaster.run()); + let _handle = tokio::spawn(queued_broadcaster.run()); + + let start_time = Instant::now(); for _ in 0..tx_count { client.broadcast(dummy_msg()).await.unwrap(); } - drop(client); - assert!(handle.await.unwrap().is_ok()); + // Advance time to just after one interval + tokio::time::advance(interval_duration + Duration::from_millis(10)).await; + + match timeout(interval_duration, rx.recv()).await { + Ok(_) => { + let elapsed = start_time.elapsed(); + assert!(elapsed > interval_duration); + assert!(elapsed < interval_duration * 2); + } + Err(_) => panic!("Broadcast did not occur within the expected timeframe"), + } } #[test(start_paused = true)] - async fn should_broadcast_when_broadcast_interval_has_been_reached() { - let tx_count = 9; + async fn should_broadcast_full_batches_in_high_load() { + let tx_count = 20; + let batch_size = 10; let batch_gas_limit = 100; - let gas_limit = 10; + let gas_limit = 11; // This will cause a batch to be full after 9 messages + let interval_duration = Duration::from_secs(5); let mut broadcaster = MockBroadcaster::new(); + broadcaster.expect_estimate_fee().returning(move |_| { + Ok(Fee { + gas_limit, + amount: vec![], + granter: None, + payer: None, + }) + }); broadcaster - .expect_estimate_fee() - .times(tx_count) - .returning(move |_| { - Ok(Fee { - gas_limit, - amount: vec![], - granter: None, - payer: None, - }) + .expect_broadcast() + .once() + .returning(move |msgs| { + assert_eq!(msgs.len(), 9); + + Ok(TxResponse::default()) }); broadcaster .expect_broadcast() .once() .returning(move |msgs| { - assert_eq!(msgs.len(), tx_count); + assert_eq!(msgs.len(), 9); + Ok(TxResponse::default()) }); @@ -283,17 +308,27 @@ mod test { // get rid of tick on startup broadcast_interval.tick().await; + let mut broadcast_interval = interval(interval_duration); + broadcast_interval.tick().await; + let queued_broadcaster = - QueuedBroadcaster::new(broadcaster, batch_gas_limit, tx_count, broadcast_interval); + QueuedBroadcaster::new(broadcaster, batch_gas_limit, batch_size, broadcast_interval); let client = queued_broadcaster.client(); - let handle = tokio::spawn(queued_broadcaster.run()); + let _handle = tokio::spawn(queued_broadcaster.run()); + + let start_time = Instant::now(); for _ in 0..tx_count { client.broadcast(dummy_msg()).await.unwrap(); } - drop(client); - assert!(handle.await.unwrap().is_ok()); + // Advance time by a small amount to allow processing + tokio::time::advance(Duration::from_millis(100)).await; + + let elapsed = start_time.elapsed(); + + // Assert that broadcasts happened faster than the interval + assert!(elapsed < interval_duration); } #[test(start_paused = true)] diff --git a/ampd/src/sui/verifier.rs b/ampd/src/sui/verifier.rs index b31634685..d088b2125 100644 --- a/ampd/src/sui/verifier.rs +++ b/ampd/src/sui/verifier.rs @@ -1,6 +1,11 @@ +use std::collections::HashMap; + use axelar_wasm_std::voting::Vote; +use axelar_wasm_std::{self}; +use cosmwasm_std::HexBinary; use move_core_types::language_storage::StructTag; -use serde::Deserialize; +use serde::de::Error; +use serde::{Deserialize, Deserializer}; use sui_json_rpc_types::{SuiEvent, SuiTransactionBlockResponse}; use sui_types::base_types::SuiAddress; @@ -8,6 +13,56 @@ use crate::handlers::sui_verify_msg::Message; use crate::handlers::sui_verify_verifier_set::VerifierSetConfirmation; use crate::types::Hash; +fn deserialize_from_str<'de, D, R>(deserializer: D) -> Result +where + D: Deserializer<'de>, + R: std::str::FromStr, + R::Err: std::fmt::Display, +{ + let string: String = Deserialize::deserialize(deserializer)?; + + R::from_str(&string).map_err(Error::custom) +} + +fn deserialize_sui_bytes<'de, D, const LENGTH: usize>( + deserializer: D, +) -> Result<[u8; LENGTH], D::Error> +where + D: Deserializer<'de>, +{ + let bytes: HashMap = Deserialize::deserialize(deserializer)?; + let hex = bytes + .get("bytes") + .ok_or_else(|| Error::custom("missing bytes"))? + .trim_start_matches("0x"); + + hex::decode(hex) + .map_err(Error::custom)? + .try_into() + .map_err(|_| Error::custom(format!("failed deserialize into [u8; {}]", LENGTH))) +} + +#[derive(Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct WeightedSigner { + pub_key: Vec, + #[serde(deserialize_with = "deserialize_from_str")] + weight: u128, +} + +#[derive(Deserialize, Debug)] +struct WeightedSigners { + signers: Vec, + #[serde(deserialize_with = "deserialize_from_str")] + threshold: u128, + #[serde(deserialize_with = "deserialize_sui_bytes")] + nonce: [u8; 32], +} + +#[derive(Deserialize, Debug)] +struct SignersRotated { + signers: WeightedSigners, +} + #[derive(Deserialize)] struct ContractCall { pub source_id: SuiAddress, @@ -16,15 +71,9 @@ struct ContractCall { pub payload_hash: Hash, } -#[derive(Deserialize)] -struct OperatorshipTransferred { - #[allow(dead_code)] - pub payload: Vec, -} - enum EventType { ContractCall, - OperatorshipTransferred, + SignersRotated, } impl EventType { @@ -32,12 +81,12 @@ impl EventType { fn struct_tag(&self, gateway_address: &SuiAddress) -> StructTag { let event = match self { EventType::ContractCall => "ContractCall", - EventType::OperatorshipTransferred => "OperatorshipTransferred", + EventType::SignersRotated => "SignersRotated", }; let module = match self { EventType::ContractCall => "gateway", - EventType::OperatorshipTransferred => "validators", + EventType::SignersRotated => "auth", }; format!("{}::{}::{}", gateway_address, module, event) @@ -49,11 +98,16 @@ impl EventType { impl PartialEq<&Message> for &SuiEvent { fn eq(&self, msg: &&Message) -> bool { match serde_json::from_value::(self.parsed_json.clone()) { - Ok(contract_call) => { - contract_call.source_id == msg.source_address - && msg.destination_chain == contract_call.destination_chain - && contract_call.destination_address == msg.destination_address - && contract_call.payload_hash == msg.payload_hash + Ok(ContractCall { + source_id, + destination_chain, + destination_address, + payload_hash, + }) => { + msg.source_address == source_id + && msg.destination_chain == destination_chain + && msg.destination_address == destination_address + && msg.payload_hash == payload_hash } _ => false, } @@ -61,11 +115,38 @@ impl PartialEq<&Message> for &SuiEvent { } impl PartialEq<&VerifierSetConfirmation> for &SuiEvent { - fn eq(&self, _verifier_set: &&VerifierSetConfirmation) -> bool { - match serde_json::from_value::(self.parsed_json.clone()) { - Ok(_event) => { - // TODO: convert verifier set to Sui gateway V2 WeightedSigners struct - todo!() + fn eq(&self, verifier_set: &&VerifierSetConfirmation) -> bool { + let expected = &verifier_set.verifier_set; + + let mut expected_signers = expected + .signers + .values() + .map(|signer| WeightedSigner { + pub_key: HexBinary::from(signer.pub_key.clone()).to_vec(), + weight: signer.weight.u128(), + }) + .collect::>(); + expected_signers.sort(); + + let expected_created_at = [0u8; 24] + .into_iter() + .chain(expected.created_at.to_be_bytes()) + .collect::>(); + + match serde_json::from_value::(self.parsed_json.clone()) { + Ok(SignersRotated { + signers: + WeightedSigners { + mut signers, + threshold, + nonce, + }, + }) => { + signers.sort(); + + signers == expected_signers + && threshold == expected.threshold.u128() + && nonce.as_slice() == expected_created_at.as_slice() } _ => false, } @@ -109,8 +190,7 @@ pub fn verify_verifier_set( match find_event(transaction_block, confirmation.event_index as u64) { Some(event) if transaction_block.digest == confirmation.tx_id - && event.type_ - == EventType::OperatorshipTransferred.struct_tag(gateway_address) + && event.type_ == EventType::SignersRotated.struct_tag(gateway_address) && event == confirmation => { Vote::SucceededOnChain @@ -121,31 +201,32 @@ pub fn verify_verifier_set( #[cfg(test)] mod tests { - use std::collections::BTreeMap; - - use cosmwasm_std::Uint128; + use axelar_wasm_std::voting::Vote; + use cosmrs::crypto::PublicKey; + use cosmwasm_std::{Addr, HexBinary, Uint128}; + use ecdsa::SigningKey; use ethers_core::abi::AbiEncode; use move_core_types::language_storage::StructTag; - use random_string::generate; - use sui_json_rpc_types::{SuiEvent, SuiTransactionBlockEvents, SuiTransactionBlockResponse}; - use sui_types::{ - base_types::{SuiAddress, TransactionDigest}, - event::EventID, - }; - - use axelar_wasm_std::voting::Vote; + use multisig::key::KeyType; + use multisig::msg::Signer; use multisig::verifier_set::VerifierSet; + use rand::rngs::OsRng; + use random_string::generate; use router_api::ChainName; + use serde_json::json; + use sui_json_rpc_types::{SuiEvent, SuiTransactionBlockEvents, SuiTransactionBlockResponse}; + use sui_types::base_types::{SuiAddress, TransactionDigest}; + use sui_types::event::EventID; - use crate::handlers::{ - sui_verify_msg::Message, sui_verify_verifier_set::VerifierSetConfirmation, - }; + use crate::handlers::sui_verify_msg::Message; + use crate::handlers::sui_verify_verifier_set::VerifierSetConfirmation; use crate::sui::verifier::{verify_message, verify_verifier_set}; use crate::types::{EVMAddress, Hash}; + use crate::PREFIX; #[test] fn should_not_verify_msg_if_tx_id_does_not_match() { - let (gateway_address, tx_receipt, mut msg) = get_matching_msg_and_tx_block(); + let (gateway_address, tx_receipt, mut msg) = matching_msg_and_tx_block(); msg.tx_id = TransactionDigest::random(); assert_eq!( @@ -156,7 +237,7 @@ mod tests { #[test] fn should_not_verify_msg_if_event_index_does_not_match() { - let (gateway_address, tx_receipt, mut msg) = get_matching_msg_and_tx_block(); + let (gateway_address, tx_receipt, mut msg) = matching_msg_and_tx_block(); msg.event_index = rand::random::(); assert_eq!( @@ -167,7 +248,7 @@ mod tests { #[test] fn should_not_verify_msg_if_source_address_does_not_match() { - let (gateway_address, tx_receipt, mut msg) = get_matching_msg_and_tx_block(); + let (gateway_address, tx_receipt, mut msg) = matching_msg_and_tx_block(); msg.source_address = SuiAddress::random_for_testing_only(); assert_eq!( @@ -178,7 +259,7 @@ mod tests { #[test] fn should_not_verify_msg_if_destination_chain_does_not_match() { - let (gateway_address, tx_receipt, mut msg) = get_matching_msg_and_tx_block(); + let (gateway_address, tx_receipt, mut msg) = matching_msg_and_tx_block(); msg.destination_chain = rand_chain_name(); assert_eq!( @@ -189,7 +270,7 @@ mod tests { #[test] fn should_not_verify_msg_if_destination_address_does_not_match() { - let (gateway_address, tx_receipt, mut msg) = get_matching_msg_and_tx_block(); + let (gateway_address, tx_receipt, mut msg) = matching_msg_and_tx_block(); msg.destination_address = EVMAddress::random().to_string(); assert_eq!( @@ -200,7 +281,7 @@ mod tests { #[test] fn should_not_verify_msg_if_payload_hash_does_not_match() { - let (gateway_address, tx_receipt, mut msg) = get_matching_msg_and_tx_block(); + let (gateway_address, tx_receipt, mut msg) = matching_msg_and_tx_block(); msg.payload_hash = Hash::random(); assert_eq!( @@ -211,25 +292,120 @@ mod tests { #[test] fn should_verify_msg_if_correct() { - let (gateway_address, tx_block, msg) = get_matching_msg_and_tx_block(); + let (gateway_address, tx_block, msg) = matching_msg_and_tx_block(); assert_eq!( verify_message(&gateway_address, &tx_block, &msg), Vote::SucceededOnChain ); } - #[ignore = "TODO: remove ignore once integrated with Sui gateway v2"] #[test] - fn should_verify_verifier_set() { - let (gateway_address, tx_receipt, verifier_set) = get_matching_verifier_set_and_tx_block(); + fn should_verify_verifier_set_if_correct() { + let (gateway_address, tx_block, verifier_set) = matching_verifier_set_and_tx_block(); assert_eq!( - verify_verifier_set(&gateway_address, &tx_receipt, &verifier_set), + verify_verifier_set(&gateway_address, &tx_block, &verifier_set), Vote::SucceededOnChain ); } - fn get_matching_msg_and_tx_block() -> (SuiAddress, SuiTransactionBlockResponse, Message) { + #[test] + fn should_not_verify_verifier_set_if_gateway_address_mismatch() { + let (_, tx_block, verifier_set) = matching_verifier_set_and_tx_block(); + + assert_eq!( + verify_verifier_set( + &SuiAddress::random_for_testing_only(), + &tx_block, + &verifier_set + ), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_verifier_set_if_tx_digest_mismatch() { + let (gateway_address, mut tx_block, verifier_set) = matching_verifier_set_and_tx_block(); + tx_block.digest = TransactionDigest::random(); + + assert_eq!( + verify_verifier_set(&gateway_address, &tx_block, &verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_verifier_set_if_event_seq_mismatch() { + let (gateway_address, tx_block, mut verifier_set) = matching_verifier_set_and_tx_block(); + verifier_set.event_index = rand::random(); + + assert_eq!( + verify_verifier_set(&gateway_address, &tx_block, &verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_verifier_set_if_struct_tag_mismatch() { + let (gateway_address, mut tx_block, verifier_set) = matching_verifier_set_and_tx_block(); + tx_block + .events + .as_mut() + .unwrap() + .data + .first_mut() + .unwrap() + .type_ = StructTag { + address: SuiAddress::random_for_testing_only().into(), + module: "module".parse().unwrap(), + name: "Name".parse().unwrap(), + type_params: vec![], + }; + + assert_eq!( + verify_verifier_set(&gateway_address, &tx_block, &verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_verifier_set_if_threshold_mismatch() { + let (gateway_address, tx_block, mut verifier_set) = matching_verifier_set_and_tx_block(); + verifier_set.verifier_set.threshold = Uint128::new(2); + + assert_eq!( + verify_verifier_set(&gateway_address, &tx_block, &verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_verifier_set_if_nonce_mismatch() { + let (gateway_address, tx_block, mut verifier_set) = matching_verifier_set_and_tx_block(); + verifier_set.verifier_set.created_at = rand::random(); + + assert_eq!( + verify_verifier_set(&gateway_address, &tx_block, &verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_verifier_set_if_signers_mismatch() { + let (gateway_address, tx_block, mut verifier_set) = matching_verifier_set_and_tx_block(); + let signer = random_signer(); + verifier_set + .verifier_set + .signers + .insert(signer.address.to_string(), signer); + + assert_eq!( + verify_verifier_set(&gateway_address, &tx_block, &verifier_set), + Vote::NotFound + ); + } + + fn matching_msg_and_tx_block() -> (SuiAddress, SuiTransactionBlockResponse, Message) { let gateway_address = SuiAddress::random_for_testing_only(); let msg = Message { @@ -279,28 +455,62 @@ mod tests { (gateway_address, tx_block, msg) } - fn get_matching_verifier_set_and_tx_block() -> ( + fn random_signer() -> Signer { + let priv_key = SigningKey::random(&mut OsRng); + let pub_key: PublicKey = priv_key.verifying_key().into(); + let address = Addr::unchecked(pub_key.account_id(PREFIX).unwrap()); + let pub_key = (KeyType::Ecdsa, HexBinary::from(pub_key.to_bytes())) + .try_into() + .unwrap(); + + Signer { + address, + weight: Uint128::one(), + pub_key, + } + } + + fn matching_verifier_set_and_tx_block() -> ( SuiAddress, SuiTransactionBlockResponse, VerifierSetConfirmation, ) { let gateway_address = SuiAddress::random_for_testing_only(); - + let signers = vec![random_signer(), random_signer(), random_signer()]; + let created_at = rand::random(); + let threshold = Uint128::one(); let verifier_set_confirmation = VerifierSetConfirmation { tx_id: TransactionDigest::random(), - event_index: rand::random::(), + event_index: rand::random(), verifier_set: VerifierSet { - signers: BTreeMap::new(), - threshold: Uint128::one(), - created_at: 2, + signers: signers + .iter() + .map(|signer| (signer.address.to_string(), signer.clone())) + .collect(), + threshold, + created_at, }, }; - let json_str = format!( - r#"{{"epoch": "{}", "payload":[9,33,2,28,79,35,229,96,199,254,112,157,252,157,33,86,76,80,174,125,71,132,149,100,185,195,50,28,56,168,173,27,148,211,13,33,2,48,84,180,104,180,217,232,81,68,34,87,5,170,93,208,110,70,34,106,18,170,230,232,84,177,96,70,223,39,33,69,243,33,2,111,165,50,83,196,229,202,139,167,22,144,71,12,136,118,134,248,101,250,219,73,67,12,46,149,223,204,58,134,78,12,140,33,2,117,97,196,77,216,94,31,8,169,159,77,164,26,249,18,252,106,73,134,164,49,179,32,156,241,200,236,219,119,96,154,174,33,2,211,238,247,108,49,105,73,69,232,85,66,59,29,114,68,216,13,187,208,76,45,190,112,127,63,78,201,189,207,232,137,80,33,2,253,243,145,109,216,125,193,53,124,210,124,157,62,195,2,187,26,78,51,29,236,222,0,247,71,157,177,44,59,201,201,110,33,3,13,71,41,67,81,196,128,14,128,66,129,231,226,77,127,173,123,58,83,198,102,149,143,165,189,207,7,26,146,127,120,223,33,3,179,111,82,200,141,104,219,127,177,163,157,28,106,41,141,191,105,54,200,199,63,140,125,57,134,20,90,19,183,153,55,68,33,3,250,161,172,32,115,91,220,86,71,57,15,155,185,167,99,209,57,194,132,114,11,176,91,70,232,219,84,202,119,5,157,125,9,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}}"#, - rand::random::(), - ); - let parsed: serde_json::Value = serde_json::from_str(json_str.as_str()).unwrap(); + let parsed_json = json!({ + "epoch": "1", + "signers": { + "nonce": { + "bytes": format!("0x{:0>64}", HexBinary::from(created_at.to_be_bytes()).to_hex()) + }, + + "signers": signers.into_iter().map(|signer| { + json!({ + "pub_key": HexBinary::from(signer.pub_key).to_vec(), + "weight": signer.weight.u128().to_string() + }) + }).collect::>(), + "threshold": threshold.to_string(), + }, + "signers_hash": { + "bytes": format!("0x{:0>64}", HexBinary::from(verifier_set_confirmation.verifier_set.hash()).to_hex()) + } + }); let event = SuiEvent { id: EventID { @@ -312,11 +522,11 @@ mod tests { sender: SuiAddress::random_for_testing_only(), type_: StructTag { address: gateway_address.into(), - module: "validators".parse().unwrap(), - name: "OperatorshipTransferred".parse().unwrap(), + module: "auth".parse().unwrap(), + name: "SignersRotated".parse().unwrap(), type_params: vec![], }, - parsed_json: parsed, + parsed_json, bcs: vec![], timestamp_ms: None, }; diff --git a/ampd/src/tests/config_template.toml b/ampd/src/tests/config_template.toml index df4cb492b..30865dbd9 100644 --- a/ampd/src/tests/config_template.toml +++ b/ampd/src/tests/config_template.toml @@ -1,8 +1,12 @@ health_check_bind_addr = '0.0.0.0:3000' tm_jsonrpc = 'http://localhost:26657/' tm_grpc = 'tcp://localhost:9090' -event_buffer_cap = 100000 -event_stream_timeout = '15s' + +[event_processor] +retry_delay = '1s' +retry_max_attempts = 3 +stream_timeout = '15s' +stream_buffer_size = 100000 [broadcast] chain_id = 'axelar-dojo-1' diff --git a/ampd/src/tm_client.rs b/ampd/src/tm_client.rs index 44f91b475..fddf7feca 100644 --- a/ampd/src/tm_client.rs +++ b/ampd/src/tm_client.rs @@ -1,9 +1,13 @@ +use std::time::Duration; + use async_trait::async_trait; use error_stack::{Report, Result}; use mockall::automock; use tendermint::block::Height; use tendermint_rpc::{Client, HttpClient}; +use crate::asyncutil::future::{self, RetryPolicy}; + pub type BlockResultsResponse = tendermint_rpc::endpoint::block_results::Response; pub type BlockResponse = tendermint_rpc::endpoint::block::Response; pub type Error = tendermint_rpc::Error; @@ -18,12 +22,26 @@ pub trait TmClient { #[async_trait] impl TmClient for HttpClient { async fn latest_block(&self) -> Result { - Client::latest_block(self).await.map_err(Report::from) + future::with_retry( + || Client::latest_block(self), + RetryPolicy::RepeatConstant { + sleep: Duration::from_secs(1), + max_attempts: 15, + }, + ) + .await + .map_err(Report::from) } async fn block_results(&self, height: Height) -> Result { - Client::block_results(self, height) - .await - .map_err(Report::from) + future::with_retry( + || Client::block_results(self, height), + RetryPolicy::RepeatConstant { + sleep: Duration::from_secs(1), + max_attempts: 15, + }, + ) + .await + .map_err(Report::from) } } diff --git a/ampd/src/tofnd/grpc.rs b/ampd/src/tofnd/grpc.rs index 0c934e423..91a805bfa 100644 --- a/ampd/src/tofnd/grpc.rs +++ b/ampd/src/tofnd/grpc.rs @@ -2,21 +2,20 @@ use std::sync::Arc; use async_trait::async_trait; use cosmrs::tendermint::public_key::PublicKey as TMPublicKey; -use der::{asn1::BitStringRef, Sequence}; -use ed25519::pkcs8::spki::{der::Decode, AlgorithmIdentifierRef}; use error_stack::{Report, ResultExt}; use k256::Secp256k1; use mockall::automock; use tokio::sync::Mutex; -use tonic::{transport::Channel, Status}; +use tonic::transport::Channel; +use tonic::Status; -use crate::{types::PublicKey, url::Url}; - -use super::proto::{ - keygen_response::KeygenResponse, multisig_client, sign_response::SignResponse, Algorithm, - KeygenRequest, SignRequest, -}; -use super::{error::Error, error::TofndError, MessageDigest, Signature}; +use super::error::{Error, TofndError}; +use super::proto::keygen_response::KeygenResponse; +use super::proto::sign_response::SignResponse; +use super::proto::{multisig_client, Algorithm, KeygenRequest, SignRequest}; +use super::{MessageDigest, Signature}; +use crate::types::PublicKey; +use crate::url::Url; type Result = error_stack::Result; @@ -119,7 +118,9 @@ impl Multisig for MultisigClient { Algorithm::Ecdsa => ecdsa::Signature::::from_der(&signature) .map(|sig| sig.to_vec()) .change_context(Error::ParsingFailed), - Algorithm::Ed25519 => parse_der_ed25519_signature(&signature), + Algorithm::Ed25519 => ed25519::Signature::from_slice(&signature) + .map(|sig| sig.to_vec()) + .change_context(Error::ParsingFailed), }, SignResponse::Error(error_msg) => { @@ -128,17 +129,3 @@ impl Multisig for MultisigClient { }) } } - -#[derive(Sequence)] -struct Asn1Signature<'a> { - pub signature_algorithm: AlgorithmIdentifierRef<'a>, - pub signature: BitStringRef<'a>, -} - -fn parse_der_ed25519_signature(der_sig: &[u8]) -> Result { - let der_decoded = Asn1Signature::from_der(der_sig).change_context(Error::ParsingFailed)?; - - ed25519_dalek::Signature::from_slice(der_decoded.signature.raw_bytes()) - .map(|s| s.to_vec()) - .change_context(Error::ParsingFailed) -} diff --git a/ampd/src/types.rs b/ampd/src/types.rs index 4801b30ba..5d21e2312 100644 --- a/ampd/src/types.rs +++ b/ampd/src/types.rs @@ -1,8 +1,7 @@ use std::fmt; use std::hash::{Hash as StdHash, Hasher}; -use cosmrs::crypto; -use cosmrs::AccountId; +use cosmrs::{crypto, AccountId}; use ethers_core::types::{Address, H256}; use serde::{Deserialize, Serialize}; diff --git a/ampd/src/url.rs b/ampd/src/url.rs index 654c99062..90a1fdc10 100644 --- a/ampd/src/url.rs +++ b/ampd/src/url.rs @@ -1,8 +1,9 @@ +use std::fmt::{Display, Formatter}; +use std::str::FromStr; + use deref_derive::Deref; use serde::de::{Error, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt::{Display, Formatter}; -use std::str::FromStr; use url::ParseError; #[derive(Debug, Deref, Hash, PartialEq, Eq, Clone)] diff --git a/codecov.yml b/codecov.yml index d81edca72..c4525eb0b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -4,3 +4,7 @@ coverage: default: target: auto threshold: 0.25% + patch: + default: + target: auto + threshold: 1% diff --git a/contracts/gateway/.cargo/config b/contracts/coordinator/.cargo/config.toml similarity index 100% rename from contracts/gateway/.cargo/config rename to contracts/coordinator/.cargo/config.toml diff --git a/contracts/coordinator/Cargo.toml b/contracts/coordinator/Cargo.toml index 509558efd..a653a2dd0 100644 --- a/contracts/coordinator/Cargo.toml +++ b/contracts/coordinator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "coordinator" -version = "0.2.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" description = "Amplifier info aggregation for external use, alongside contract management, instantiation and migration" @@ -33,13 +33,13 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] -axelar-wasm-std = { workspace = true } -axelar-wasm-std-derive = { workspace = true } +axelar-wasm-std = { workspace = true, features = ["derive"] } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } error-stack = { workspace = true } +msgs-derive = { workspace = true } multisig = { workspace = true, features = ["library"] } report = { workspace = true } router-api = { workspace = true } @@ -48,7 +48,7 @@ thiserror = { workspace = true } [dev-dependencies] cw-multi-test = "0.15.1" integration-tests = { workspace = true } -tofn = { git = "https://github.com/axelarnetwork/tofn.git", branch = "update-deps" } +tofn = { workspace = true } [lints] workspace = true diff --git a/contracts/coordinator/src/bin/schema.rs b/contracts/coordinator/src/bin/schema.rs index e026cca8c..c99447b20 100644 --- a/contracts/coordinator/src/bin/schema.rs +++ b/contracts/coordinator/src/bin/schema.rs @@ -1,6 +1,5 @@ -use cosmwasm_schema::write_api; - use coordinator::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use cosmwasm_schema::write_api; fn main() { write_api! { diff --git a/contracts/coordinator/src/contract.rs b/contracts/coordinator/src/contract.rs index 6da163524..2199452ee 100644 --- a/contracts/coordinator/src/contract.rs +++ b/contracts/coordinator/src/contract.rs @@ -1,24 +1,33 @@ -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{Config, CONFIG}; +mod execute; +mod query; + +mod migrations; +use axelar_wasm_std::{permission_control, FnExt}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response}; +use cosmwasm_std::{ + to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, Storage, +}; +use error_stack::report; +use crate::contract::migrations::v0_2_0; use crate::error::ContractError; -use crate::execute; -use crate::query; +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::state::is_prover_registered; -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate( deps: DepsMut, _env: Env, _msg: Empty, -) -> Result { - // any version checks should be done before here +) -> Result { + v0_2_0::migrate(deps.storage)?; + // this needs to be the last thing to do during migration, + // because previous migration steps should check the old version cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; Ok(Response::default()) @@ -30,15 +39,12 @@ pub fn instantiate( _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> Result { +) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - CONFIG.save( - deps.storage, - &Config { - governance: deps.api.addr_validate(&msg.governance_address)?, - }, - )?; + let governance = deps.api.addr_validate(&msg.governance_address)?; + permission_control::set_governance(deps.storage, &governance)?; + Ok(Response::default()) } @@ -48,37 +54,51 @@ pub fn execute( _env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result { - match msg { +) -> Result { + match msg.ensure_permissions( + deps.storage, + &info.sender, + find_prover_address(&info.sender), + )? { ExecuteMsg::RegisterProverContract { chain_name, new_prover_addr, - } => { - execute::check_governance(&deps, info)?; - execute::register_prover(deps, chain_name, new_prover_addr) - } + } => execute::register_prover(deps, chain_name, new_prover_addr), ExecuteMsg::SetActiveVerifiers { verifiers } => { execute::set_active_verifier_set(deps, info, verifiers) } + }? + .then(Ok) +} + +fn find_prover_address( + sender: &Addr, +) -> impl FnOnce(&dyn Storage, &ExecuteMsg) -> error_stack::Result + '_ { + |storage, _| { + if is_prover_registered(storage, sender.clone())? { + Ok(sender.clone()) + } else { + Err(report!(ContractError::ProverNotRegistered)) + } } - .map_err(axelar_wasm_std::ContractError::from) } #[cfg_attr(not(feature = "library"), entry_point)] -#[allow(dead_code)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { match msg { QueryMsg::ReadyToUnbond { worker_address } => to_json_binary( &query::check_verifier_ready_to_unbond(deps, worker_address)?, - ) - .map_err(|err| err.into()), + )?, } + .then(Ok) } #[cfg(test)] mod tests { - use crate::error::ContractError; + use std::collections::HashSet; + + use axelar_wasm_std::permission_control::Permission; use cosmwasm_std::testing::{ mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, }; @@ -86,6 +106,7 @@ mod tests { use router_api::ChainName; use super::*; + use crate::state::load_prover_by_chain; struct TestSetup { deps: OwnedDeps, @@ -107,7 +128,7 @@ mod tests { assert!(res.is_ok()); let eth_prover = Addr::unchecked("eth_prover"); - let eth: ChainName = "Ethereum".to_string().try_into().unwrap(); + let eth: ChainName = "Ethereum".parse().unwrap(); TestSetup { deps, @@ -121,21 +142,29 @@ mod tests { #[allow(clippy::arithmetic_side_effects)] fn test_instantiation() { let governance = "governance_for_coordinator"; - let test_setup = setup(governance); - - let config = CONFIG.load(test_setup.deps.as_ref().storage).unwrap(); - assert_eq!(config.governance, governance); - } - - #[test] - fn migrate_sets_contract_version() { - let mut deps = mock_dependencies(); + let mut test_setup = setup(governance); - migrate(deps.as_mut(), mock_env(), Empty {}).unwrap(); + assert!(execute( + test_setup.deps.as_mut(), + test_setup.env.clone(), + mock_info("not_governance", &[]), + ExecuteMsg::RegisterProverContract { + chain_name: test_setup.chain_name.clone(), + new_prover_addr: test_setup.prover.clone(), + } + ) + .is_err()); - let contract_version = cw2::get_contract_version(deps.as_mut().storage).unwrap(); - assert_eq!(contract_version.contract, "coordinator"); - assert_eq!(contract_version.version, CONTRACT_VERSION); + assert!(execute( + test_setup.deps.as_mut(), + test_setup.env, + mock_info(governance, &[]), + ExecuteMsg::RegisterProverContract { + chain_name: test_setup.chain_name.clone(), + new_prover_addr: test_setup.prover.clone(), + } + ) + .is_ok()); } #[test] @@ -154,9 +183,12 @@ mod tests { ) .unwrap(); - let chain_provers = - query::prover(test_setup.deps.as_ref(), test_setup.chain_name.clone()).unwrap(); - assert_eq!(chain_provers, test_setup.prover); + let chain_prover = load_prover_by_chain( + test_setup.deps.as_ref().storage, + test_setup.chain_name.clone(), + ); + assert!(chain_prover.is_ok(), "{:?}", chain_prover); + assert_eq!(chain_prover.unwrap(), test_setup.prover); } #[test] @@ -175,7 +207,63 @@ mod tests { ); assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from(ContractError::Unauthorized).to_string() + axelar_wasm_std::error::ContractError::from( + permission_control::Error::PermissionDenied { + expected: Permission::Governance.into(), + actual: Permission::NoPrivilege.into() + } + ) + .to_string() + ); + } + + #[test] + fn set_active_verifiers_from_prover_succeeds() { + let governance = "governance_for_coordinator"; + let mut test_setup = setup(governance); + + execute( + test_setup.deps.as_mut(), + test_setup.env.clone(), + mock_info(governance, &[]), + ExecuteMsg::RegisterProverContract { + chain_name: test_setup.chain_name.clone(), + new_prover_addr: test_setup.prover.clone(), + }, + ) + .unwrap(); + + let res = execute( + test_setup.deps.as_mut(), + test_setup.env, + mock_info(test_setup.prover.as_str(), &[]), + ExecuteMsg::SetActiveVerifiers { + verifiers: HashSet::new(), + }, + ); + assert!(res.is_ok(), "{:?}", res); + } + + #[test] + fn set_active_verifiers_from_random_address_fails() { + let governance = "governance_for_coordinator"; + let mut test_setup = setup(governance); + + let res = execute( + test_setup.deps.as_mut(), + test_setup.env, + mock_info(test_setup.prover.as_str(), &[]), + ExecuteMsg::SetActiveVerifiers { + verifiers: HashSet::new(), + }, ); + assert!(res.unwrap_err().to_string().contains( + &axelar_wasm_std::error::ContractError::from( + permission_control::Error::WhitelistNotFound { + sender: test_setup.prover + } + ) + .to_string() + )); } } diff --git a/contracts/coordinator/src/execute.rs b/contracts/coordinator/src/contract/execute.rs similarity index 57% rename from contracts/coordinator/src/execute.rs rename to contracts/coordinator/src/contract/execute.rs index d96277058..23aa67db3 100644 --- a/contracts/coordinator/src/execute.rs +++ b/contracts/coordinator/src/contract/execute.rs @@ -1,25 +1,17 @@ -use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response}; use std::collections::HashSet; +use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response}; use router_api::ChainName; use crate::error::ContractError; -use crate::state::{update_verifier_set_for_prover, CONFIG, PROVER_PER_CHAIN}; - -pub fn check_governance(deps: &DepsMut, info: MessageInfo) -> Result<(), ContractError> { - let config = CONFIG.load(deps.storage)?; - if config.governance != info.sender { - return Err(ContractError::Unauthorized); - } - Ok(()) -} +use crate::state::{save_prover_for_chain, update_verifier_set_for_prover}; pub fn register_prover( deps: DepsMut, chain_name: ChainName, new_prover_addr: Addr, ) -> Result { - PROVER_PER_CHAIN.save(deps.storage, chain_name.clone(), &(new_prover_addr))?; + save_prover_for_chain(deps.storage, chain_name, new_prover_addr)?; Ok(Response::new()) } diff --git a/contracts/coordinator/src/contract/migrations/mod.rs b/contracts/coordinator/src/contract/migrations/mod.rs new file mode 100644 index 000000000..693d0ab24 --- /dev/null +++ b/contracts/coordinator/src/contract/migrations/mod.rs @@ -0,0 +1,2 @@ +#[allow(deprecated)] +pub mod v0_2_0; diff --git a/contracts/coordinator/src/contract/migrations/v0_2_0.rs b/contracts/coordinator/src/contract/migrations/v0_2_0.rs new file mode 100644 index 000000000..ae6db4fa0 --- /dev/null +++ b/contracts/coordinator/src/contract/migrations/v0_2_0.rs @@ -0,0 +1,181 @@ +use axelar_wasm_std::permission_control; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Storage}; +use cw_storage_plus::{Item, Map}; +use router_api::ChainName; + +use crate::contract::CONTRACT_NAME; +use crate::error::ContractError; +use crate::state::save_prover_for_chain; + +const BASE_VERSION: &str = "0.2.0"; + +pub(crate) fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { + cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; + + migrate_config_to_permission_control(storage)?; + migrate_registered_provers(storage)?; + Ok(()) +} + +fn migrate_config_to_permission_control(storage: &mut dyn Storage) -> Result<(), ContractError> { + let config = CONFIG.load(storage).map_err(ContractError::from)?; + permission_control::set_governance(storage, &config.governance).map_err(ContractError::from)?; + CONFIG.remove(storage); + Ok(()) +} + +fn migrate_registered_provers(storage: &mut dyn Storage) -> Result<(), ContractError> { + PROVER_PER_CHAIN + .range(storage, None, None, cosmwasm_std::Order::Ascending) + .collect::, _>>()? + .into_iter() + .try_for_each(|(chain, prover)| save_prover_for_chain(storage, chain, prover))?; + + PROVER_PER_CHAIN.clear(storage); + Ok(()) +} + +#[cw_serde] +#[deprecated(since = "0.2.0", note = "only used to test the migration")] +pub struct Config { + pub governance: Addr, +} + +#[deprecated(since = "0.2.0", note = "only used to test the migration")] +pub const CONFIG: Item = Item::new("config"); + +#[deprecated(since = "0.2.0", note = "only used to test the migration")] +pub const PROVER_PER_CHAIN: Map = Map::new("prover_per_chain"); + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response}; + use router_api::ChainName; + + use super::PROVER_PER_CHAIN; + use crate::contract::migrations::v0_2_0; + use crate::contract::migrations::v0_2_0::BASE_VERSION; + use crate::contract::{execute, CONTRACT_NAME}; + use crate::error::ContractError; + use crate::msg::{ExecuteMsg, InstantiateMsg}; + use crate::state::{is_prover_registered, load_prover_by_chain}; + + #[test] + fn migrate_checks_contract_version() { + let mut deps = mock_dependencies(); + let _ = instantiate_0_2_0_contract(deps.as_mut()).unwrap(); + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap(); + + assert!(v0_2_0::migrate(deps.as_mut().storage).is_err()); + + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, BASE_VERSION).unwrap(); + + assert!(v0_2_0::migrate(deps.as_mut().storage).is_ok()); + } + + #[test] + fn ensure_governance_is_migrated_to_permission_control() { + let mut deps = mock_dependencies(); + + let msg = instantiate_0_2_0_contract(deps.as_mut()).unwrap(); + + assert!(v0_2_0::CONFIG.may_load(&deps.storage).unwrap().is_some()); + + assert!(v0_2_0::migrate(deps.as_mut().storage).is_ok()); + + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info("anyone", &[]), + ExecuteMsg::RegisterProverContract { + chain_name: "chain".parse().unwrap(), + new_prover_addr: Addr::unchecked("any_addr"), + }, + ) + .is_err()); + + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(&msg.governance_address, &[]), + ExecuteMsg::RegisterProverContract { + chain_name: "chain".parse().unwrap(), + new_prover_addr: Addr::unchecked("any_addr"), + }, + ) + .is_ok()); + + assert!(v0_2_0::CONFIG.may_load(&deps.storage).unwrap().is_none()) + } + + #[test] + fn ensure_registered_provers_are_migrated() { + let mut deps = mock_dependencies(); + instantiate_0_2_0_contract(deps.as_mut()).unwrap(); + + let provers: Vec<(ChainName, Addr)> = vec![ + ("chain1".parse().unwrap(), Addr::unchecked("addr1")), + ("chain2".parse().unwrap(), Addr::unchecked("addr2")), + ]; + + for (chain, prover) in &provers { + register_prover_0_2_0(deps.as_mut(), chain.clone(), prover.clone()).unwrap(); + } + + assert!(v0_2_0::migrate(deps.as_mut().storage).is_ok()); + + for (chain, prover) in provers { + assert_eq!( + load_prover_by_chain(deps.as_ref().storage, chain).unwrap(), + prover.clone() + ); + + // check index is working as well + assert!(is_prover_registered(deps.as_ref().storage, prover).unwrap()); + } + + assert!(PROVER_PER_CHAIN.is_empty(deps.as_ref().storage)); + } + + fn instantiate_0_2_0_contract( + deps: DepsMut, + ) -> Result { + let governance = "governance"; + + let msg = InstantiateMsg { + governance_address: governance.to_string(), + }; + instantiate_0_2_0(deps, mock_env(), mock_info("sender", &[]), msg.clone())?; + Ok(msg) + } + + #[deprecated(since = "0.2.0", note = "only used to test the migration")] + fn instantiate_0_2_0( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, + ) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, BASE_VERSION)?; + + v0_2_0::CONFIG.save( + deps.storage, + &v0_2_0::Config { + governance: deps.api.addr_validate(&msg.governance_address)?, + }, + )?; + Ok(Response::default()) + } + + #[deprecated(since = "0.2.0", note = "only used to test the migration")] + fn register_prover_0_2_0( + deps: DepsMut, + chain_name: ChainName, + new_prover_addr: Addr, + ) -> Result { + PROVER_PER_CHAIN.save(deps.storage, chain_name.clone(), &(new_prover_addr))?; + Ok(Response::new()) + } +} diff --git a/contracts/coordinator/src/contract/query.rs b/contracts/coordinator/src/contract/query.rs new file mode 100644 index 000000000..275e5488e --- /dev/null +++ b/contracts/coordinator/src/contract/query.rs @@ -0,0 +1,16 @@ +use cosmwasm_std::{Addr, Deps, Order, StdResult}; + +use crate::state::VERIFIER_PROVER_INDEXED_MAP; + +fn is_verifier_in_any_verifier_set(deps: Deps, verifier_address: &Addr) -> bool { + VERIFIER_PROVER_INDEXED_MAP + .idx + .by_verifier + .prefix(verifier_address.clone()) + .range(deps.storage, None, None, Order::Ascending) + .any(|_| true) +} + +pub fn check_verifier_ready_to_unbond(deps: Deps, verifier_address: Addr) -> StdResult { + Ok(!is_verifier_in_any_verifier_set(deps, &verifier_address)) +} diff --git a/contracts/coordinator/src/error.rs b/contracts/coordinator/src/error.rs index 8a69ce8ba..a60817fc5 100644 --- a/contracts/coordinator/src/error.rs +++ b/contracts/coordinator/src/error.rs @@ -1,6 +1,6 @@ -use axelar_wasm_std_derive::IntoContractError; +use axelar_wasm_std::IntoContractError; use cosmwasm_std::StdError; -use router_api::ChainName; +use cw2::VersionError; use thiserror::Error; #[derive(Error, Debug, PartialEq, IntoContractError)] @@ -8,9 +8,12 @@ pub enum ContractError { #[error(transparent)] Std(#[from] StdError), + #[error(transparent)] + Version(#[from] VersionError), + #[error("caller not unauthorized to perform this action")] Unauthorized, - #[error("no provers registered for chain {0}")] - NoProversRegisteredForChain(ChainName), + #[error("prover is not registered")] + ProverNotRegistered, } diff --git a/contracts/coordinator/src/lib.rs b/contracts/coordinator/src/lib.rs index b1acd54e1..f6466fdf3 100644 --- a/contracts/coordinator/src/lib.rs +++ b/contracts/coordinator/src/lib.rs @@ -1,6 +1,4 @@ pub mod contract; pub mod error; -pub mod execute; pub mod msg; -pub mod query; -pub mod state; +mod state; diff --git a/contracts/coordinator/src/msg.rs b/contracts/coordinator/src/msg.rs index af75e4d0e..b0e95f90f 100644 --- a/contracts/coordinator/src/msg.rs +++ b/contracts/coordinator/src/msg.rs @@ -1,7 +1,9 @@ +use std::collections::HashSet; + use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Addr; +use msgs_derive::EnsurePermissions; use router_api::ChainName; -use std::collections::HashSet; #[cw_serde] pub struct InstantiateMsg { @@ -9,15 +11,15 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(EnsurePermissions)] pub enum ExecuteMsg { - // Can only be called by governance + #[permission(Governance)] RegisterProverContract { chain_name: ChainName, new_prover_addr: Addr, }, - SetActiveVerifiers { - verifiers: HashSet, - }, + #[permission(Specific(prover))] + SetActiveVerifiers { verifiers: HashSet }, } #[cw_serde] diff --git a/contracts/coordinator/src/query.rs b/contracts/coordinator/src/query.rs deleted file mode 100644 index c7241d3d0..000000000 --- a/contracts/coordinator/src/query.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::error::ContractError; -use crate::state::{VerifierAddress, PROVER_PER_CHAIN, VERIFIER_PROVER_INDEXED_MAP}; -use cosmwasm_std::{Addr, Deps, Order, StdResult}; -use router_api::ChainName; - -pub fn prover(deps: Deps, chain_name: ChainName) -> Result { - PROVER_PER_CHAIN - .may_load(deps.storage, chain_name.clone())? - .ok_or(ContractError::NoProversRegisteredForChain(chain_name)) -} - -fn is_verifier_in_any_verifier_set(deps: Deps, verifier_address: &VerifierAddress) -> bool { - VERIFIER_PROVER_INDEXED_MAP - .idx - .by_verifier - .prefix(verifier_address.clone()) - .range(deps.storage, None, None, Order::Ascending) - .any(|_| true) -} - -pub fn check_verifier_ready_to_unbond( - deps: Deps, - verifier_address: VerifierAddress, -) -> StdResult { - Ok(!is_verifier_in_any_verifier_set(deps, &verifier_address)) -} diff --git a/contracts/coordinator/src/state.rs b/contracts/coordinator/src/state.rs index 631387162..8b953900b 100644 --- a/contracts/coordinator/src/state.rs +++ b/contracts/coordinator/src/state.rs @@ -1,34 +1,62 @@ -use crate::error::ContractError; +use std::collections::HashSet; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Order, Storage}; -use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; +use cw_storage_plus::{index_list, IndexedMap, MultiIndex, UniqueIndex}; use router_api::ChainName; -use std::collections::HashSet; -#[cw_serde] -pub struct Config { - pub governance: Addr, -} +use crate::error::ContractError; -pub const CONFIG: Item = Item::new("config"); +type ProverAddress = Addr; +type VerifierAddress = Addr; + +#[index_list(ProverAddress)] +struct ChainProverIndexes<'a> { + pub by_prover: UniqueIndex<'a, ProverAddress, ProverAddress, ChainName>, +} -pub type ProverAddress = Addr; +const CHAIN_PROVER_INDEXED_MAP: IndexedMap = + IndexedMap::new( + "chain_prover_map", + ChainProverIndexes { + by_prover: UniqueIndex::new(|prover| prover.clone(), "chain_prover_map_by_prover"), + }, + ); -pub const PROVER_PER_CHAIN: Map = Map::new("prover_per_chain"); +pub fn is_prover_registered( + storage: &dyn Storage, + prover_address: ProverAddress, +) -> Result { + Ok(CHAIN_PROVER_INDEXED_MAP + .idx + .by_prover + .item(storage, prover_address)? + .is_some()) +} -pub type ChainNames = HashSet; -pub type VerifierAddress = Addr; -pub const CHAINS_OF_VERIFIER: Map = Map::new("chains_of_verifier"); +#[allow(dead_code)] // Used in tests, might be useful in future query +pub fn load_prover_by_chain( + storage: &dyn Storage, + chain_name: ChainName, +) -> Result { + CHAIN_PROVER_INDEXED_MAP + .may_load(storage, chain_name)? + .ok_or(ContractError::ProverNotRegistered) +} -pub struct VerifierSetIndex<'a> { - pub by_verifier: MultiIndex<'a, Addr, VerifierProverRecord, (Addr, Addr)>, +pub fn save_prover_for_chain( + storage: &mut dyn Storage, + chain: ChainName, + prover: ProverAddress, +) -> Result<(), ContractError> { + CHAIN_PROVER_INDEXED_MAP.save(storage, chain.clone(), &prover)?; + Ok(()) } -impl<'a> IndexList for VerifierSetIndex<'a> { - fn get_indexes(&self) -> Box> + '_> { - let v: Vec<&dyn Index> = vec![&self.by_verifier]; - Box::new(v.into_iter()) - } +#[index_list(VerifierProverRecord)] +pub struct VerifierSetIndex<'a> { + pub by_verifier: + MultiIndex<'a, VerifierAddress, VerifierProverRecord, (ProverAddress, VerifierAddress)>, } #[cw_serde] @@ -38,7 +66,7 @@ pub struct VerifierProverRecord { } pub const VERIFIER_PROVER_INDEXED_MAP: IndexedMap< - (Addr, Addr), + (ProverAddress, VerifierAddress), VerifierProverRecord, VerifierSetIndex, > = IndexedMap::new( diff --git a/contracts/multisig-prover/.cargo/config b/contracts/gateway/.cargo/config.toml similarity index 100% rename from contracts/multisig-prover/.cargo/config rename to contracts/gateway/.cargo/config.toml diff --git a/contracts/gateway/Cargo.toml b/contracts/gateway/Cargo.toml index f822930ac..a442943d0 100644 --- a/contracts/gateway/Cargo.toml +++ b/contracts/gateway/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gateway" -version = "0.2.3" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" description = "Gateway contract" @@ -35,8 +35,7 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] -axelar-wasm-std = { workspace = true } -axelar-wasm-std-derive = { workspace = true } +axelar-wasm-std = { workspace = true, features = ["derive"] } client = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } @@ -54,6 +53,7 @@ voting-verifier = { workspace = true, features = ["library"] } [dev-dependencies] cw-multi-test = "0.15.1" +rand = "0.8.5" [lints] workspace = true diff --git a/contracts/gateway/src/bin/schema.rs b/contracts/gateway/src/bin/schema.rs index afb8679ae..edf3475d3 100644 --- a/contracts/gateway/src/bin/schema.rs +++ b/contracts/gateway/src/bin/schema.rs @@ -1,7 +1,6 @@ use cosmwasm_schema::write_api; -use gateway_api::msg::{ExecuteMsg, QueryMsg}; - use gateway::msg::InstantiateMsg; +use gateway_api::msg::{ExecuteMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/gateway/src/contract.rs b/contracts/gateway/src/contract.rs index da85f155c..9787a139d 100644 --- a/contracts/gateway/src/contract.rs +++ b/contracts/gateway/src/contract.rs @@ -1,165 +1,126 @@ +use std::fmt::Debug; + +use axelar_wasm_std::FnExt; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response}; +use error_stack::ResultExt; use gateway_api::msg::{ExecuteMsg, QueryMsg}; -use router_api::CrossChainId; -use std::fmt::Debug; +use router_api::client::Router; +use crate::contract::migrations::v0_2_3; use crate::msg::InstantiateMsg; +use crate::state; +use crate::state::Config; mod execute; +mod migrations; mod query; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("invalid store access")] + InvalidStoreAccess, + #[error("batch contains duplicate message ids")] + DuplicateMessageIds, + #[error("invalid address")] + InvalidAddress, + #[error("failed to query message status")] + MessageStatus, + #[error("failed to verify messages")] + VerifyMessages, + #[error("failed to route outgoing messages to gateway")] + RouteOutgoingMessages, + #[error("failed to route messages from gateway to router")] + RouteIncomingMessages, + #[error("failed to query outgoing messages")] + OutgoingMessages, + #[error("failed to save outgoing message")] + SaveOutgoingMessage, + #[error("failed to execute gateway command")] + Execute, +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate( deps: DepsMut, _env: Env, _msg: Empty, -) -> Result { - // any version checks should be done before here - - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - +) -> Result { + v0_2_3::migrate(deps.storage)?; Ok(Response::default()) } #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, - env: Env, - info: MessageInfo, + _: Env, + _: MessageInfo, msg: InstantiateMsg, -) -> Result { +) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Ok(internal::instantiate(deps, env, info, msg)?) + let router = deps + .api + .addr_validate(&msg.router_address) + .change_context(Error::InvalidAddress) + .attach_printable(msg.router_address)?; + + let verifier = deps + .api + .addr_validate(&msg.verifier_address) + .change_context(Error::InvalidAddress) + .attach_printable(msg.verifier_address)?; + + state::save_config(deps.storage, &Config { verifier, router })?; + Ok(Response::new()) } #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, - env: Env, + _: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result { - Ok(internal::execute(deps, env, info, msg)?) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query( - deps: Deps, - env: Env, - msg: QueryMsg, -) -> Result { - Ok(internal::query(deps, env, msg)?) -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("gateway contract config is missing")] - ConfigMissing, - #[error("invalid store access")] - InvalidStoreAccess, - #[error("failed to serialize the response")] - SerializeResponse, - #[error("batch contains duplicate message ids")] - DuplicateMessageIds, - #[error("invalid address")] - InvalidAddress, - #[error("failed to query message status")] - MessageStatus, - #[error("message with ID {0} not found")] - MessageNotFound(CrossChainId), -} - -mod internal { - use client::Client; - use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; - use error_stack::{Result, ResultExt}; - use gateway_api::msg::{ExecuteMsg, QueryMsg}; - use router_api::client::Router; - - use crate::contract::Error; - use crate::msg::InstantiateMsg; - use crate::state::Config; - use crate::{contract, state}; - - pub(crate) fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, - ) -> Result { - let router = deps - .api - .addr_validate(&msg.router_address) - .change_context(Error::InvalidAddress) - .attach_printable(msg.router_address)?; - - let verifier = deps - .api - .addr_validate(&msg.verifier_address) - .change_context(Error::InvalidAddress) - .attach_printable(msg.verifier_address)?; +) -> Result { + let config = state::load_config(deps.storage).change_context(Error::Execute)?; + let verifier = client::Client::new(deps.querier, config.verifier).into(); - state::save_config(deps.storage, &Config { verifier, router }) - .change_context(Error::InvalidStoreAccess)?; + let router = Router { + address: config.router, + }; - Ok(Response::new()) - } - - pub(crate) fn execute( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: ExecuteMsg, - ) -> Result { - let config = state::load_config(deps.storage).change_context(Error::ConfigMissing)?; - let verifier = Client::new(deps.querier, config.verifier).into(); - - let router = Router { - address: config.router, - }; - - match msg { - ExecuteMsg::VerifyMessages(msgs) => contract::execute::verify_messages(&verifier, msgs), - ExecuteMsg::RouteMessages(msgs) => { - if info.sender == router.address { - contract::execute::route_outgoing_messages(deps.storage, msgs) - } else { - contract::execute::route_incoming_messages(&verifier, &router, msgs) - } - } + match msg.ensure_permissions(deps.storage, &info.sender)? { + ExecuteMsg::VerifyMessages(msgs) => { + execute::verify_messages(&verifier, msgs).change_context(Error::VerifyMessages) } - } - - pub(crate) fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { - match msg { - QueryMsg::GetOutgoingMessages { message_ids } => { - let msgs = contract::query::get_outgoing_messages(deps.storage, message_ids)?; - to_json_binary(&msgs).change_context(Error::SerializeResponse) + ExecuteMsg::RouteMessages(msgs) => { + if info.sender == router.address { + execute::route_outgoing_messages(deps.storage, msgs) + .change_context(Error::RouteOutgoingMessages) + } else { + execute::route_incoming_messages(&verifier, &router, msgs) + .change_context(Error::RouteIncomingMessages) } } - } + }? + .then(Ok) } -#[cfg(test)] -mod tests { - use cosmwasm_std::testing::{mock_dependencies, mock_env}; - - use super::*; - - #[test] - fn migrate_sets_contract_version() { - let mut deps = mock_dependencies(); - - migrate(deps.as_mut(), mock_env(), Empty {}).unwrap(); - - let contract_version = cw2::get_contract_version(deps.as_mut().storage).unwrap(); - assert_eq!(contract_version.contract, "gateway"); - assert_eq!(contract_version.version, CONTRACT_VERSION); - } +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query( + deps: Deps, + _: Env, + msg: QueryMsg, +) -> Result { + match msg { + QueryMsg::OutgoingMessages(message_ids) => { + query::outgoing_messages(deps.storage, message_ids.iter()) + .change_context(Error::OutgoingMessages) + } + }? + .then(Ok) } diff --git a/contracts/gateway/src/contract/execute.rs b/contracts/gateway/src/contract/execute.rs index efd1721cd..d72f98527 100644 --- a/contracts/gateway/src/contract/execute.rs +++ b/contracts/gateway/src/contract/execute.rs @@ -10,7 +10,7 @@ use crate::contract::Error; use crate::events::GatewayEvent; use crate::state; -pub fn verify_messages( +pub(crate) fn verify_messages( verifier: &voting_verifier::Client, msgs: Vec, ) -> Result { @@ -37,8 +37,8 @@ pub(crate) fn route_outgoing_messages( let msgs = check_for_duplicates(verified)?; for msg in msgs.iter() { - state::save_outgoing_msg(store, msg.cc_id.clone(), msg) - .change_context(Error::InvalidStoreAccess)?; + state::save_outgoing_message(store, &msg.cc_id, msg) + .change_context(Error::SaveOutgoingMessage)?; } Ok(Response::new().add_events( diff --git a/contracts/gateway/src/contract/migrations/mod.rs b/contracts/gateway/src/contract/migrations/mod.rs new file mode 100644 index 000000000..3f8ffb3bd --- /dev/null +++ b/contracts/gateway/src/contract/migrations/mod.rs @@ -0,0 +1 @@ +pub mod v0_2_3; diff --git a/contracts/gateway/src/contract/migrations/v0_2_3.rs b/contracts/gateway/src/contract/migrations/v0_2_3.rs new file mode 100644 index 000000000..48ff502c6 --- /dev/null +++ b/contracts/gateway/src/contract/migrations/v0_2_3.rs @@ -0,0 +1,214 @@ +#![allow(deprecated)] + +use std::any::type_name; + +use axelar_wasm_std::error::ContractError; +use axelar_wasm_std::nonempty; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{StdError, StdResult, Storage}; +use cw_storage_plus::{Key, KeyDeserialize, Map, PrimaryKey}; +use router_api::{Address, ChainName, ChainNameRaw}; + +use crate::contract::{CONTRACT_NAME, CONTRACT_VERSION}; + +const BASE_VERSION: &str = "0.2.3"; + +pub fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { + cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; + + delete_outgoing_messages(storage); + + cw2::set_contract_version(storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + Ok(()) +} + +fn delete_outgoing_messages(storage: &mut dyn Storage) { + OUTGOING_MESSAGES.clear(storage); +} + +#[deprecated(since = "0.2.3", note = "only used during migration")] +const OUTGOING_MESSAGES_NAME: &str = "outgoing_messages"; + +#[deprecated(since = "0.2.3", note = "only used during migration")] +const OUTGOING_MESSAGES: Map<&CrossChainId, Message> = Map::new(OUTGOING_MESSAGES_NAME); + +#[cw_serde] +#[derive(Eq, Hash)] +#[deprecated(since = "0.2.3", note = "only used during migration")] +pub struct Message { + pub cc_id: CrossChainId, + pub source_address: Address, + pub destination_chain: ChainName, + pub destination_address: Address, + /// for better user experience, the payload hash gets encoded into hex at the edges (input/output), + /// but internally, we treat it as raw bytes to enforce its format. + #[serde(with = "axelar_wasm_std::hex")] + #[schemars(with = "String")] // necessary attribute in conjunction with #[serde(with ...)] + pub payload_hash: [u8; 32], +} + +#[cw_serde] +#[derive(Eq, Hash)] +#[deprecated(since = "0.2.3", note = "only used during migration")] +pub struct CrossChainId { + pub chain: ChainNameRaw, + pub id: nonempty::String, +} + +impl PrimaryKey<'_> for CrossChainId { + type Prefix = ChainNameRaw; + type SubPrefix = (); + type Suffix = String; + type SuperSuffix = (ChainNameRaw, String); + + fn key(&self) -> Vec { + let mut keys = self.chain.key(); + keys.extend(self.id.key()); + keys + } +} + +impl KeyDeserialize for &CrossChainId { + type Output = CrossChainId; + + fn from_vec(value: Vec) -> StdResult { + let (chain, id) = <(ChainNameRaw, String)>::from_vec(value)?; + Ok(CrossChainId { + chain, + id: id + .try_into() + .map_err(|err| StdError::parse_err(type_name::(), err))?, + }) + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; + use error_stack::ResultExt; + + use crate::contract::migrations::v0_2_3; + use crate::contract::{Error, CONTRACT_NAME, CONTRACT_VERSION}; + use crate::msg::InstantiateMsg; + use crate::state; + use crate::state::Config; + + #[test] + fn migrate_checks_contract_version() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap(); + + assert!(v0_2_3::migrate(deps.as_mut().storage).is_err()); + + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, v0_2_3::BASE_VERSION) + .unwrap(); + + assert!(v0_2_3::migrate(deps.as_mut().storage).is_ok()); + } + + #[test] + fn migrate_sets_contract_version() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + v0_2_3::migrate(deps.as_mut().storage).unwrap(); + + let contract_version = cw2::get_contract_version(deps.as_mut().storage).unwrap(); + assert_eq!(contract_version.contract, CONTRACT_NAME); + assert_eq!(contract_version.version, CONTRACT_VERSION); + } + + fn instantiate_contract(deps: DepsMut) { + instantiate( + deps, + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + verifier_address: "verifier".to_string(), + router_address: "router".to_string(), + }, + ) + .unwrap(); + } + + #[test] + fn migrate_outgoing_messages() { + let mut deps = mock_dependencies(); + + instantiate_contract(deps.as_mut()); + + let msgs = vec![ + v0_2_3::Message { + cc_id: v0_2_3::CrossChainId { + id: "id1".try_into().unwrap(), + chain: "chain1".try_into().unwrap(), + }, + source_address: "source-address".parse().unwrap(), + destination_chain: "destination".parse().unwrap(), + destination_address: "destination-address".parse().unwrap(), + payload_hash: [1; 32], + }, + v0_2_3::Message { + cc_id: v0_2_3::CrossChainId { + id: "id2".try_into().unwrap(), + chain: "chain2".try_into().unwrap(), + }, + source_address: "source-address2".parse().unwrap(), + destination_chain: "destination2".parse().unwrap(), + destination_address: "destination-address2".parse().unwrap(), + payload_hash: [2; 32], + }, + v0_2_3::Message { + cc_id: v0_2_3::CrossChainId { + id: "id3".try_into().unwrap(), + chain: "chain3".try_into().unwrap(), + }, + source_address: "source-address3".parse().unwrap(), + destination_chain: "destination3".parse().unwrap(), + destination_address: "destination-address3".parse().unwrap(), + payload_hash: [3; 32], + }, + ]; + + for msg in msgs.iter() { + v0_2_3::OUTGOING_MESSAGES + .save(deps.as_mut().storage, &msg.cc_id, msg) + .unwrap(); + } + + assert!(v0_2_3::migrate(deps.as_mut().storage).is_ok()); + + assert!(v0_2_3::OUTGOING_MESSAGES.is_empty(deps.as_ref().storage)) + } + + #[deprecated(since = "0.2.3", note = "only used to test the migration")] + pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, + ) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, v0_2_3::BASE_VERSION)?; + + let router = deps + .api + .addr_validate(&msg.router_address) + .change_context(Error::InvalidAddress) + .attach_printable(msg.router_address)?; + + let verifier = deps + .api + .addr_validate(&msg.verifier_address) + .change_context(Error::InvalidAddress) + .attach_printable(msg.verifier_address)?; + + state::save_config(deps.storage, &Config { verifier, router }) + .change_context(Error::InvalidStoreAccess)?; + + Ok(Response::new()) + } +} diff --git a/contracts/gateway/src/contract/query.rs b/contracts/gateway/src/contract/query.rs index 13243d90d..59cc4c4e5 100644 --- a/contracts/gateway/src/contract/query.rs +++ b/contracts/gateway/src/contract/query.rs @@ -1,94 +1,85 @@ -use crate::contract::Error; use axelar_wasm_std::error::extend_err; -use cosmwasm_std::Storage; -use error_stack::{report, Result, ResultExt}; +use cosmwasm_std::{to_json_binary, Binary, Storage}; +use error_stack::Result; use router_api::{CrossChainId, Message}; use crate::state; -pub fn get_outgoing_messages( +pub fn outgoing_messages<'a>( storage: &dyn Storage, - cross_chain_ids: Vec, -) -> Result, Error> { - cross_chain_ids - .into_iter() - .map(|id| try_load_msg(storage, id)) - .fold(Ok(vec![]), accumulate_errs) -} + cross_chain_ids: impl Iterator, +) -> Result { + let msgs = cross_chain_ids + .map(|id| state::load_outgoing_message(storage, id)) + .fold(Ok(vec![]), accumulate_errs)?; -fn try_load_msg(storage: &dyn Storage, id: CrossChainId) -> Result { - state::may_load_outgoing_msg(storage, id.clone()) - .change_context(Error::InvalidStoreAccess) - .transpose() - .unwrap_or(Err(report!(Error::MessageNotFound(id)))) + Ok(to_json_binary(&msgs).map_err(state::Error::from)?) } fn accumulate_errs( - acc: Result, Error>, - msg: Result, -) -> Result, Error> { + acc: Result, state::Error>, + msg: std::result::Result, +) -> Result, state::Error> { match (acc, msg) { - (Ok(mut acc), Ok(msg)) => { - acc.push(msg); - Ok(acc) + (Ok(mut msgs), Ok(msg)) => { + msgs.push(msg); + Ok(msgs) } - (Err(acc), Ok(_)) => Err(acc), - (acc, Err(msg_err)) => extend_err(acc, msg_err), + (Err(report), Ok(_)) => Err(report), + (acc, Err(msg_err)) => extend_err(acc, msg_err.into()), } } #[cfg(test)] mod test { - use crate::state; + use cosmwasm_std::from_json; use cosmwasm_std::testing::mock_dependencies; use router_api::{CrossChainId, Message}; + use crate::state; + #[test] - fn get_outgoing_messages_all_messages_present_returns_all() { + fn outgoing_messages_all_messages_present_returns_all() { let mut deps = mock_dependencies(); let messages = generate_messages(); for message in messages.iter() { - state::save_outgoing_msg(deps.as_mut().storage, message.cc_id.clone(), message) - .unwrap(); + state::save_outgoing_message(deps.as_mut().storage, &message.cc_id, message).unwrap(); } - let ids = messages.iter().map(|msg| msg.cc_id.clone()).collect(); + let ids = messages.iter().map(|msg| &msg.cc_id); - let res = super::get_outgoing_messages(&deps.storage, ids).unwrap(); - assert_eq!(res, messages); + let res = super::outgoing_messages(&deps.storage, ids).unwrap(); + let actual_messages: Vec = from_json(res).unwrap(); + assert_eq!(actual_messages, messages); } #[test] - fn get_outgoing_messages_nothing_stored_returns_not_found_error() { + fn outgoing_messages_nothing_stored_returns_not_found_error() { let deps = mock_dependencies(); let messages = generate_messages(); - let ids = messages.iter().map(|msg| msg.cc_id.clone()).collect(); + let ids = messages.iter().map(|msg| &msg.cc_id); - let res = super::get_outgoing_messages(&deps.storage, ids); + let res = super::outgoing_messages(&deps.storage, ids); assert!(res.is_err()); assert_eq!(res.unwrap_err().current_frames().len(), messages.len()); } #[test] - fn get_outgoing_messages_only_partially_found_returns_not_found_error() { + fn outgoing_messages_only_partially_found_returns_not_found_error() { let mut deps = mock_dependencies(); let messages = generate_messages(); - state::save_outgoing_msg( - deps.as_mut().storage, - messages[1].cc_id.clone(), - &messages[1], - ) - .unwrap(); + state::save_outgoing_message(deps.as_mut().storage, &messages[1].cc_id, &messages[1]) + .unwrap(); - let ids = messages.iter().map(|msg| msg.cc_id.clone()).collect(); + let ids = messages.iter().map(|msg| &msg.cc_id); - let res = super::get_outgoing_messages(&deps.storage, ids); + let res = super::outgoing_messages(&deps.storage, ids); assert!(res.is_err()); assert_eq!(res.unwrap_err().current_frames().len(), messages.len() - 1); @@ -97,30 +88,21 @@ mod test { fn generate_messages() -> Vec { vec![ Message { - cc_id: CrossChainId { - chain: "chain1".parse().unwrap(), - id: "id1".parse().unwrap(), - }, + cc_id: CrossChainId::new("chain1", "id1").unwrap(), destination_address: "addr1".parse().unwrap(), destination_chain: "chain2".parse().unwrap(), source_address: "addr2".parse().unwrap(), payload_hash: [0; 32], }, Message { - cc_id: CrossChainId { - chain: "chain2".parse().unwrap(), - id: "id2".parse().unwrap(), - }, + cc_id: CrossChainId::new("chain2", "id2").unwrap(), destination_address: "addr3".parse().unwrap(), destination_chain: "chain3".parse().unwrap(), source_address: "addr4".parse().unwrap(), payload_hash: [1; 32], }, Message { - cc_id: CrossChainId { - chain: "chain3".parse().unwrap(), - id: "id3".parse().unwrap(), - }, + cc_id: CrossChainId::new("chain3", "id3").unwrap(), destination_address: "addr5".parse().unwrap(), destination_chain: "chain4".parse().unwrap(), source_address: "addr6".parse().unwrap(), diff --git a/contracts/gateway/src/lib.rs b/contracts/gateway/src/lib.rs index cdacc8154..5501bc9c7 100644 --- a/contracts/gateway/src/lib.rs +++ b/contracts/gateway/src/lib.rs @@ -1,4 +1,4 @@ pub mod contract; -pub mod events; +mod events; pub mod msg; pub mod state; diff --git a/contracts/gateway/src/state.rs b/contracts/gateway/src/state.rs index c36a2fc33..1b8712aa0 100644 --- a/contracts/gateway/src/state.rs +++ b/contracts/gateway/src/state.rs @@ -1,7 +1,7 @@ +use axelar_wasm_std::IntoContractError; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Storage}; +use cosmwasm_std::{Addr, StdError, Storage}; use cw_storage_plus::{Item, Map}; -use error_stack::{Result, ResultExt}; use router_api::{CrossChainId, Message}; #[cw_serde] @@ -10,112 +10,104 @@ pub(crate) struct Config { pub router: Addr, } -pub(crate) fn save_config(storage: &mut dyn Storage, value: &Config) -> Result<(), Error> { - CONFIG - .save(storage, value) - .change_context(Error::SaveValue(CONFIG_NAME)) +const CONFIG: Item = Item::new("config"); +const OUTGOING_MESSAGES: Map<&CrossChainId, Message> = Map::new("outgoing_messages"); + +#[derive(thiserror::Error, Debug, IntoContractError)] +pub enum Error { + #[error(transparent)] + Std(#[from] StdError), + #[error("gateway got into an invalid state, its config is missing")] + MissingConfig, + #[error("message with ID {0} mismatches with the stored one")] + MessageMismatch(CrossChainId), + #[error("message with ID {0} not found")] + MessageNotFound(CrossChainId), } pub(crate) fn load_config(storage: &dyn Storage) -> Result { CONFIG - .load(storage) - .change_context(Error::LoadValue(CONFIG_NAME)) + .may_load(storage) + .map_err(Error::from)? + .ok_or(Error::MissingConfig) } -pub(crate) fn save_outgoing_msg( - storage: &mut dyn Storage, - key: CrossChainId, - value: &Message, -) -> Result<(), Error> { - OUTGOING_MESSAGES - .save(storage, key, value) - .change_context(Error::SaveValue(OUTGOING_MESSAGES_NAME)) +pub(crate) fn save_config(storage: &mut dyn Storage, config: &Config) -> Result<(), Error> { + CONFIG.save(storage, config).map_err(Error::from) } -pub(crate) fn may_load_outgoing_msg( + +pub(crate) fn load_outgoing_message( storage: &dyn Storage, - id: CrossChainId, -) -> Result, Error> { + cc_id: &CrossChainId, +) -> Result { OUTGOING_MESSAGES - .may_load(storage, id.clone()) - .change_context(Error::Parse(OUTGOING_MESSAGES_NAME)) - .attach_printable(id.to_string()) + .may_load(storage, cc_id) + .map_err(Error::from)? + .ok_or_else(|| Error::MessageNotFound(cc_id.clone())) } -#[derive(thiserror::Error, Debug)] -pub(crate) enum Error { - #[error("failed to save {0}")] - SaveValue(&'static str), - #[error("failed to load {0}")] - LoadValue(&'static str), - #[error("failed to parse key for {0}")] - Parse(&'static str), +pub(crate) fn save_outgoing_message( + storage: &mut dyn Storage, + cc_id: &CrossChainId, + msg: &Message, +) -> Result<(), Error> { + let existing = OUTGOING_MESSAGES + .may_load(storage, cc_id) + .map_err(Error::from)?; + match existing { + Some(existing) if msg.hash() != existing.hash() => { + Err(Error::MessageMismatch(msg.cc_id.clone())) + } + Some(_) => Ok(()), // new message is identical, no need to store it + None => Ok(OUTGOING_MESSAGES + .save(storage, cc_id, msg) + .map_err(Error::from)?), + } } -const CONFIG_NAME: &str = "config"; -const CONFIG: Item = Item::new(CONFIG_NAME); -const OUTGOING_MESSAGES_NAME: &str = "outgoing_messages"; -const OUTGOING_MESSAGES: Map = Map::new(OUTGOING_MESSAGES_NAME); - #[cfg(test)] mod test { use cosmwasm_std::testing::mock_dependencies; - use cosmwasm_std::Addr; use router_api::{CrossChainId, Message}; - use crate::state::{ - load_config, may_load_outgoing_msg, save_config, save_outgoing_msg, Config, - }; - - #[test] - fn config_storage() { - let mut deps = mock_dependencies(); - - let config = Config { - verifier: Addr::unchecked("verifier"), - router: Addr::unchecked("router"), - }; - assert!(save_config(deps.as_mut().storage, &config).is_ok()); - - assert_eq!(load_config(&deps.storage).unwrap(), config); - } + use crate::state::OUTGOING_MESSAGES; #[test] fn outgoing_messages_storage() { let mut deps = mock_dependencies(); let message = Message { - cc_id: CrossChainId { - chain: "chain".parse().unwrap(), - id: "id".parse().unwrap(), - }, - source_address: "source_address".parse().unwrap(), + cc_id: CrossChainId::new("chain", "id").unwrap(), + source_address: "source-address".parse().unwrap(), destination_chain: "destination".parse().unwrap(), - destination_address: "destination_address".parse().unwrap(), + destination_address: "destination-address".parse().unwrap(), payload_hash: [1; 32], }; - assert!(save_outgoing_msg(deps.as_mut().storage, message.cc_id.clone(), &message).is_ok()); + assert!(OUTGOING_MESSAGES + .save(deps.as_mut().storage, &message.cc_id, &message) + .is_ok()); assert_eq!( - may_load_outgoing_msg(&deps.storage, message.cc_id.clone()).unwrap(), + OUTGOING_MESSAGES + .may_load(&deps.storage, &message.cc_id) + .unwrap(), Some(message) ); - let unknown_chain_id = CrossChainId { - chain: "unknown".parse().unwrap(), - id: "id".parse().unwrap(), - }; + let unknown_chain_id = CrossChainId::new("unknown", "id").unwrap(); assert_eq!( - may_load_outgoing_msg(&deps.storage, unknown_chain_id).unwrap(), + OUTGOING_MESSAGES + .may_load(&deps.storage, &unknown_chain_id) + .unwrap(), None ); - let unknown_id = CrossChainId { - chain: "chain".parse().unwrap(), - id: "unknown".parse().unwrap(), - }; + let unknown_id = CrossChainId::new("chain", "unkown").unwrap(); assert_eq!( - may_load_outgoing_msg(&deps.storage, unknown_id).unwrap(), + OUTGOING_MESSAGES + .may_load(&deps.storage, &unknown_id) + .unwrap(), None ); } diff --git a/contracts/gateway/tests/contract.rs b/contracts/gateway/tests/contract.rs index 3e0992d6b..deeb2af49 100644 --- a/contracts/gateway/tests/contract.rs +++ b/contracts/gateway/tests/contract.rs @@ -3,20 +3,22 @@ use std::fmt::Debug; use std::fs::File; use std::iter; -use axelar_wasm_std::{ContractError, VerificationStatus}; +use axelar_wasm_std::error::ContractError; +use axelar_wasm_std::{err_contains, VerificationStatus}; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockQuerier}; #[cfg(not(feature = "generate_golden_files"))] use cosmwasm_std::Response; use cosmwasm_std::{ from_json, to_json_binary, Addr, ContractResult, DepsMut, QuerierResult, WasmQuery, }; -use itertools::Itertools; -use router_api::{CrossChainId, Message}; -use serde::Serialize; - use gateway::contract::*; use gateway::msg::InstantiateMsg; +use gateway::state; use gateway_api::msg::{ExecuteMsg, QueryMsg}; +use itertools::Itertools; +use rand::{thread_rng, Rng}; +use router_api::{CrossChainId, Message}; +use serde::Serialize; use voting_verifier::msg::MessageStatus; #[test] @@ -138,9 +140,8 @@ fn successful_route_outgoing() { let router = "router"; instantiate_contract(deps.as_mut(), "verifier", router); - let query_msg = QueryMsg::GetOutgoingMessages { - message_ids: msgs.iter().map(|msg| msg.cc_id.clone()).collect(), - }; + let query_msg = + QueryMsg::OutgoingMessages(msgs.iter().map(|msg| msg.cc_id.clone()).collect()); // check no messages are outgoing let query_response = query(deps.as_ref(), mock_env(), query_msg.clone()); @@ -281,6 +282,43 @@ fn route_duplicate_ids_should_fail() { } } +#[test] +fn reject_reroute_outgoing_message_with_different_contents() { + let mut msgs = generate_msgs(VerificationStatus::SucceededOnSourceChain, 10); + + let mut deps = mock_dependencies(); + + let router = "router"; + instantiate_contract(deps.as_mut(), "verifier", router); + + let response = execute( + deps.as_mut(), + mock_env(), + mock_info(router, &[]), + ExecuteMsg::RouteMessages(msgs.clone()), + ); + assert!(response.is_ok()); + + // re-route with different payload + msgs.iter_mut().for_each(|msg| { + let mut rng = thread_rng(); + msg.payload_hash.iter_mut().for_each(|byte| { + *byte = rng.gen(); + }); + }); + let response = execute( + deps.as_mut(), + mock_env(), + mock_info(router, &[]), + ExecuteMsg::RouteMessages(msgs.clone()), + ); + assert!(response.is_err_and(|err| err_contains!( + err.report, + state::Error, + state::Error::MessageMismatch { .. } + ))); +} + fn test_cases_for_correct_verifier() -> ( Vec>, impl Fn(voting_verifier::msg::QueryMsg) -> Result, ContractError> + Clone, @@ -345,10 +383,7 @@ fn generate_msgs_with_all_statuses( fn generate_msgs(namespace: impl Debug, count: u8) -> Vec { (0..count) .map(|i| Message { - cc_id: CrossChainId { - chain: "mock-chain".parse().unwrap(), - id: format!("{:?}{}", namespace, i).parse().unwrap(), - }, + cc_id: CrossChainId::new("mock-chain", format!("{:?}{}", namespace, i)).unwrap(), destination_address: "idc".parse().unwrap(), destination_chain: "mock-chain-2".parse().unwrap(), source_address: "idc".parse().unwrap(), @@ -401,7 +436,7 @@ fn correctly_working_verifier_handler( { move |msg: voting_verifier::msg::QueryMsg| -> Result, ContractError> { match msg { - voting_verifier::msg::QueryMsg::GetMessagesStatus { messages } => Ok(messages + voting_verifier::msg::QueryMsg::MessagesStatus(messages) => Ok(messages .into_iter() .map(|msg| { MessageStatus::new( diff --git a/contracts/gateway/tests/test_route_incoming.json b/contracts/gateway/tests/test_route_incoming.json index f69f36d33..f0b261d23 100644 --- a/contracts/gateway/tests/test_route_incoming.json +++ b/contracts/gateway/tests/test_route_incoming.json @@ -13,7 +13,7 @@ "wasm": { "execute": { "contract_addr": "router", - "msg": "eyJyb3V0ZV9tZXNzYWdlcyI6W3siY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifV19", + "msg": "eyJyb3V0ZV9tZXNzYWdlcyI6W3siY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifV19", "funds": [] } } @@ -28,7 +28,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain0" }, { @@ -36,7 +36,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -44,7 +44,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -64,7 +64,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain0" }, { @@ -72,7 +72,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -80,7 +80,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -100,7 +100,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain0" }, { @@ -108,7 +108,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -116,7 +116,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -136,7 +136,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify0" }, { @@ -144,7 +144,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -152,7 +152,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -172,7 +172,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress0" }, { @@ -180,7 +180,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -188,7 +188,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -208,7 +208,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown0" }, { @@ -216,7 +216,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -224,7 +224,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -244,7 +244,7 @@ "wasm": { "execute": { "contract_addr": "router", - "msg": "eyJyb3V0ZV9tZXNzYWdlcyI6W3siY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiU3VjY2VlZGVkT25Tb3VyY2VDaGFpbjEifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxIn0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IlN1Y2NlZWRlZE9uU291cmNlQ2hhaW4yIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMiJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluMyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiU3VjY2VlZGVkT25Tb3VyY2VDaGFpbjQifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0In0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IlN1Y2NlZWRlZE9uU291cmNlQ2hhaW41In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNSJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluNiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiU3VjY2VlZGVkT25Tb3VyY2VDaGFpbjcifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3In0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IlN1Y2NlZWRlZE9uU291cmNlQ2hhaW44In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwOCJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluOSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkifV19", + "msg": "eyJyb3V0ZV9tZXNzYWdlcyI6W3siY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiU3VjY2VlZGVkT25Tb3VyY2VDaGFpbjEifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxIn0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IlN1Y2NlZWRlZE9uU291cmNlQ2hhaW4yIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMiJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluMyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiU3VjY2VlZGVkT25Tb3VyY2VDaGFpbjQifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0In0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IlN1Y2NlZWRlZE9uU291cmNlQ2hhaW41In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNSJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluNiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiU3VjY2VlZGVkT25Tb3VyY2VDaGFpbjcifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3In0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IlN1Y2NlZWRlZE9uU291cmNlQ2hhaW44In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwOCJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluOSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkifV19", "funds": [] } } @@ -259,7 +259,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain0" }, { @@ -267,7 +267,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -275,7 +275,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -288,7 +288,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain1" }, { @@ -296,7 +296,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -304,7 +304,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -317,7 +317,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain2" }, { @@ -325,7 +325,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -333,7 +333,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -346,7 +346,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain3" }, { @@ -354,7 +354,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -362,7 +362,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -375,7 +375,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain4" }, { @@ -383,7 +383,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -391,7 +391,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -404,7 +404,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain5" }, { @@ -412,7 +412,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -420,7 +420,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -433,7 +433,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain6" }, { @@ -441,7 +441,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -449,7 +449,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -462,7 +462,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain7" }, { @@ -470,7 +470,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -478,7 +478,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -491,7 +491,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain8" }, { @@ -499,7 +499,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -507,7 +507,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -520,7 +520,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain9" }, { @@ -528,7 +528,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -536,7 +536,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -556,7 +556,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain0" }, { @@ -564,7 +564,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -572,7 +572,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -585,7 +585,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain1" }, { @@ -593,7 +593,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -601,7 +601,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -614,7 +614,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain2" }, { @@ -622,7 +622,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -630,7 +630,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -643,7 +643,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain3" }, { @@ -651,7 +651,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -659,7 +659,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -672,7 +672,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain4" }, { @@ -680,7 +680,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -688,7 +688,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -701,7 +701,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain5" }, { @@ -709,7 +709,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -717,7 +717,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -730,7 +730,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain6" }, { @@ -738,7 +738,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -746,7 +746,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -759,7 +759,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain7" }, { @@ -767,7 +767,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -775,7 +775,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -788,7 +788,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain8" }, { @@ -796,7 +796,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -804,7 +804,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -817,7 +817,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain9" }, { @@ -825,7 +825,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -833,7 +833,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -853,7 +853,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain0" }, { @@ -861,7 +861,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -869,7 +869,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -882,7 +882,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain1" }, { @@ -890,7 +890,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -898,7 +898,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -911,7 +911,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain2" }, { @@ -919,7 +919,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -927,7 +927,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -940,7 +940,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain3" }, { @@ -948,7 +948,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -956,7 +956,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -969,7 +969,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain4" }, { @@ -977,7 +977,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -985,7 +985,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -998,7 +998,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain5" }, { @@ -1006,7 +1006,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1014,7 +1014,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1027,7 +1027,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain6" }, { @@ -1035,7 +1035,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1043,7 +1043,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1056,7 +1056,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain7" }, { @@ -1064,7 +1064,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1072,7 +1072,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1085,7 +1085,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain8" }, { @@ -1093,7 +1093,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1101,7 +1101,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1114,7 +1114,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain9" }, { @@ -1122,7 +1122,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1130,7 +1130,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1150,7 +1150,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify0" }, { @@ -1158,7 +1158,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1166,7 +1166,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1179,7 +1179,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify1" }, { @@ -1187,7 +1187,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1195,7 +1195,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1208,7 +1208,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify2" }, { @@ -1216,7 +1216,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1224,7 +1224,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1237,7 +1237,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify3" }, { @@ -1245,7 +1245,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1253,7 +1253,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1266,7 +1266,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify4" }, { @@ -1274,7 +1274,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1282,7 +1282,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1295,7 +1295,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify5" }, { @@ -1303,7 +1303,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1311,7 +1311,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1324,7 +1324,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify6" }, { @@ -1332,7 +1332,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1340,7 +1340,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1353,7 +1353,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify7" }, { @@ -1361,7 +1361,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1369,7 +1369,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1382,7 +1382,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify8" }, { @@ -1390,7 +1390,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1398,7 +1398,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1411,7 +1411,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify9" }, { @@ -1419,7 +1419,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1427,7 +1427,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1447,7 +1447,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress0" }, { @@ -1455,7 +1455,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1463,7 +1463,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1476,7 +1476,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress1" }, { @@ -1484,7 +1484,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1492,7 +1492,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1505,7 +1505,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress2" }, { @@ -1513,7 +1513,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1521,7 +1521,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1534,7 +1534,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress3" }, { @@ -1542,7 +1542,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1550,7 +1550,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1563,7 +1563,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress4" }, { @@ -1571,7 +1571,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1579,7 +1579,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1592,7 +1592,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress5" }, { @@ -1600,7 +1600,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1608,7 +1608,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1621,7 +1621,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress6" }, { @@ -1629,7 +1629,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1637,7 +1637,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1650,7 +1650,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress7" }, { @@ -1658,7 +1658,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1666,7 +1666,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1679,7 +1679,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress8" }, { @@ -1687,7 +1687,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1695,7 +1695,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1708,7 +1708,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress9" }, { @@ -1716,7 +1716,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1724,7 +1724,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1744,7 +1744,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown0" }, { @@ -1752,7 +1752,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1760,7 +1760,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1773,7 +1773,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown1" }, { @@ -1781,7 +1781,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1789,7 +1789,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1802,7 +1802,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown2" }, { @@ -1810,7 +1810,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1818,7 +1818,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1831,7 +1831,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown3" }, { @@ -1839,7 +1839,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1847,7 +1847,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1860,7 +1860,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown4" }, { @@ -1868,7 +1868,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1876,7 +1876,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1889,7 +1889,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown5" }, { @@ -1897,7 +1897,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1905,7 +1905,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1918,7 +1918,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown6" }, { @@ -1926,7 +1926,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1934,7 +1934,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1947,7 +1947,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown7" }, { @@ -1955,7 +1955,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1963,7 +1963,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1976,7 +1976,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown8" }, { @@ -1984,7 +1984,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1992,7 +1992,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2005,7 +2005,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown9" }, { @@ -2013,7 +2013,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2021,7 +2021,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2041,7 +2041,7 @@ "wasm": { "execute": { "contract_addr": "router", - "msg": "eyJyb3V0ZV9tZXNzYWdlcyI6W3siY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiU3VjY2VlZGVkT25Tb3VyY2VDaGFpbjEifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxIn0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IlN1Y2NlZWRlZE9uU291cmNlQ2hhaW4yIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMiJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluMyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiU3VjY2VlZGVkT25Tb3VyY2VDaGFpbjQifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0In0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IlN1Y2NlZWRlZE9uU291cmNlQ2hhaW41In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNSJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluNiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiU3VjY2VlZGVkT25Tb3VyY2VDaGFpbjcifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3In0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IlN1Y2NlZWRlZE9uU291cmNlQ2hhaW44In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwOCJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluOSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkifV19", + "msg": "eyJyb3V0ZV9tZXNzYWdlcyI6W3siY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiU3VjY2VlZGVkT25Tb3VyY2VDaGFpbjEifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxIn0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IlN1Y2NlZWRlZE9uU291cmNlQ2hhaW4yIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMiJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluMyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiU3VjY2VlZGVkT25Tb3VyY2VDaGFpbjQifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0In0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IlN1Y2NlZWRlZE9uU291cmNlQ2hhaW41In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNSJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluNiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiU3VjY2VlZGVkT25Tb3VyY2VDaGFpbjcifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3In0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IlN1Y2NlZWRlZE9uU291cmNlQ2hhaW44In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwOCJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJTdWNjZWVkZWRPblNvdXJjZUNoYWluOSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkifV19", "funds": [] } } @@ -2056,7 +2056,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain0" }, { @@ -2064,7 +2064,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2072,7 +2072,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2085,7 +2085,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain1" }, { @@ -2093,7 +2093,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2101,7 +2101,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2114,7 +2114,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain2" }, { @@ -2122,7 +2122,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2130,7 +2130,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2143,7 +2143,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain3" }, { @@ -2151,7 +2151,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2159,7 +2159,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2172,7 +2172,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain4" }, { @@ -2180,7 +2180,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2188,7 +2188,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2201,7 +2201,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain5" }, { @@ -2209,7 +2209,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2217,7 +2217,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2230,7 +2230,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain6" }, { @@ -2238,7 +2238,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2246,7 +2246,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2259,7 +2259,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain7" }, { @@ -2267,7 +2267,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2275,7 +2275,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2288,7 +2288,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain8" }, { @@ -2296,7 +2296,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2304,7 +2304,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2317,7 +2317,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain9" }, { @@ -2325,7 +2325,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2333,7 +2333,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2346,7 +2346,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain0" }, { @@ -2354,7 +2354,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2362,7 +2362,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2375,7 +2375,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain1" }, { @@ -2383,7 +2383,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2391,7 +2391,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2404,7 +2404,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain2" }, { @@ -2412,7 +2412,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2420,7 +2420,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2433,7 +2433,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain3" }, { @@ -2441,7 +2441,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2449,7 +2449,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2462,7 +2462,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain4" }, { @@ -2470,7 +2470,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2478,7 +2478,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2491,7 +2491,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain5" }, { @@ -2499,7 +2499,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2507,7 +2507,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2520,7 +2520,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain6" }, { @@ -2528,7 +2528,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2536,7 +2536,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2549,7 +2549,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain7" }, { @@ -2557,7 +2557,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2565,7 +2565,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2578,7 +2578,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain8" }, { @@ -2586,7 +2586,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2594,7 +2594,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2607,7 +2607,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain9" }, { @@ -2615,7 +2615,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2623,7 +2623,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2636,7 +2636,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain0" }, { @@ -2644,7 +2644,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2652,7 +2652,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2665,7 +2665,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain1" }, { @@ -2673,7 +2673,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2681,7 +2681,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2694,7 +2694,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain2" }, { @@ -2702,7 +2702,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2710,7 +2710,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2723,7 +2723,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain3" }, { @@ -2731,7 +2731,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2739,7 +2739,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2752,7 +2752,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain4" }, { @@ -2760,7 +2760,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2768,7 +2768,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2781,7 +2781,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain5" }, { @@ -2789,7 +2789,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2797,7 +2797,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2810,7 +2810,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain6" }, { @@ -2818,7 +2818,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2826,7 +2826,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2839,7 +2839,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain7" }, { @@ -2847,7 +2847,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2855,7 +2855,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2868,7 +2868,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain8" }, { @@ -2876,7 +2876,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2884,7 +2884,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2897,7 +2897,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain9" }, { @@ -2905,7 +2905,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2913,7 +2913,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2926,7 +2926,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify0" }, { @@ -2934,7 +2934,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2942,7 +2942,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2955,7 +2955,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify1" }, { @@ -2963,7 +2963,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2971,7 +2971,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2984,7 +2984,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify2" }, { @@ -2992,7 +2992,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3000,7 +3000,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3013,7 +3013,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify3" }, { @@ -3021,7 +3021,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3029,7 +3029,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3042,7 +3042,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify4" }, { @@ -3050,7 +3050,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3058,7 +3058,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3071,7 +3071,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify5" }, { @@ -3079,7 +3079,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3087,7 +3087,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3100,7 +3100,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify6" }, { @@ -3108,7 +3108,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3116,7 +3116,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3129,7 +3129,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify7" }, { @@ -3137,7 +3137,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3145,7 +3145,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3158,7 +3158,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify8" }, { @@ -3166,7 +3166,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3174,7 +3174,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3187,7 +3187,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify9" }, { @@ -3195,7 +3195,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3203,7 +3203,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3216,7 +3216,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress0" }, { @@ -3224,7 +3224,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3232,7 +3232,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3245,7 +3245,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress1" }, { @@ -3253,7 +3253,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3261,7 +3261,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3274,7 +3274,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress2" }, { @@ -3282,7 +3282,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3290,7 +3290,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3303,7 +3303,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress3" }, { @@ -3311,7 +3311,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3319,7 +3319,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3332,7 +3332,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress4" }, { @@ -3340,7 +3340,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3348,7 +3348,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3361,7 +3361,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress5" }, { @@ -3369,7 +3369,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3377,7 +3377,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3390,7 +3390,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress6" }, { @@ -3398,7 +3398,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3406,7 +3406,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3419,7 +3419,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress7" }, { @@ -3427,7 +3427,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3435,7 +3435,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3448,7 +3448,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress8" }, { @@ -3456,7 +3456,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3464,7 +3464,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3477,7 +3477,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress9" }, { @@ -3485,7 +3485,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3493,7 +3493,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3506,7 +3506,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown0" }, { @@ -3514,7 +3514,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3522,7 +3522,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3535,7 +3535,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown1" }, { @@ -3543,7 +3543,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3551,7 +3551,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3564,7 +3564,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown2" }, { @@ -3572,7 +3572,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3580,7 +3580,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3593,7 +3593,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown3" }, { @@ -3601,7 +3601,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3609,7 +3609,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3622,7 +3622,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown4" }, { @@ -3630,7 +3630,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3638,7 +3638,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3651,7 +3651,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown5" }, { @@ -3659,7 +3659,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3667,7 +3667,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3680,7 +3680,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown6" }, { @@ -3688,7 +3688,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3696,7 +3696,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3709,7 +3709,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown7" }, { @@ -3717,7 +3717,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3725,7 +3725,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3738,7 +3738,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown8" }, { @@ -3746,7 +3746,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3754,7 +3754,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3767,7 +3767,7 @@ "type": "unfit_for_routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown9" }, { @@ -3775,7 +3775,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3783,7 +3783,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { diff --git a/contracts/gateway/tests/test_route_outgoing.json b/contracts/gateway/tests/test_route_outgoing.json index 030d0a533..6590430c4 100644 --- a/contracts/gateway/tests/test_route_outgoing.json +++ b/contracts/gateway/tests/test_route_outgoing.json @@ -13,7 +13,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain0" }, { @@ -21,7 +21,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -29,7 +29,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -49,7 +49,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain0" }, { @@ -57,7 +57,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -65,7 +65,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -85,7 +85,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain0" }, { @@ -93,7 +93,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -101,7 +101,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -121,7 +121,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify0" }, { @@ -129,7 +129,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -137,7 +137,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -157,7 +157,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress0" }, { @@ -165,7 +165,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -173,7 +173,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -193,7 +193,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown0" }, { @@ -201,7 +201,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -209,7 +209,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -229,7 +229,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain0" }, { @@ -237,7 +237,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -245,7 +245,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -258,7 +258,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain1" }, { @@ -266,7 +266,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -274,7 +274,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -287,7 +287,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain2" }, { @@ -295,7 +295,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -303,7 +303,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -316,7 +316,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain3" }, { @@ -324,7 +324,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -332,7 +332,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -345,7 +345,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain4" }, { @@ -353,7 +353,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -361,7 +361,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -374,7 +374,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain5" }, { @@ -382,7 +382,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -390,7 +390,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -403,7 +403,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain6" }, { @@ -411,7 +411,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -419,7 +419,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -432,7 +432,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain7" }, { @@ -440,7 +440,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -448,7 +448,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -461,7 +461,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain8" }, { @@ -469,7 +469,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -477,7 +477,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -490,7 +490,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain9" }, { @@ -498,7 +498,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -506,7 +506,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -526,7 +526,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain0" }, { @@ -534,7 +534,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -542,7 +542,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -555,7 +555,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain1" }, { @@ -563,7 +563,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -571,7 +571,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -584,7 +584,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain2" }, { @@ -592,7 +592,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -600,7 +600,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -613,7 +613,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain3" }, { @@ -621,7 +621,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -629,7 +629,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -642,7 +642,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain4" }, { @@ -650,7 +650,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -658,7 +658,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -671,7 +671,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain5" }, { @@ -679,7 +679,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -687,7 +687,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -700,7 +700,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain6" }, { @@ -708,7 +708,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -716,7 +716,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -729,7 +729,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain7" }, { @@ -737,7 +737,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -745,7 +745,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -758,7 +758,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain8" }, { @@ -766,7 +766,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -774,7 +774,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -787,7 +787,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain9" }, { @@ -795,7 +795,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -803,7 +803,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -823,7 +823,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain0" }, { @@ -831,7 +831,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -839,7 +839,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -852,7 +852,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain1" }, { @@ -860,7 +860,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -868,7 +868,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -881,7 +881,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain2" }, { @@ -889,7 +889,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -897,7 +897,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -910,7 +910,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain3" }, { @@ -918,7 +918,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -926,7 +926,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -939,7 +939,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain4" }, { @@ -947,7 +947,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -955,7 +955,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -968,7 +968,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain5" }, { @@ -976,7 +976,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -984,7 +984,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -997,7 +997,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain6" }, { @@ -1005,7 +1005,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1013,7 +1013,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1026,7 +1026,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain7" }, { @@ -1034,7 +1034,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1042,7 +1042,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1055,7 +1055,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain8" }, { @@ -1063,7 +1063,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1071,7 +1071,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1084,7 +1084,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain9" }, { @@ -1092,7 +1092,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1100,7 +1100,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1120,7 +1120,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify0" }, { @@ -1128,7 +1128,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1136,7 +1136,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1149,7 +1149,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify1" }, { @@ -1157,7 +1157,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1165,7 +1165,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1178,7 +1178,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify2" }, { @@ -1186,7 +1186,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1194,7 +1194,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1207,7 +1207,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify3" }, { @@ -1215,7 +1215,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1223,7 +1223,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1236,7 +1236,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify4" }, { @@ -1244,7 +1244,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1252,7 +1252,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1265,7 +1265,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify5" }, { @@ -1273,7 +1273,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1281,7 +1281,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1294,7 +1294,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify6" }, { @@ -1302,7 +1302,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1310,7 +1310,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1323,7 +1323,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify7" }, { @@ -1331,7 +1331,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1339,7 +1339,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1352,7 +1352,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify8" }, { @@ -1360,7 +1360,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1368,7 +1368,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1381,7 +1381,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify9" }, { @@ -1389,7 +1389,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1397,7 +1397,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1417,7 +1417,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress0" }, { @@ -1425,7 +1425,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1433,7 +1433,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1446,7 +1446,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress1" }, { @@ -1454,7 +1454,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1462,7 +1462,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1475,7 +1475,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress2" }, { @@ -1483,7 +1483,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1491,7 +1491,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1504,7 +1504,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress3" }, { @@ -1512,7 +1512,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1520,7 +1520,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1533,7 +1533,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress4" }, { @@ -1541,7 +1541,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1549,7 +1549,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1562,7 +1562,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress5" }, { @@ -1570,7 +1570,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1578,7 +1578,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1591,7 +1591,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress6" }, { @@ -1599,7 +1599,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1607,7 +1607,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1620,7 +1620,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress7" }, { @@ -1628,7 +1628,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1636,7 +1636,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1649,7 +1649,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress8" }, { @@ -1657,7 +1657,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1665,7 +1665,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1678,7 +1678,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress9" }, { @@ -1686,7 +1686,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1694,7 +1694,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1714,7 +1714,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown0" }, { @@ -1722,7 +1722,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1730,7 +1730,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1743,7 +1743,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown1" }, { @@ -1751,7 +1751,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1759,7 +1759,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1772,7 +1772,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown2" }, { @@ -1780,7 +1780,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1788,7 +1788,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1801,7 +1801,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown3" }, { @@ -1809,7 +1809,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1817,7 +1817,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1830,7 +1830,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown4" }, { @@ -1838,7 +1838,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1846,7 +1846,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1859,7 +1859,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown5" }, { @@ -1867,7 +1867,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1875,7 +1875,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1888,7 +1888,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown6" }, { @@ -1896,7 +1896,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1904,7 +1904,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1917,7 +1917,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown7" }, { @@ -1925,7 +1925,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1933,7 +1933,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1946,7 +1946,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown8" }, { @@ -1954,7 +1954,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1962,7 +1962,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1975,7 +1975,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown9" }, { @@ -1983,7 +1983,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1991,7 +1991,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2011,7 +2011,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain0" }, { @@ -2019,7 +2019,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2027,7 +2027,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2040,7 +2040,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain1" }, { @@ -2048,7 +2048,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2056,7 +2056,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2069,7 +2069,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain2" }, { @@ -2077,7 +2077,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2085,7 +2085,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2098,7 +2098,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain3" }, { @@ -2106,7 +2106,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2114,7 +2114,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2127,7 +2127,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain4" }, { @@ -2135,7 +2135,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2143,7 +2143,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2156,7 +2156,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain5" }, { @@ -2164,7 +2164,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2172,7 +2172,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2185,7 +2185,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain6" }, { @@ -2193,7 +2193,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2201,7 +2201,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2214,7 +2214,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain7" }, { @@ -2222,7 +2222,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2230,7 +2230,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2243,7 +2243,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain8" }, { @@ -2251,7 +2251,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2259,7 +2259,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2272,7 +2272,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain9" }, { @@ -2280,7 +2280,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2288,7 +2288,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2301,7 +2301,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain0" }, { @@ -2309,7 +2309,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2317,7 +2317,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2330,7 +2330,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain1" }, { @@ -2338,7 +2338,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2346,7 +2346,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2359,7 +2359,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain2" }, { @@ -2367,7 +2367,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2375,7 +2375,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2388,7 +2388,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain3" }, { @@ -2396,7 +2396,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2404,7 +2404,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2417,7 +2417,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain4" }, { @@ -2425,7 +2425,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2433,7 +2433,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2446,7 +2446,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain5" }, { @@ -2454,7 +2454,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2462,7 +2462,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2475,7 +2475,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain6" }, { @@ -2483,7 +2483,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2491,7 +2491,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2504,7 +2504,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain7" }, { @@ -2512,7 +2512,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2520,7 +2520,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2533,7 +2533,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain8" }, { @@ -2541,7 +2541,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2549,7 +2549,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2562,7 +2562,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain9" }, { @@ -2570,7 +2570,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2578,7 +2578,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2591,7 +2591,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain0" }, { @@ -2599,7 +2599,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2607,7 +2607,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2620,7 +2620,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain1" }, { @@ -2628,7 +2628,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2636,7 +2636,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2649,7 +2649,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain2" }, { @@ -2657,7 +2657,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2665,7 +2665,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2678,7 +2678,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain3" }, { @@ -2686,7 +2686,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2694,7 +2694,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2707,7 +2707,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain4" }, { @@ -2715,7 +2715,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2723,7 +2723,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2736,7 +2736,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain5" }, { @@ -2744,7 +2744,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2752,7 +2752,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2765,7 +2765,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain6" }, { @@ -2773,7 +2773,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2781,7 +2781,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2794,7 +2794,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain7" }, { @@ -2802,7 +2802,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2810,7 +2810,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2823,7 +2823,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain8" }, { @@ -2831,7 +2831,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2839,7 +2839,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2852,7 +2852,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain9" }, { @@ -2860,7 +2860,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2868,7 +2868,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2881,7 +2881,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify0" }, { @@ -2889,7 +2889,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2897,7 +2897,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2910,7 +2910,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify1" }, { @@ -2918,7 +2918,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2926,7 +2926,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2939,7 +2939,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify2" }, { @@ -2947,7 +2947,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2955,7 +2955,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2968,7 +2968,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify3" }, { @@ -2976,7 +2976,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2984,7 +2984,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2997,7 +2997,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify4" }, { @@ -3005,7 +3005,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3013,7 +3013,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3026,7 +3026,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify5" }, { @@ -3034,7 +3034,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3042,7 +3042,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3055,7 +3055,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify6" }, { @@ -3063,7 +3063,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3071,7 +3071,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3084,7 +3084,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify7" }, { @@ -3092,7 +3092,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3100,7 +3100,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3113,7 +3113,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify8" }, { @@ -3121,7 +3121,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3129,7 +3129,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3142,7 +3142,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify9" }, { @@ -3150,7 +3150,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3158,7 +3158,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3171,7 +3171,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress0" }, { @@ -3179,7 +3179,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3187,7 +3187,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3200,7 +3200,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress1" }, { @@ -3208,7 +3208,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3216,7 +3216,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3229,7 +3229,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress2" }, { @@ -3237,7 +3237,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3245,7 +3245,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3258,7 +3258,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress3" }, { @@ -3266,7 +3266,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3274,7 +3274,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3287,7 +3287,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress4" }, { @@ -3295,7 +3295,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3303,7 +3303,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3316,7 +3316,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress5" }, { @@ -3324,7 +3324,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3332,7 +3332,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3345,7 +3345,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress6" }, { @@ -3353,7 +3353,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3361,7 +3361,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3374,7 +3374,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress7" }, { @@ -3382,7 +3382,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3390,7 +3390,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3403,7 +3403,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress8" }, { @@ -3411,7 +3411,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3419,7 +3419,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3432,7 +3432,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress9" }, { @@ -3440,7 +3440,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3448,7 +3448,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3461,7 +3461,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown0" }, { @@ -3469,7 +3469,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3477,7 +3477,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3490,7 +3490,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown1" }, { @@ -3498,7 +3498,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3506,7 +3506,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3519,7 +3519,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown2" }, { @@ -3527,7 +3527,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3535,7 +3535,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3548,7 +3548,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown3" }, { @@ -3556,7 +3556,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3564,7 +3564,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3577,7 +3577,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown4" }, { @@ -3585,7 +3585,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3593,7 +3593,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3606,7 +3606,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown5" }, { @@ -3614,7 +3614,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3622,7 +3622,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3635,7 +3635,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown6" }, { @@ -3643,7 +3643,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3651,7 +3651,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3664,7 +3664,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown7" }, { @@ -3672,7 +3672,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3680,7 +3680,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3693,7 +3693,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown8" }, { @@ -3701,7 +3701,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3709,7 +3709,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3722,7 +3722,7 @@ "type": "routing", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown9" }, { @@ -3730,7 +3730,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3738,7 +3738,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { diff --git a/contracts/gateway/tests/test_verify.json b/contracts/gateway/tests/test_verify.json index 0b37c440e..1cf376c00 100644 --- a/contracts/gateway/tests/test_verify.json +++ b/contracts/gateway/tests/test_verify.json @@ -13,7 +13,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain0" }, { @@ -21,7 +21,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -29,7 +29,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -49,7 +49,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain0" }, { @@ -57,7 +57,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -65,7 +65,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -85,7 +85,7 @@ "wasm": { "execute": { "contract_addr": "verifier", - "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOnsibWVzc2FnZXMiOlt7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifV19fQ==", + "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOlt7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifV19", "funds": [] } } @@ -100,7 +100,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain0" }, { @@ -108,7 +108,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -116,7 +116,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -136,7 +136,7 @@ "wasm": { "execute": { "contract_addr": "verifier", - "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOnsibWVzc2FnZXMiOlt7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiRmFpbGVkVG9WZXJpZnkwIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCJ9XX19", + "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOlt7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiRmFpbGVkVG9WZXJpZnkwIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCJ9XX0=", "funds": [] } } @@ -151,7 +151,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify0" }, { @@ -159,7 +159,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -167,7 +167,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -187,7 +187,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress0" }, { @@ -195,7 +195,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -203,7 +203,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -223,7 +223,7 @@ "wasm": { "execute": { "contract_addr": "verifier", - "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOnsibWVzc2FnZXMiOlt7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiVW5rbm93bjAifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIn1dfX0=", + "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOlt7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiVW5rbm93bjAifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIn1dfQ==", "funds": [] } } @@ -238,7 +238,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown0" }, { @@ -246,7 +246,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -254,7 +254,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -274,7 +274,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain0" }, { @@ -282,7 +282,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -290,7 +290,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -303,7 +303,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain1" }, { @@ -311,7 +311,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -319,7 +319,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -332,7 +332,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain2" }, { @@ -340,7 +340,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -348,7 +348,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -361,7 +361,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain3" }, { @@ -369,7 +369,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -377,7 +377,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -390,7 +390,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain4" }, { @@ -398,7 +398,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -406,7 +406,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -419,7 +419,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain5" }, { @@ -427,7 +427,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -435,7 +435,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -448,7 +448,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain6" }, { @@ -456,7 +456,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -464,7 +464,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -477,7 +477,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain7" }, { @@ -485,7 +485,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -493,7 +493,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -506,7 +506,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain8" }, { @@ -514,7 +514,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -522,7 +522,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -535,7 +535,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain9" }, { @@ -543,7 +543,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -551,7 +551,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -571,7 +571,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain0" }, { @@ -579,7 +579,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -587,7 +587,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -600,7 +600,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain1" }, { @@ -608,7 +608,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -616,7 +616,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -629,7 +629,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain2" }, { @@ -637,7 +637,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -645,7 +645,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -658,7 +658,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain3" }, { @@ -666,7 +666,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -674,7 +674,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -687,7 +687,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain4" }, { @@ -695,7 +695,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -703,7 +703,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -716,7 +716,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain5" }, { @@ -724,7 +724,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -732,7 +732,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -745,7 +745,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain6" }, { @@ -753,7 +753,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -761,7 +761,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -774,7 +774,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain7" }, { @@ -782,7 +782,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -790,7 +790,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -803,7 +803,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain8" }, { @@ -811,7 +811,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -819,7 +819,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -832,7 +832,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain9" }, { @@ -840,7 +840,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -848,7 +848,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -868,7 +868,7 @@ "wasm": { "execute": { "contract_addr": "verifier", - "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOnsibWVzc2FnZXMiOlt7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluOCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluOSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkifV19fQ==", + "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOlt7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluOCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluOSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkifV19", "funds": [] } } @@ -883,7 +883,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain0" }, { @@ -891,7 +891,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -899,7 +899,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -912,7 +912,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain1" }, { @@ -920,7 +920,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -928,7 +928,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -941,7 +941,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain2" }, { @@ -949,7 +949,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -957,7 +957,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -970,7 +970,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain3" }, { @@ -978,7 +978,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -986,7 +986,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -999,7 +999,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain4" }, { @@ -1007,7 +1007,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1015,7 +1015,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1028,7 +1028,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain5" }, { @@ -1036,7 +1036,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1044,7 +1044,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1057,7 +1057,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain6" }, { @@ -1065,7 +1065,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1073,7 +1073,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1086,7 +1086,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain7" }, { @@ -1094,7 +1094,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1102,7 +1102,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1115,7 +1115,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain8" }, { @@ -1123,7 +1123,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1131,7 +1131,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1144,7 +1144,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain9" }, { @@ -1152,7 +1152,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1160,7 +1160,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1180,7 +1180,7 @@ "wasm": { "execute": { "contract_addr": "verifier", - "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOnsibWVzc2FnZXMiOlt7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiRmFpbGVkVG9WZXJpZnkwIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJGYWlsZWRUb1ZlcmlmeTEifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxIn0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IkZhaWxlZFRvVmVyaWZ5MiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiRmFpbGVkVG9WZXJpZnkzIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMyJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJGYWlsZWRUb1ZlcmlmeTQifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0In0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IkZhaWxlZFRvVmVyaWZ5NSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiRmFpbGVkVG9WZXJpZnk2In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNiJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJGYWlsZWRUb1ZlcmlmeTcifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3In0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IkZhaWxlZFRvVmVyaWZ5OCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiRmFpbGVkVG9WZXJpZnk5In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOSJ9XX19", + "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOlt7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiRmFpbGVkVG9WZXJpZnkwIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJGYWlsZWRUb1ZlcmlmeTEifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxIn0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IkZhaWxlZFRvVmVyaWZ5MiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiRmFpbGVkVG9WZXJpZnkzIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMyJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJGYWlsZWRUb1ZlcmlmeTQifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0In0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IkZhaWxlZFRvVmVyaWZ5NSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiRmFpbGVkVG9WZXJpZnk2In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNiJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJGYWlsZWRUb1ZlcmlmeTcifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3In0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IkZhaWxlZFRvVmVyaWZ5OCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiRmFpbGVkVG9WZXJpZnk5In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOSJ9XX0=", "funds": [] } } @@ -1195,7 +1195,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify0" }, { @@ -1203,7 +1203,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1211,7 +1211,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1224,7 +1224,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify1" }, { @@ -1232,7 +1232,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1240,7 +1240,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1253,7 +1253,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify2" }, { @@ -1261,7 +1261,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1269,7 +1269,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1282,7 +1282,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify3" }, { @@ -1290,7 +1290,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1298,7 +1298,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1311,7 +1311,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify4" }, { @@ -1319,7 +1319,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1327,7 +1327,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1340,7 +1340,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify5" }, { @@ -1348,7 +1348,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1356,7 +1356,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1369,7 +1369,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify6" }, { @@ -1377,7 +1377,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1385,7 +1385,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1398,7 +1398,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify7" }, { @@ -1406,7 +1406,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1414,7 +1414,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1427,7 +1427,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify8" }, { @@ -1435,7 +1435,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1443,7 +1443,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1456,7 +1456,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify9" }, { @@ -1464,7 +1464,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1472,7 +1472,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1492,7 +1492,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress0" }, { @@ -1500,7 +1500,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1508,7 +1508,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1521,7 +1521,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress1" }, { @@ -1529,7 +1529,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1537,7 +1537,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1550,7 +1550,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress2" }, { @@ -1558,7 +1558,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1566,7 +1566,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1579,7 +1579,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress3" }, { @@ -1587,7 +1587,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1595,7 +1595,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1608,7 +1608,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress4" }, { @@ -1616,7 +1616,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1624,7 +1624,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1637,7 +1637,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress5" }, { @@ -1645,7 +1645,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1653,7 +1653,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1666,7 +1666,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress6" }, { @@ -1674,7 +1674,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1682,7 +1682,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1695,7 +1695,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress7" }, { @@ -1703,7 +1703,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1711,7 +1711,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1724,7 +1724,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress8" }, { @@ -1732,7 +1732,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1740,7 +1740,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1753,7 +1753,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress9" }, { @@ -1761,7 +1761,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1769,7 +1769,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1789,7 +1789,7 @@ "wasm": { "execute": { "contract_addr": "verifier", - "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOnsibWVzc2FnZXMiOlt7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiVW5rbm93bjAifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIn0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IlVua25vd24xIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMSJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJVbmtub3duMiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiVW5rbm93bjMifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzIn0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IlVua25vd240In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNCJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJVbmtub3duNSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiVW5rbm93bjYifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2In0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IlVua25vd243In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNyJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJVbmtub3duOCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiVW5rbm93bjkifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5In1dfX0=", + "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOlt7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiVW5rbm93bjAifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIn0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IlVua25vd24xIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMSJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJVbmtub3duMiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiVW5rbm93bjMifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzIn0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IlVua25vd240In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNCJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJVbmtub3duNSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiVW5rbm93bjYifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2In0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IlVua25vd243In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNyJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJVbmtub3duOCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiVW5rbm93bjkifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5In1dfQ==", "funds": [] } } @@ -1804,7 +1804,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown0" }, { @@ -1812,7 +1812,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1820,7 +1820,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1833,7 +1833,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown1" }, { @@ -1841,7 +1841,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1849,7 +1849,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1862,7 +1862,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown2" }, { @@ -1870,7 +1870,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1878,7 +1878,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1891,7 +1891,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown3" }, { @@ -1899,7 +1899,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1907,7 +1907,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1920,7 +1920,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown4" }, { @@ -1928,7 +1928,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1936,7 +1936,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1949,7 +1949,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown5" }, { @@ -1957,7 +1957,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1965,7 +1965,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -1978,7 +1978,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown6" }, { @@ -1986,7 +1986,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -1994,7 +1994,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2007,7 +2007,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown7" }, { @@ -2015,7 +2015,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2023,7 +2023,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2036,7 +2036,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown8" }, { @@ -2044,7 +2044,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2052,7 +2052,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2065,7 +2065,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown9" }, { @@ -2073,7 +2073,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2081,7 +2081,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2101,7 +2101,7 @@ "wasm": { "execute": { "contract_addr": "verifier", - "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOnsibWVzc2FnZXMiOlt7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluOCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluOSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiRmFpbGVkVG9WZXJpZnkwIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJGYWlsZWRUb1ZlcmlmeTEifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxIn0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IkZhaWxlZFRvVmVyaWZ5MiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiRmFpbGVkVG9WZXJpZnkzIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMyJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJGYWlsZWRUb1ZlcmlmeTQifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0In0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IkZhaWxlZFRvVmVyaWZ5NSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiRmFpbGVkVG9WZXJpZnk2In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNiJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJGYWlsZWRUb1ZlcmlmeTcifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3In0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IkZhaWxlZFRvVmVyaWZ5OCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiRmFpbGVkVG9WZXJpZnk5In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOSJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJVbmtub3duMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiVW5rbm93bjEifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxIn0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IlVua25vd24yIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMiJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJVbmtub3duMyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiVW5rbm93bjQifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0In0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IlVua25vd241In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNSJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJVbmtub3duNiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYifSx7ImNjX2lkIjp7ImNoYWluIjoibW9jay1jaGFpbiIsImlkIjoiVW5rbm93bjcifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3In0seyJjY19pZCI6eyJjaGFpbiI6Im1vY2stY2hhaW4iLCJpZCI6IlVua25vd244In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwOCJ9LHsiY2NfaWQiOnsiY2hhaW4iOiJtb2NrLWNoYWluIiwiaWQiOiJVbmtub3duOSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkifV19fQ==", + "msg": "eyJ2ZXJpZnlfbWVzc2FnZXMiOlt7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluMyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluNyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluOCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiTm90Rm91bmRPblNvdXJjZUNoYWluOSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiRmFpbGVkVG9WZXJpZnkwIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJGYWlsZWRUb1ZlcmlmeTEifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxIn0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IkZhaWxlZFRvVmVyaWZ5MiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiRmFpbGVkVG9WZXJpZnkzIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMyJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJGYWlsZWRUb1ZlcmlmeTQifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0In0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IkZhaWxlZFRvVmVyaWZ5NSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiRmFpbGVkVG9WZXJpZnk2In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNiJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJGYWlsZWRUb1ZlcmlmeTcifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3In0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IkZhaWxlZFRvVmVyaWZ5OCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiRmFpbGVkVG9WZXJpZnk5In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOSJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJVbmtub3duMCJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiVW5rbm93bjEifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxIn0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IlVua25vd24yIn0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMiJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJVbmtub3duMyJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiVW5rbm93bjQifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0MDQwNDA0In0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IlVua25vd241In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNTA1MDUwNSJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJVbmtub3duNiJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYwNjA2MDYifSx7ImNjX2lkIjp7InNvdXJjZV9jaGFpbiI6Im1vY2stY2hhaW4iLCJtZXNzYWdlX2lkIjoiVW5rbm93bjcifSwic291cmNlX2FkZHJlc3MiOiJpZGMiLCJkZXN0aW5hdGlvbl9jaGFpbiI6Im1vY2stY2hhaW4tMiIsImRlc3RpbmF0aW9uX2FkZHJlc3MiOiJpZGMiLCJwYXlsb2FkX2hhc2giOiIwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3MDcwNzA3In0seyJjY19pZCI6eyJzb3VyY2VfY2hhaW4iOiJtb2NrLWNoYWluIiwibWVzc2FnZV9pZCI6IlVua25vd244In0sInNvdXJjZV9hZGRyZXNzIjoiaWRjIiwiZGVzdGluYXRpb25fY2hhaW4iOiJtb2NrLWNoYWluLTIiLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiaWRjIiwicGF5bG9hZF9oYXNoIjoiMDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwODA4MDgwOCJ9LHsiY2NfaWQiOnsic291cmNlX2NoYWluIjoibW9jay1jaGFpbiIsIm1lc3NhZ2VfaWQiOiJVbmtub3duOSJ9LCJzb3VyY2VfYWRkcmVzcyI6ImlkYyIsImRlc3RpbmF0aW9uX2NoYWluIjoibW9jay1jaGFpbi0yIiwiZGVzdGluYXRpb25fYWRkcmVzcyI6ImlkYyIsInBheWxvYWRfaGFzaCI6IjA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkwOTA5MDkifV19", "funds": [] } } @@ -2116,7 +2116,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain0" }, { @@ -2124,7 +2124,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2132,7 +2132,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2145,7 +2145,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain1" }, { @@ -2153,7 +2153,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2161,7 +2161,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2174,7 +2174,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain2" }, { @@ -2182,7 +2182,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2190,7 +2190,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2203,7 +2203,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain3" }, { @@ -2211,7 +2211,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2219,7 +2219,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2232,7 +2232,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain4" }, { @@ -2240,7 +2240,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2248,7 +2248,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2261,7 +2261,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain5" }, { @@ -2269,7 +2269,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2277,7 +2277,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2290,7 +2290,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain6" }, { @@ -2298,7 +2298,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2306,7 +2306,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2319,7 +2319,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain7" }, { @@ -2327,7 +2327,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2335,7 +2335,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2348,7 +2348,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain8" }, { @@ -2356,7 +2356,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2364,7 +2364,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2377,7 +2377,7 @@ "type": "already_verified", "attributes": [ { - "key": "id", + "key": "message_id", "value": "SucceededOnSourceChain9" }, { @@ -2385,7 +2385,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2393,7 +2393,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2406,7 +2406,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain0" }, { @@ -2414,7 +2414,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2422,7 +2422,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2435,7 +2435,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain1" }, { @@ -2443,7 +2443,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2451,7 +2451,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2464,7 +2464,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain2" }, { @@ -2472,7 +2472,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2480,7 +2480,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2493,7 +2493,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain3" }, { @@ -2501,7 +2501,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2509,7 +2509,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2522,7 +2522,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain4" }, { @@ -2530,7 +2530,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2538,7 +2538,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2551,7 +2551,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain5" }, { @@ -2559,7 +2559,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2567,7 +2567,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2580,7 +2580,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain6" }, { @@ -2588,7 +2588,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2596,7 +2596,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2609,7 +2609,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain7" }, { @@ -2617,7 +2617,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2625,7 +2625,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2638,7 +2638,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain8" }, { @@ -2646,7 +2646,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2654,7 +2654,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2667,7 +2667,7 @@ "type": "already_rejected", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedOnSourceChain9" }, { @@ -2675,7 +2675,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2683,7 +2683,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2696,7 +2696,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain0" }, { @@ -2704,7 +2704,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2712,7 +2712,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2725,7 +2725,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain1" }, { @@ -2733,7 +2733,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2741,7 +2741,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2754,7 +2754,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain2" }, { @@ -2762,7 +2762,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2770,7 +2770,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2783,7 +2783,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain3" }, { @@ -2791,7 +2791,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2799,7 +2799,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2812,7 +2812,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain4" }, { @@ -2820,7 +2820,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2828,7 +2828,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2841,7 +2841,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain5" }, { @@ -2849,7 +2849,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2857,7 +2857,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2870,7 +2870,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain6" }, { @@ -2878,7 +2878,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2886,7 +2886,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2899,7 +2899,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain7" }, { @@ -2907,7 +2907,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2915,7 +2915,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2928,7 +2928,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain8" }, { @@ -2936,7 +2936,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2944,7 +2944,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2957,7 +2957,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "NotFoundOnSourceChain9" }, { @@ -2965,7 +2965,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -2973,7 +2973,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -2986,7 +2986,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify0" }, { @@ -2994,7 +2994,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3002,7 +3002,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3015,7 +3015,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify1" }, { @@ -3023,7 +3023,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3031,7 +3031,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3044,7 +3044,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify2" }, { @@ -3052,7 +3052,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3060,7 +3060,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3073,7 +3073,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify3" }, { @@ -3081,7 +3081,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3089,7 +3089,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3102,7 +3102,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify4" }, { @@ -3110,7 +3110,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3118,7 +3118,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3131,7 +3131,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify5" }, { @@ -3139,7 +3139,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3147,7 +3147,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3160,7 +3160,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify6" }, { @@ -3168,7 +3168,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3176,7 +3176,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3189,7 +3189,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify7" }, { @@ -3197,7 +3197,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3205,7 +3205,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3218,7 +3218,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify8" }, { @@ -3226,7 +3226,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3234,7 +3234,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3247,7 +3247,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "FailedToVerify9" }, { @@ -3255,7 +3255,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3263,7 +3263,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3276,7 +3276,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress0" }, { @@ -3284,7 +3284,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3292,7 +3292,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3305,7 +3305,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress1" }, { @@ -3313,7 +3313,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3321,7 +3321,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3334,7 +3334,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress2" }, { @@ -3342,7 +3342,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3350,7 +3350,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3363,7 +3363,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress3" }, { @@ -3371,7 +3371,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3379,7 +3379,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3392,7 +3392,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress4" }, { @@ -3400,7 +3400,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3408,7 +3408,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3421,7 +3421,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress5" }, { @@ -3429,7 +3429,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3437,7 +3437,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3450,7 +3450,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress6" }, { @@ -3458,7 +3458,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3466,7 +3466,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3479,7 +3479,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress7" }, { @@ -3487,7 +3487,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3495,7 +3495,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3508,7 +3508,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress8" }, { @@ -3516,7 +3516,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3524,7 +3524,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3537,7 +3537,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "InProgress9" }, { @@ -3545,7 +3545,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3553,7 +3553,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3566,7 +3566,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown0" }, { @@ -3574,7 +3574,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3582,7 +3582,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3595,7 +3595,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown1" }, { @@ -3603,7 +3603,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3611,7 +3611,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3624,7 +3624,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown2" }, { @@ -3632,7 +3632,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3640,7 +3640,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3653,7 +3653,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown3" }, { @@ -3661,7 +3661,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3669,7 +3669,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3682,7 +3682,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown4" }, { @@ -3690,7 +3690,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3698,7 +3698,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3711,7 +3711,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown5" }, { @@ -3719,7 +3719,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3727,7 +3727,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3740,7 +3740,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown6" }, { @@ -3748,7 +3748,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3756,7 +3756,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3769,7 +3769,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown7" }, { @@ -3777,7 +3777,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3785,7 +3785,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3798,7 +3798,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown8" }, { @@ -3806,7 +3806,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3814,7 +3814,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { @@ -3827,7 +3827,7 @@ "type": "verifying", "attributes": [ { - "key": "id", + "key": "message_id", "value": "Unknown9" }, { @@ -3835,7 +3835,7 @@ "value": "mock-chain" }, { - "key": "source_addresses", + "key": "source_address", "value": "idc" }, { @@ -3843,7 +3843,7 @@ "value": "mock-chain-2" }, { - "key": "destination_addresses", + "key": "destination_address", "value": "idc" }, { diff --git a/contracts/multisig/.cargo/config b/contracts/multisig-prover/.cargo/config.toml similarity index 100% rename from contracts/multisig/.cargo/config rename to contracts/multisig-prover/.cargo/config.toml diff --git a/contracts/multisig-prover/Cargo.toml b/contracts/multisig-prover/Cargo.toml index 3f4d92f08..98fe1fe1c 100644 --- a/contracts/multisig-prover/Cargo.toml +++ b/contracts/multisig-prover/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "multisig-prover" -version = "0.6.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" description = "Multisig prover contract" @@ -33,8 +33,7 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] -axelar-wasm-std = { workspace = true } -axelar-wasm-std-derive = { workspace = true } +axelar-wasm-std = { workspace = true, features = ["derive"] } bcs = "0.1.5" coordinator = { workspace = true } cosmwasm-schema = { workspace = true } @@ -51,6 +50,7 @@ gateway-api = { workspace = true } hex = { version = "0.4.3", default-features = false, features = [] } itertools = "0.11.0" k256 = { version = "0.13.1", features = ["ecdsa"] } +msgs-derive = { workspace = true } multisig = { workspace = true, features = ["library"] } report = { workspace = true } router-api = { workspace = true } @@ -65,6 +65,7 @@ anyhow = "1.0" cw-multi-test = "0.15.1" elliptic-curve = "0.13.5" generic-array = "0.14.7" +goldie = { workspace = true } prost = "0.12.4" [lints] diff --git a/contracts/multisig-prover/src/bin/schema.rs b/contracts/multisig-prover/src/bin/schema.rs index 8a58b5f6e..0dc35b5f9 100644 --- a/contracts/multisig-prover/src/bin/schema.rs +++ b/contracts/multisig-prover/src/bin/schema.rs @@ -1,5 +1,4 @@ use cosmwasm_schema::write_api; - use multisig_prover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { diff --git a/contracts/multisig-prover/src/contract.rs b/contracts/multisig-prover/src/contract.rs index 5f1825867..068d7d41b 100644 --- a/contracts/multisig-prover/src/contract.rs +++ b/contracts/multisig-prover/src/contract.rs @@ -1,3 +1,4 @@ +use axelar_wasm_std::permission_control; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -5,13 +6,13 @@ use cosmwasm_std::{ }; use error_stack::ResultExt; -use crate::{ - error::ContractError, - execute, - msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, - query, reply, - state::{Config, CONFIG}, -}; +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::state::{Config, CONFIG}; +mod execute; +mod migrations; +mod query; +mod reply; pub const START_MULTISIG_REPLY_ID: u64 = 1; @@ -24,46 +25,32 @@ pub fn instantiate( _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> Result { +) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let config = make_config(&deps, msg)?; - CONFIG.save(deps.storage, &config)?; - - Ok(Response::default()) -} - -fn make_config( - deps: &DepsMut, - msg: InstantiateMsg, -) -> Result { - let admin = deps.api.addr_validate(&msg.admin_address)?; - let governance = deps.api.addr_validate(&msg.governance_address)?; - let gateway = deps.api.addr_validate(&msg.gateway_address)?; - let multisig = deps.api.addr_validate(&msg.multisig_address)?; - let coordinator = deps.api.addr_validate(&msg.coordinator_address)?; - let service_registry = deps.api.addr_validate(&msg.service_registry_address)?; - let voting_verifier = deps.api.addr_validate(&msg.voting_verifier_address)?; - - Ok(Config { - admin, - governance, - gateway, - multisig, - coordinator, - service_registry, - voting_verifier, + let config = Config { + gateway: deps.api.addr_validate(&msg.gateway_address)?, + multisig: deps.api.addr_validate(&msg.multisig_address)?, + coordinator: deps.api.addr_validate(&msg.coordinator_address)?, + service_registry: deps.api.addr_validate(&msg.service_registry_address)?, + voting_verifier: deps.api.addr_validate(&msg.voting_verifier_address)?, signing_threshold: msg.signing_threshold, service_name: msg.service_name, - chain_name: msg - .chain_name - .parse() - .map_err(|_| ContractError::InvalidChainName)?, + chain_name: msg.chain_name.parse()?, verifier_set_diff_threshold: msg.verifier_set_diff_threshold, encoder: msg.encoder, key_type: msg.key_type, domain_separator: msg.domain_separator, - }) + }; + CONFIG.save(deps.storage, &config)?; + + permission_control::set_admin(deps.storage, &deps.api.addr_validate(&msg.admin_address)?)?; + permission_control::set_governance( + deps.storage, + &deps.api.addr_validate(&msg.governance_address)?, + )?; + + Ok(Response::default()) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -72,30 +59,21 @@ pub fn execute( env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::ConstructProof { message_ids } => execute::construct_proof(deps, message_ids), - ExecuteMsg::UpdateVerifierSet {} => { - execute::require_admin(&deps, info.clone()) - .or_else(|_| execute::require_governance(&deps, info))?; - execute::update_verifier_set(deps, env) - } +) -> Result { + match msg.ensure_permissions(deps.storage, &info.sender)? { + ExecuteMsg::ConstructProof(message_ids) => Ok(execute::construct_proof(deps, message_ids)?), + ExecuteMsg::UpdateVerifierSet {} => Ok(execute::update_verifier_set(deps, env)?), ExecuteMsg::ConfirmVerifierSet {} => Ok(execute::confirm_verifier_set(deps, info.sender)?), ExecuteMsg::UpdateSigningThreshold { new_signing_threshold, - } => { - execute::require_governance(&deps, info)?; - Ok(execute::update_signing_threshold( - deps, - new_signing_threshold, - )?) - } + } => Ok(execute::update_signing_threshold( + deps, + new_signing_threshold, + )?), ExecuteMsg::UpdateAdmin { new_admin_address } => { - execute::require_governance(&deps, info)?; Ok(execute::update_admin(deps, new_admin_address)?) } } - .map_err(axelar_wasm_std::ContractError::from) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -103,12 +81,12 @@ pub fn reply( deps: DepsMut, _env: Env, reply: Reply, -) -> Result { +) -> Result { match reply.id { START_MULTISIG_REPLY_ID => reply::start_multisig_reply(deps, reply), _ => unreachable!("unknown reply ID"), } - .map_err(axelar_wasm_std::ContractError::from) + .map_err(axelar_wasm_std::error::ContractError::from) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -116,16 +94,16 @@ pub fn query( deps: Deps, _env: Env, msg: QueryMsg, -) -> Result { +) -> Result { match msg { - QueryMsg::GetProof { + QueryMsg::Proof { multisig_session_id, - } => to_json_binary(&query::get_proof(deps, multisig_session_id)?), + } => to_json_binary(&query::proof(deps, multisig_session_id)?), QueryMsg::CurrentVerifierSet {} => to_json_binary(&query::current_verifier_set(deps)?), QueryMsg::NextVerifierSet {} => to_json_binary(&query::next_verifier_set(deps)?), } .change_context(ContractError::SerializeResponse) - .map_err(axelar_wasm_std::ContractError::from) + .map_err(axelar_wasm_std::error::ContractError::from) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -133,40 +111,37 @@ pub fn migrate( deps: DepsMut, _env: Env, _msg: Empty, -) -> Result { - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; +) -> Result { + migrations::v0_6_0::migrate(deps.storage)?; Ok(Response::default()) } #[cfg(test)] mod tests { + use axelar_wasm_std::permission_control::Permission; + use axelar_wasm_std::{permission_control, MajorityThreshold, Threshold, VerificationStatus}; + use cosmwasm_std::testing::{ + mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, + }; use cosmwasm_std::{ - from_json, - testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, - Addr, Empty, Fraction, OwnedDeps, SubMsgResponse, SubMsgResult, Uint128, Uint64, + from_json, Addr, Api, Empty, Fraction, OwnedDeps, SubMsgResponse, SubMsgResult, Uint128, + Uint64, }; - - use axelar_wasm_std::{MajorityThreshold, Threshold, VerificationStatus}; - use multisig::{msg::Signer, verifier_set::VerifierSet}; + use multisig::msg::Signer; + use multisig::verifier_set::VerifierSet; use prost::Message; use router_api::CrossChainId; - use crate::{ - contract::execute::should_update_verifier_set, - msg::VerifierSetResponse, - test::test_utils::{ - mock_querier_handler, ADMIN, COORDINATOR_ADDRESS, GATEWAY_ADDRESS, GOVERNANCE, - MULTISIG_ADDRESS, SERVICE_NAME, SERVICE_REGISTRY_ADDRESS, VOTING_VERIFIER_ADDRESS, - }, - }; - use crate::{ - encoding::Encoder, - msg::{GetProofResponse, ProofStatus}, - test::test_data::{self, TestOperator}, - }; - use super::*; + use crate::contract::execute::should_update_verifier_set; + use crate::encoding::Encoder; + use crate::msg::{ProofResponse, ProofStatus, VerifierSetResponse}; + use crate::test::test_data::{self, TestOperator}; + use crate::test::test_utils::{ + mock_querier_handler, ADMIN, COORDINATOR_ADDRESS, GATEWAY_ADDRESS, GOVERNANCE, + MULTISIG_ADDRESS, SERVICE_NAME, SERVICE_REGISTRY_ADDRESS, VOTING_VERIFIER_ADDRESS, + }; const RELAYER: &str = "relayer"; const MULTISIG_SESSION_ID: Uint64 = Uint64::one(); @@ -195,7 +170,7 @@ mod tests { service_name: SERVICE_NAME.to_string(), chain_name: "ganache-0".to_string(), verifier_set_diff_threshold: 0, - encoder: crate::encoding::Encoder::Abi, + encoder: Encoder::Abi, key_type: multisig::key::KeyType::Ecdsa, domain_separator: [0; 32], }, @@ -207,7 +182,7 @@ mod tests { fn execute_update_verifier_set( deps: DepsMut, - ) -> Result { + ) -> Result { let msg = ExecuteMsg::UpdateVerifierSet {}; execute(deps, mock_env(), mock_info(ADMIN, &[]), msg) } @@ -215,7 +190,7 @@ mod tests { fn confirm_verifier_set( deps: DepsMut, sender: Addr, - ) -> Result { + ) -> Result { let msg = ExecuteMsg::ConfirmVerifierSet {}; execute(deps, mock_env(), mock_info(sender.as_str(), &[]), msg) } @@ -224,7 +199,7 @@ mod tests { deps: DepsMut, sender: Addr, new_signing_threshold: MajorityThreshold, - ) -> Result { + ) -> Result { let msg = ExecuteMsg::UpdateSigningThreshold { new_signing_threshold, }; @@ -235,7 +210,7 @@ mod tests { deps: DepsMut, sender: &str, new_admin_address: String, - ) -> Result { + ) -> Result { let msg = ExecuteMsg::UpdateAdmin { new_admin_address }; execute(deps, mock_env(), mock_info(sender, &[]), msg) } @@ -243,20 +218,21 @@ mod tests { fn execute_construct_proof( deps: DepsMut, message_ids: Option>, - ) -> Result { - let message_ids = match message_ids { - Some(ids) => ids, - None => test_data::messages() + ) -> Result { + let message_ids = message_ids.unwrap_or_else(|| { + test_data::messages() .into_iter() .map(|msg| msg.cc_id) - .collect::>(), - }; + .collect::>() + }); - let msg = ExecuteMsg::ConstructProof { message_ids }; + let msg = ExecuteMsg::ConstructProof(message_ids); execute(deps, mock_env(), mock_info(RELAYER, &[]), msg) } - fn reply_construct_proof(deps: DepsMut) -> Result { + fn reply_construct_proof( + deps: DepsMut, + ) -> Result { let session_id = to_json_binary(&MULTISIG_SESSION_ID).unwrap(); let response = SubMsgResponse { @@ -279,10 +255,10 @@ mod tests { ) } - fn query_get_proof( + fn query_proof( deps: Deps, multisig_session_id: Option, - ) -> Result { + ) -> Result { let multisig_session_id = match multisig_session_id { Some(id) => id, None => MULTISIG_SESSION_ID, @@ -291,16 +267,16 @@ mod tests { query( deps, mock_env(), - QueryMsg::GetProof { + QueryMsg::Proof { multisig_session_id, }, ) .map(|res| from_json(res).unwrap()) } - fn query_get_verifier_set( + fn query_verifier_set( deps: Deps, - ) -> Result, axelar_wasm_std::ContractError> { + ) -> Result, axelar_wasm_std::error::ContractError> { query(deps, mock_env(), QueryMsg::CurrentVerifierSet {}).map(|res| from_json(res).unwrap()) } @@ -353,13 +329,30 @@ mod tests { assert_eq!(res.messages.len(), 0); let config = CONFIG.load(deps.as_ref().storage).unwrap(); - assert_eq!(config.admin, admin); assert_eq!(config.gateway, gateway_address); assert_eq!(config.multisig, multisig_address); assert_eq!(config.service_registry, service_registry_address); assert_eq!(config.signing_threshold, signing_threshold); assert_eq!(config.service_name, service_name); - assert_eq!(config.encoder, encoding) + assert_eq!(config.encoder, encoding); + + assert_eq!( + permission_control::sender_role( + deps.as_ref().storage, + &deps.api.addr_validate(admin).unwrap() + ) + .unwrap(), + Permission::Admin.into() + ); + + assert_eq!( + permission_control::sender_role( + deps.as_ref().storage, + &deps.api.addr_validate(governance).unwrap() + ) + .unwrap(), + Permission::Governance.into() + ); } } @@ -391,14 +384,14 @@ mod tests { #[test] fn test_update_verifier_set_fresh() { let mut deps = setup_test_case(); - let verifier_set = query_get_verifier_set(deps.as_ref()); + let verifier_set = query_verifier_set(deps.as_ref()); assert!(verifier_set.is_ok()); assert!(verifier_set.unwrap().is_none()); let res = execute_update_verifier_set(deps.as_mut()); assert!(res.is_ok()); - let verifier_set = query_get_verifier_set(deps.as_ref()); + let verifier_set = query_verifier_set(deps.as_ref()); assert!(verifier_set.is_ok()); let verifier_set = verifier_set.unwrap().unwrap(); @@ -421,7 +414,13 @@ mod tests { assert!(res.is_err()); assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from(ContractError::Unauthorized).to_string() + axelar_wasm_std::error::ContractError::from( + permission_control::Error::PermissionDenied { + expected: Permission::Elevated.into(), + actual: Permission::NoPrivilege.into() + } + ) + .to_string() ); } @@ -468,7 +467,7 @@ mod tests { assert!(res.is_ok()); - let verifier_set = query_get_verifier_set(deps.as_ref()); + let verifier_set = query_verifier_set(deps.as_ref()); assert!(verifier_set.is_ok()); let verifier_set = verifier_set.unwrap().unwrap(); @@ -502,7 +501,7 @@ mod tests { let res = execute_update_verifier_set(deps.as_mut()); assert!(res.is_ok()); - let verifier_set = query_get_verifier_set(deps.as_ref()); + let verifier_set = query_verifier_set(deps.as_ref()); assert!(verifier_set.is_ok()); let verifier_set = verifier_set.unwrap().unwrap(); @@ -536,7 +535,7 @@ mod tests { assert!(res.is_ok()); - let verifier_set = query_get_verifier_set(deps.as_ref()); + let verifier_set = query_verifier_set(deps.as_ref()); assert!(verifier_set.is_ok()); let verifier_set = verifier_set.unwrap().unwrap(); @@ -559,7 +558,8 @@ mod tests { assert!(res.is_err()); assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from(ContractError::VerifierSetUnchanged).to_string() + axelar_wasm_std::error::ContractError::from(ContractError::VerifierSetUnchanged) + .to_string() ); } @@ -584,7 +584,7 @@ mod tests { assert!(res.is_err()); assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from(ContractError::VerifierSetNotConfirmed) + axelar_wasm_std::error::ContractError::from(ContractError::VerifierSetNotConfirmed) .to_string() ); } @@ -614,7 +614,20 @@ mod tests { assert!(res.is_err()); assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from(ContractError::VerifierSetNotConfirmed) + axelar_wasm_std::error::ContractError::from(ContractError::VerifierSetNotConfirmed) + .to_string() + ); + } + + #[test] + fn confirm_verifier_no_update_in_progress_should_fail() { + let mut deps = setup_test_case(); + + let res = confirm_verifier_set(deps.as_mut(), Addr::unchecked("relayer")); + assert!(res.is_err()); + assert_eq!( + res.unwrap_err().to_string(), + axelar_wasm_std::error::ContractError::from(ContractError::NoVerifierSetToConfirm) .to_string() ); } @@ -652,7 +665,7 @@ mod tests { execute_construct_proof(deps.as_mut(), None).unwrap(); reply_construct_proof(deps.as_mut()).unwrap(); // simulate reply from multisig - let res = query_get_proof(deps.as_ref(), None).unwrap(); + let res = query_proof(deps.as_ref(), None).unwrap(); assert_eq!(res.multisig_session_id, MULTISIG_SESSION_ID); assert_eq!(res.message_ids.len(), 1); @@ -671,7 +684,7 @@ mod tests { assert!(res.is_err()); assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from(ContractError::NoVerifierSet).to_string() + axelar_wasm_std::error::ContractError::from(ContractError::NoVerifierSet).to_string() ); } @@ -701,7 +714,7 @@ mod tests { /// Calls update_signing_threshold, increasing the threshold by one. /// Returns (initial threshold, new threshold) fn update_signing_threshold_increase_by_one(deps: DepsMut) -> (Uint128, Uint128) { - let verifier_set = query_get_verifier_set(deps.as_ref()) + let verifier_set = query_verifier_set(deps.as_ref()) .unwrap() .unwrap() .verifier_set; @@ -739,7 +752,7 @@ mod tests { update_signing_threshold_increase_by_one(deps.as_mut()); assert_ne!(initial_threshold, new_threshold); - let verifier_set = query_get_verifier_set(deps.as_ref()) + let verifier_set = query_verifier_set(deps.as_ref()) .unwrap() .unwrap() .verifier_set; @@ -760,7 +773,7 @@ mod tests { let governance = Addr::unchecked(GOVERNANCE); confirm_verifier_set(deps.as_mut(), governance).unwrap(); - let verifier_set = query_get_verifier_set(deps.as_ref()) + let verifier_set = query_verifier_set(deps.as_ref()) .unwrap() .unwrap() .verifier_set; @@ -781,7 +794,7 @@ mod tests { let res = confirm_verifier_set(deps.as_mut(), Addr::unchecked("relayer")); assert!(res.is_ok()); - let verifier_set = query_get_verifier_set(deps.as_ref()) + let verifier_set = query_verifier_set(deps.as_ref()) .unwrap() .unwrap() .verifier_set; @@ -872,7 +885,16 @@ mod tests { let res = execute_update_admin(deps.as_mut(), GOVERNANCE, new_admin.to_string()); assert!(res.is_ok(), "{:?}", res); - let config = CONFIG.load(deps.as_ref().storage).unwrap(); - assert_eq!(config.admin, Addr::unchecked(new_admin)); + assert_eq!( + permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked(new_admin)) + .unwrap(), + Permission::Admin.into() + ); + + assert_eq!( + permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked(ADMIN)) + .unwrap(), + Permission::NoPrivilege.into() + ); } } diff --git a/contracts/multisig-prover/src/execute.rs b/contracts/multisig-prover/src/contract/execute.rs similarity index 85% rename from contracts/multisig-prover/src/execute.rs rename to contracts/multisig-prover/src/contract/execute.rs index 25f4c528d..b29947ab5 100644 --- a/contracts/multisig-prover/src/execute.rs +++ b/contracts/multisig-prover/src/contract/execute.rs @@ -1,66 +1,56 @@ use std::collections::{BTreeMap, HashSet}; +use axelar_wasm_std::permission_control::Permission; +use axelar_wasm_std::snapshot::{Participant, Snapshot}; +use axelar_wasm_std::{permission_control, FnExt, MajorityThreshold, VerificationStatus}; use cosmwasm_std::{ - to_json_binary, wasm_execute, Addr, DepsMut, Env, MessageInfo, QuerierWrapper, QueryRequest, - Response, Storage, SubMsg, WasmQuery, + to_json_binary, wasm_execute, Addr, DepsMut, Env, QuerierWrapper, QueryRequest, Response, + Storage, SubMsg, WasmQuery, }; +use error_stack::{report, ResultExt}; use itertools::Itertools; - -use axelar_wasm_std::{ - snapshot::{Participant, Snapshot}, - FnExt, MajorityThreshold, VerificationStatus, -}; -use multisig::{msg::Signer, verifier_set::VerifierSet}; +use multisig::msg::Signer; +use multisig::verifier_set::VerifierSet; use router_api::{ChainName, CrossChainId, Message}; use service_registry::state::{Service, WeightedVerifier}; -use crate::{ - contract::START_MULTISIG_REPLY_ID, - error::ContractError, - payload::Payload, - state::{Config, CONFIG, CURRENT_VERIFIER_SET, NEXT_VERIFIER_SET, PAYLOAD, REPLY_TRACKER}, +use crate::contract::START_MULTISIG_REPLY_ID; +use crate::error::ContractError; +use crate::payload::Payload; +use crate::state::{ + Config, CONFIG, CURRENT_VERIFIER_SET, NEXT_VERIFIER_SET, PAYLOAD, REPLY_TRACKER, }; -pub fn require_admin(deps: &DepsMut, info: MessageInfo) -> Result<(), ContractError> { - match CONFIG.load(deps.storage)?.admin { - admin if admin == info.sender => Ok(()), - _ => Err(ContractError::Unauthorized), - } -} - -pub fn require_governance(deps: &DepsMut, info: MessageInfo) -> Result<(), ContractError> { - match CONFIG.load(deps.storage)?.governance { - governance if governance == info.sender => Ok(()), - _ => Err(ContractError::Unauthorized), - } -} - pub fn construct_proof( deps: DepsMut, message_ids: Vec, ) -> error_stack::Result { let config = CONFIG.load(deps.storage).map_err(ContractError::from)?; - let payload_id = message_ids.as_slice().into(); - let messages = get_messages( + let messages = messages( deps.querier, message_ids, config.gateway.clone(), config.chain_name.clone(), )?; - let payload = match PAYLOAD + let payload = Payload::Messages(messages); + let payload_id = payload.id(); + + match PAYLOAD .may_load(deps.storage, &payload_id) .map_err(ContractError::from)? { - Some(payload) => payload, + Some(stored_payload) => { + if stored_payload != payload { + return Err(report!(ContractError::PayloadMismatch)) + .attach_printable_lazy(|| format!("{:?}", stored_payload)); + } + } None => { - let payload = Payload::Messages(messages); PAYLOAD .save(deps.storage, &payload_id, &payload) .map_err(ContractError::from)?; - - payload } }; @@ -89,7 +79,7 @@ pub fn construct_proof( Ok(Response::new().add_submessage(SubMsg::reply_on_success(wasm_msg, START_MULTISIG_REPLY_ID))) } -fn get_messages( +fn messages( querier: QuerierWrapper, message_ids: Vec, gateway: Addr, @@ -97,7 +87,7 @@ fn get_messages( ) -> Result, ContractError> { let length = message_ids.len(); - let query = gateway_api::msg::QueryMsg::GetOutgoingMessages { message_ids }; + let query = gateway_api::msg::QueryMsg::OutgoingMessages(message_ids); let messages: Vec = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: gateway.into(), msg: to_json_binary(&query)?, @@ -109,14 +99,17 @@ fn get_messages( "violated invariant: returned gateway messages count mismatch" ); - if messages + if let Some(wrong_destination) = messages .iter() - .any(|msg| msg.destination_chain != chain_name) + .find(|msg| msg.destination_chain != chain_name) { - panic!("violated invariant: messages from different chain found"); + Err(ContractError::InvalidDestinationChain { + expected: chain_name, + actual: wrong_destination.destination_chain.clone(), + }) + } else { + Ok(messages) } - - Ok(messages) } fn make_verifier_set( @@ -124,7 +117,7 @@ fn make_verifier_set( env: &Env, config: &Config, ) -> Result { - let active_verifiers_query = service_registry::msg::QueryMsg::GetActiveVerifiers { + let active_verifiers_query = service_registry::msg::QueryMsg::ActiveVerifiers { service_name: config.service_name.clone(), chain_name: config.chain_name.clone(), }; @@ -139,7 +132,7 @@ fn make_verifier_set( .querier .query::(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: config.service_registry.to_string(), - msg: to_json_binary(&service_registry::msg::QueryMsg::GetService { + msg: to_json_binary(&service_registry::msg::QueryMsg::Service { service_name: config.service_name.clone(), })?, }))? @@ -148,7 +141,7 @@ fn make_verifier_set( let participants_with_pubkeys = verifiers .into_iter() .filter_map(|verifier| { - let pub_key_query = multisig::msg::QueryMsg::GetPublicKey { + let pub_key_query = multisig::msg::QueryMsg::PublicKey { verifier_address: verifier.verifier_info.address.to_string(), key_type: config.key_type, }; @@ -183,7 +176,7 @@ fn make_verifier_set( )) } -fn get_next_verifier_set( +fn next_verifier_set( deps: &DepsMut, env: &Env, config: &Config, @@ -252,7 +245,7 @@ pub fn update_verifier_set( )) } Some(cur_verifier_set) => { - let new_verifier_set = get_next_verifier_set(&deps, &env, &config)? + let new_verifier_set = next_verifier_set(&deps, &env, &config)? .ok_or(ContractError::VerifierSetUnchanged)?; save_next_verifier_set(deps.storage, &new_verifier_set)?; @@ -303,9 +296,7 @@ fn ensure_verifier_set_verification( config: &Config, deps: &DepsMut, ) -> Result<(), ContractError> { - let query = voting_verifier::msg::QueryMsg::GetVerifierSetStatus { - new_verifier_set: verifier_set.clone(), - }; + let query = voting_verifier::msg::QueryMsg::VerifierSetStatus(verifier_set.clone()); let status: VerificationStatus = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: config.voting_verifier.to_string(), @@ -322,9 +313,12 @@ fn ensure_verifier_set_verification( pub fn confirm_verifier_set(deps: DepsMut, sender: Addr) -> Result { let config = CONFIG.load(deps.storage)?; - let verifier_set = NEXT_VERIFIER_SET.load(deps.storage)?; + let verifier_set = NEXT_VERIFIER_SET + .may_load(deps.storage)? + .ok_or(ContractError::NoVerifierSetToConfirm)?; - if sender != config.governance { + let sender_role = permission_control::sender_role(deps.storage, &sender)?; + if !sender_role.contains(Permission::Governance) { ensure_verifier_set_verification(&verifier_set, &config, &deps)?; } @@ -415,33 +409,23 @@ pub fn update_signing_threshold( } pub fn update_admin(deps: DepsMut, new_admin_address: String) -> Result { - CONFIG.update( - deps.storage, - |mut config| -> Result { - config.admin = deps.api.addr_validate(&new_admin_address)?; - Ok(config) - }, - )?; + let new_admin = deps.api.addr_validate(&new_admin_address)?; + permission_control::set_admin(deps.storage, &new_admin)?; Ok(Response::new()) } #[cfg(test)] mod tests { + use std::collections::BTreeMap; + use axelar_wasm_std::Threshold; - use cosmwasm_std::{ - testing::{mock_dependencies, mock_env}, - Addr, - }; + use cosmwasm_std::testing::{mock_dependencies, mock_env}; + use cosmwasm_std::Addr; use router_api::ChainName; - use crate::{ - execute::should_update_verifier_set, - state::{Config, NEXT_VERIFIER_SET}, - test::test_data, - }; - use std::collections::BTreeMap; - - use super::{different_set_in_progress, get_next_verifier_set}; + use super::{different_set_in_progress, next_verifier_set, should_update_verifier_set}; + use crate::state::{Config, NEXT_VERIFIER_SET}; + use crate::test::test_data; #[test] fn should_update_verifier_set_no_change() { @@ -547,21 +531,19 @@ mod tests { } #[test] - fn get_next_verifier_set_should_return_pending() { + fn next_verifier_set_should_return_pending() { let mut deps = mock_dependencies(); let env = mock_env(); let new_verifier_set = test_data::new_verifier_set(); NEXT_VERIFIER_SET .save(deps.as_mut().storage, &new_verifier_set) .unwrap(); - let ret_verifier_set = get_next_verifier_set(&deps.as_mut(), &env, &mock_config()); + let ret_verifier_set = next_verifier_set(&deps.as_mut(), &env, &mock_config()); assert_eq!(ret_verifier_set.unwrap().unwrap(), new_verifier_set); } fn mock_config() -> Config { Config { - admin: Addr::unchecked("doesn't matter"), - governance: Addr::unchecked("doesn't matter"), gateway: Addr::unchecked("doesn't matter"), multisig: Addr::unchecked("doesn't matter"), coordinator: Addr::unchecked("doesn't matter"), diff --git a/contracts/multisig-prover/src/contract/migrations/mod.rs b/contracts/multisig-prover/src/contract/migrations/mod.rs new file mode 100644 index 000000000..b29376b44 --- /dev/null +++ b/contracts/multisig-prover/src/contract/migrations/mod.rs @@ -0,0 +1 @@ +pub(crate) mod v0_6_0; diff --git a/contracts/multisig-prover/src/contract/migrations/v0_6_0.rs b/contracts/multisig-prover/src/contract/migrations/v0_6_0.rs new file mode 100644 index 000000000..316fab18a --- /dev/null +++ b/contracts/multisig-prover/src/contract/migrations/v0_6_0.rs @@ -0,0 +1,290 @@ +#![allow(deprecated)] + +use axelar_wasm_std::error::ContractError; +use axelar_wasm_std::hash::Hash; +use axelar_wasm_std::{permission_control, MajorityThreshold}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Storage}; +use cw_storage_plus::Item; +use multisig::key::KeyType; +use router_api::ChainName; + +use crate::contract::{CONTRACT_NAME, CONTRACT_VERSION}; +use crate::encoding::Encoder; +use crate::state; + +const BASE_VERSION: &str = "0.6.0"; + +pub(crate) fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { + cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; + + let config = CONFIG.load(storage)?; + + migrate_permission_control(storage, &config)?; + migrate_config(storage, config)?; + delete_payloads(storage); + + cw2::set_contract_version(storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(()) +} + +fn delete_payloads(storage: &mut dyn Storage) { + state::PAYLOAD.clear(storage); + state::MULTISIG_SESSION_PAYLOAD.clear(storage); + state::REPLY_TRACKER.remove(storage); +} + +fn migrate_permission_control( + storage: &mut dyn Storage, + config: &Config, +) -> Result<(), ContractError> { + permission_control::set_governance(storage, &config.governance)?; + permission_control::set_admin(storage, &config.admin)?; + Ok(()) +} + +fn migrate_config(storage: &mut dyn Storage, config: Config) -> Result<(), ContractError> { + CONFIG.remove(storage); + + let config = state::Config { + gateway: config.gateway, + multisig: config.multisig, + coordinator: config.coordinator, + service_registry: config.service_registry, + voting_verifier: config.voting_verifier, + signing_threshold: config.signing_threshold, + service_name: config.service_name, + chain_name: config.chain_name, + verifier_set_diff_threshold: config.verifier_set_diff_threshold, + encoder: config.encoder, + key_type: config.key_type, + domain_separator: config.domain_separator, + }; + state::CONFIG.save(storage, &config)?; + Ok(()) +} + +#[cw_serde] +#[deprecated(since = "0.6.0", note = "only used during migration")] +struct Config { + pub admin: Addr, + pub governance: Addr, + pub gateway: Addr, + pub multisig: Addr, + pub coordinator: Addr, + pub service_registry: Addr, + pub voting_verifier: Addr, + pub signing_threshold: MajorityThreshold, + pub service_name: String, + pub chain_name: ChainName, + pub verifier_set_diff_threshold: u32, + pub encoder: Encoder, + pub key_type: KeyType, + pub domain_separator: Hash, +} +#[deprecated(since = "0.6.0", note = "only used during migration")] +const CONFIG: Item = Item::new("config"); + +#[cfg(test)] +mod tests { + use axelar_wasm_std::permission_control::Permission; + use axelar_wasm_std::{permission_control, MajorityThreshold, Threshold}; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response}; + use multisig::key::KeyType; + use router_api::{CrossChainId, Message}; + + use crate::contract::migrations::v0_6_0; + use crate::contract::{CONTRACT_NAME, CONTRACT_VERSION}; + use crate::encoding::Encoder; + use crate::error::ContractError; + use crate::msg::InstantiateMsg; + use crate::{payload, state}; + + #[test] + fn migrate_checks_contract_version() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap(); + + assert!(v0_6_0::migrate(deps.as_mut().storage).is_err()); + + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, v0_6_0::BASE_VERSION) + .unwrap(); + + assert!(v0_6_0::migrate(deps.as_mut().storage).is_ok()); + } + + #[test] + fn migrate_sets_contract_version() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + v0_6_0::migrate(deps.as_mut().storage).unwrap(); + + let contract_version = cw2::get_contract_version(deps.as_mut().storage).unwrap(); + assert_eq!(contract_version.contract, CONTRACT_NAME); + assert_eq!(contract_version.version, CONTRACT_VERSION); + } + + #[test] + fn migrate_payload() { + let mut deps = mock_dependencies(); + + instantiate_contract(deps.as_mut()); + + let msgs = vec![ + Message { + cc_id: CrossChainId { + message_id: "id1".try_into().unwrap(), + source_chain: "chain1".try_into().unwrap(), + }, + source_address: "source-address".parse().unwrap(), + destination_chain: "destination".parse().unwrap(), + destination_address: "destination-address".parse().unwrap(), + payload_hash: [1; 32], + }, + Message { + cc_id: CrossChainId { + message_id: "id2".try_into().unwrap(), + source_chain: "chain2".try_into().unwrap(), + }, + source_address: "source-address2".parse().unwrap(), + destination_chain: "destination2".parse().unwrap(), + destination_address: "destination-address2".parse().unwrap(), + payload_hash: [2; 32], + }, + Message { + cc_id: CrossChainId { + message_id: "id3".try_into().unwrap(), + source_chain: "chain3".try_into().unwrap(), + }, + source_address: "source-address3".parse().unwrap(), + destination_chain: "destination3".parse().unwrap(), + destination_address: "destination-address3".parse().unwrap(), + payload_hash: [3; 32], + }, + ]; + + let payload = payload::Payload::Messages(msgs); + + state::PAYLOAD + .save(deps.as_mut().storage, &payload.id(), &payload) + .unwrap(); + + assert!(v0_6_0::migrate(deps.as_mut().storage).is_ok()); + + assert!(state::PAYLOAD.is_empty(deps.as_ref().storage)); + } + + #[test] + fn migrate_permission_control() { + let mut deps = mock_dependencies(); + + instantiate_contract(deps.as_mut()); + + assert!(v0_6_0::migrate(deps.as_mut().storage).is_ok()); + + assert_eq!( + permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked("admin")) + .unwrap(), + Permission::Admin.into() + ); + + assert_eq!( + permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked("governance")) + .unwrap(), + Permission::Governance.into() + ); + } + + #[test] + fn migrate_config() { + let mut deps = mock_dependencies(); + + instantiate_contract(deps.as_mut()); + + assert!(v0_6_0::CONFIG.load(deps.as_ref().storage).is_ok()); + assert!(state::CONFIG.load(deps.as_ref().storage).is_err()); + + assert!(v0_6_0::migrate(deps.as_mut().storage).is_ok()); + + assert!(v0_6_0::CONFIG.load(deps.as_ref().storage).is_err()); + assert!(state::CONFIG.load(deps.as_ref().storage).is_ok()); + } + + fn instantiate_contract(deps: DepsMut) { + instantiate( + deps, + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + admin_address: "admin".to_string(), + governance_address: "governance".to_string(), + gateway_address: "gateway".to_string(), + multisig_address: "multisig".to_string(), + coordinator_address: "coordinator".to_string(), + service_registry_address: "service_registry".to_string(), + voting_verifier_address: "voting_verifier".to_string(), + signing_threshold: Threshold::try_from((2u64, 3u64)) + .and_then(MajorityThreshold::try_from) + .unwrap(), + service_name: "service".to_string(), + chain_name: "chain".to_string(), + verifier_set_diff_threshold: 1, + encoder: Encoder::Abi, + key_type: KeyType::Ecdsa, + domain_separator: [0; 32], + }, + ) + .unwrap(); + } + + #[deprecated(since = "0.6.0", note = "only used to test the migration")] + pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, + ) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, v0_6_0::BASE_VERSION)?; + + let config = make_config(&deps, msg)?; + v0_6_0::CONFIG.save(deps.storage, &config)?; + + Ok(Response::default()) + } + + fn make_config( + deps: &DepsMut, + msg: InstantiateMsg, + ) -> Result { + let admin = deps.api.addr_validate(&msg.admin_address)?; + let governance = deps.api.addr_validate(&msg.governance_address)?; + let gateway = deps.api.addr_validate(&msg.gateway_address)?; + let multisig = deps.api.addr_validate(&msg.multisig_address)?; + let coordinator = deps.api.addr_validate(&msg.coordinator_address)?; + let service_registry = deps.api.addr_validate(&msg.service_registry_address)?; + let voting_verifier = deps.api.addr_validate(&msg.voting_verifier_address)?; + + Ok(v0_6_0::Config { + admin, + governance, + gateway, + multisig, + coordinator, + service_registry, + voting_verifier, + signing_threshold: msg.signing_threshold, + service_name: msg.service_name, + chain_name: msg + .chain_name + .parse() + .map_err(|_| ContractError::InvalidChainName)?, + verifier_set_diff_threshold: msg.verifier_set_diff_threshold, + encoder: msg.encoder, + key_type: msg.key_type, + domain_separator: msg.domain_separator, + }) + } +} diff --git a/contracts/multisig-prover/src/query.rs b/contracts/multisig-prover/src/contract/query.rs similarity index 81% rename from contracts/multisig-prover/src/query.rs rename to contracts/multisig-prover/src/contract/query.rs index d6b2cfc4f..14c35a102 100644 --- a/contracts/multisig-prover/src/query.rs +++ b/contracts/multisig-prover/src/contract/query.rs @@ -1,25 +1,22 @@ use cosmwasm_std::{to_json_binary, Deps, QueryRequest, StdResult, Uint64, WasmQuery}; use error_stack::Result; +use multisig::multisig::Multisig; +use multisig::types::MultisigState; -use multisig::{multisig::Multisig, types::MultisigState}; - -use crate::{ - error::ContractError, - msg::{GetProofResponse, ProofStatus, VerifierSetResponse}, - state::{CONFIG, CURRENT_VERIFIER_SET, MULTISIG_SESSION_PAYLOAD, NEXT_VERIFIER_SET, PAYLOAD}, +use crate::error::ContractError; +use crate::msg::{ProofResponse, ProofStatus, VerifierSetResponse}; +use crate::state::{ + CONFIG, CURRENT_VERIFIER_SET, MULTISIG_SESSION_PAYLOAD, NEXT_VERIFIER_SET, PAYLOAD, }; -pub fn get_proof( - deps: Deps, - multisig_session_id: Uint64, -) -> Result { +pub fn proof(deps: Deps, multisig_session_id: Uint64) -> Result { let config = CONFIG.load(deps.storage).map_err(ContractError::from)?; let payload_id = MULTISIG_SESSION_PAYLOAD .load(deps.storage, multisig_session_id.u64()) .map_err(ContractError::from)?; - let query_msg = multisig::msg::QueryMsg::GetMultisig { + let query_msg = multisig::msg::QueryMsg::Multisig { session_id: multisig_session_id, }; @@ -49,7 +46,7 @@ pub fn get_proof( } }; - Ok(GetProofResponse { + Ok(ProofResponse { multisig_session_id, message_ids: payload.message_ids().unwrap_or_default(), payload, @@ -73,7 +70,8 @@ pub fn next_verifier_set(deps: Deps) -> StdResult> { mod test { use cosmwasm_std::testing::mock_dependencies; - use crate::{state, test::test_data::new_verifier_set}; + use crate::state; + use crate::test::test_data::new_verifier_set; #[test] fn next_verifier_set() { diff --git a/contracts/multisig-prover/src/reply.rs b/contracts/multisig-prover/src/contract/reply.rs similarity index 90% rename from contracts/multisig-prover/src/reply.rs rename to contracts/multisig-prover/src/contract/reply.rs index ddb6951f2..d3fc7253b 100644 --- a/contracts/multisig-prover/src/reply.rs +++ b/contracts/multisig-prover/src/contract/reply.rs @@ -1,12 +1,9 @@ use cosmwasm_std::{from_json, DepsMut, Reply, Response, Uint64}; use cw_utils::{parse_reply_execute_data, MsgExecuteContractResponse}; -use crate::state::{CONFIG, PAYLOAD}; -use crate::{ - error::ContractError, - events::Event, - state::{MULTISIG_SESSION_PAYLOAD, REPLY_TRACKER}, -}; +use crate::error::ContractError; +use crate::events::Event; +use crate::state::{CONFIG, MULTISIG_SESSION_PAYLOAD, PAYLOAD, REPLY_TRACKER}; pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result { let config = CONFIG.load(deps.storage)?; diff --git a/contracts/multisig-prover/src/encoding/abi/execute_data.rs b/contracts/multisig-prover/src/encoding/abi/execute_data.rs index 1f3ed470d..dea1d443e 100644 --- a/contracts/multisig-prover/src/encoding/abi/execute_data.rs +++ b/contracts/multisig-prover/src/encoding/abi/execute_data.rs @@ -1,13 +1,15 @@ +use axelar_wasm_std::hash::Hash; use cosmwasm_std::HexBinary; use error_stack::ResultExt; use ethers_contract::contract::EthCall; use ethers_core::abi::{encode as abi_encode, Tokenize}; - -use axelar_wasm_std::hash::Hash; use evm_gateway::{ApproveMessagesCall, Message, Proof, RotateSignersCall, WeightedSigners}; -use multisig::{key::Signature, msg::SignerWithSig, verifier_set::VerifierSet}; +use multisig::key::Signature; +use multisig::msg::SignerWithSig; +use multisig::verifier_set::VerifierSet; -use crate::{error::ContractError, payload::Payload}; +use crate::error::ContractError; +use crate::payload::Payload; pub fn encode( verifier_set: &VerifierSet, @@ -78,31 +80,24 @@ pub fn add27(recovery_byte: k256::ecdsa::RecoveryId) -> u8 { mod tests { use std::str::FromStr; + use axelar_wasm_std::hash::Hash; use cosmwasm_std::HexBinary; use elliptic_curve::consts::U32; use ethers_core::types::Signature as EthersSignature; + use evm_gateway::evm_address; use generic_array::GenericArray; use hex::FromHex; use itertools::Itertools; use k256::ecdsa::Signature as K256Signature; + use multisig::key::{KeyType, KeyTyped, Signature}; + use multisig::msg::{Signer, SignerWithSig}; use sha3::{Digest, Keccak256}; - use axelar_wasm_std::hash::Hash; - use evm_gateway::evm_address; - use multisig::{ - key::{KeyType, KeyTyped, Signature}, - msg::{Signer, SignerWithSig}, - }; - - use crate::{ - encoding::abi::{ - execute_data::{add27, encode}, - payload_hash_to_sign, - }, - payload::Payload, - test::test_data::{ - curr_verifier_set, domain_separator, messages, verifier_set_from_pub_keys, - }, + use crate::encoding::abi::execute_data::{add27, encode}; + use crate::encoding::abi::payload_hash_to_sign; + use crate::payload::Payload; + use crate::test::test_data::{ + curr_verifier_set, domain_separator, messages, verifier_set_from_pub_keys, }; #[test] diff --git a/contracts/multisig-prover/src/encoding/abi/mod.rs b/contracts/multisig-prover/src/encoding/abi/mod.rs index c2135a065..6ed8a79b0 100644 --- a/contracts/multisig-prover/src/encoding/abi/mod.rs +++ b/contracts/multisig-prover/src/encoding/abi/mod.rs @@ -1,15 +1,15 @@ pub mod execute_data; +use axelar_wasm_std::hash::Hash; use error_stack::{Result, ResultExt}; use ethers_core::abi::{encode as abi_encode, Token, Tokenize}; -use itertools::Itertools; -use sha3::{Digest, Keccak256}; - -use axelar_wasm_std::hash::Hash; use evm_gateway::{CommandType, Message, WeightedSigners}; +use itertools::Itertools; use multisig::verifier_set::VerifierSet; +use sha3::{Digest, Keccak256}; -use crate::{error::ContractError, payload::Payload}; +use crate::error::ContractError; +use crate::payload::Payload; impl From<&Payload> for CommandType { fn from(payload: &Payload) -> Self { @@ -72,13 +72,10 @@ pub fn encode(payload: &Payload) -> Result, ContractError> { mod tests { use cosmwasm_std::HexBinary; - use crate::{ - encoding::abi::{payload_hash_to_sign, CommandType}, - payload::Payload, - test::test_data::{ - curr_verifier_set, domain_separator, messages, new_verifier_set, - verifier_set_from_pub_keys, - }, + use crate::encoding::abi::{payload_hash_to_sign, CommandType}; + use crate::payload::Payload; + use crate::test::test_data::{ + curr_verifier_set, domain_separator, messages, new_verifier_set, verifier_set_from_pub_keys, }; #[test] diff --git a/contracts/multisig-prover/src/error.rs b/contracts/multisig-prover/src/error.rs index 5d070a80b..4ed88fea3 100644 --- a/contracts/multisig-prover/src/error.rs +++ b/contracts/multisig-prover/src/error.rs @@ -1,17 +1,13 @@ +use axelar_wasm_std::{nonempty, IntoContractError}; use cosmwasm_std::StdError; +use router_api::ChainName; use thiserror::Error; -use axelar_wasm_std::nonempty; -use axelar_wasm_std_derive::IntoContractError; - #[derive(Error, Debug, PartialEq, IntoContractError)] pub enum ContractError { #[error(transparent)] Std(#[from] StdError), - #[error("caller is not authorized")] - Unauthorized, - #[error("message is invalid")] InvalidMessage, @@ -22,6 +18,7 @@ pub enum ContractError { InvalidSignature { reason: String }, #[error("chain name is invalid")] + #[deprecated(since = "0.6.0")] InvalidChainName, #[error("invalid participants: {reason}")] @@ -51,6 +48,9 @@ pub enum ContractError { #[error("a verifier set confirmation already in progress")] VerifierSetConfirmationInProgress, + #[error("no verifier set to confirm")] + NoVerifierSetToConfirm, + #[error("no verifier set stored")] NoVerifierSet, @@ -65,4 +65,13 @@ pub enum ContractError { #[error("not enough verifiers")] NotEnoughVerifiers, + + #[error("invalid destination chain '{actual}', expected '{expected}'")] + InvalidDestinationChain { + actual: ChainName, + expected: ChainName, + }, + + #[error("payload does not match the stored value")] + PayloadMismatch, } diff --git a/contracts/multisig-prover/src/events.rs b/contracts/multisig-prover/src/events.rs index 63ce89efd..0992a7637 100644 --- a/contracts/multisig-prover/src/events.rs +++ b/contracts/multisig-prover/src/events.rs @@ -47,27 +47,36 @@ impl From for cosmwasm_std::Event { #[cfg(test)] mod tests { - use super::*; + use router_api::Message; use serde_json::to_string; + use super::*; + use crate::payload::Payload; + #[test] fn proof_under_construction_is_serializable() { - let msg_ids = vec![ - CrossChainId { - chain: "ethereum".parse().unwrap(), - id: "some_id".try_into().unwrap(), + let payload = Payload::Messages(vec![ + Message { + cc_id: CrossChainId::new("ethereum", "some-id").unwrap(), + source_address: "0x1234".parse().unwrap(), + destination_chain: "avalanche".parse().unwrap(), + destination_address: "0x5678".parse().unwrap(), + payload_hash: [0; 32], }, - CrossChainId { - chain: "fantom".parse().unwrap(), - id: "some_other_id".try_into().unwrap(), + Message { + cc_id: CrossChainId::new("fantom", "some-other-id").unwrap(), + source_address: "0x1234".parse().unwrap(), + destination_chain: "avalanche".parse().unwrap(), + destination_address: "0x5678".parse().unwrap(), + payload_hash: [0; 32], }, - ]; + ]); let event = Event::ProofUnderConstruction { destination_chain: "avalanche".parse().unwrap(), - payload_id: msg_ids.as_slice().into(), + payload_id: payload.id(), multisig_session_id: Uint64::new(2), - msg_ids, + msg_ids: payload.message_ids().unwrap(), }; assert!(to_string(&cosmwasm_std::Event::from(event)).is_ok()); diff --git a/contracts/multisig-prover/src/lib.rs b/contracts/multisig-prover/src/lib.rs index aa670ca61..b59c7551a 100644 --- a/contracts/multisig-prover/src/lib.rs +++ b/contracts/multisig-prover/src/lib.rs @@ -2,11 +2,8 @@ pub mod contract; pub mod encoding; pub mod error; pub mod events; -mod execute; pub mod msg; pub mod payload; -mod query; -mod reply; pub mod state; #[cfg(test)] diff --git a/contracts/multisig-prover/src/msg.rs b/contracts/multisig-prover/src/msg.rs index 4aa108c84..69b50beaf 100644 --- a/contracts/multisig-prover/src/msg.rs +++ b/contracts/multisig-prover/src/msg.rs @@ -1,10 +1,13 @@ -use axelar_wasm_std::{hash::Hash, MajorityThreshold}; +use axelar_wasm_std::hash::Hash; +use axelar_wasm_std::MajorityThreshold; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{HexBinary, Uint64}; +use msgs_derive::EnsurePermissions; use multisig::key::KeyType; use router_api::CrossChainId; -use crate::{encoding::Encoder, payload::Payload}; +use crate::encoding::Encoder; +use crate::payload::Payload; #[cw_serde] pub struct InstantiateMsg { @@ -55,30 +58,32 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(EnsurePermissions)] pub enum ExecuteMsg { // Start building a proof that includes specified messages // Queries the gateway for actual message contents - ConstructProof { - message_ids: Vec, - }, + #[permission(Any)] + ConstructProof(Vec), + #[permission(Elevated)] UpdateVerifierSet, + + #[permission(Any)] ConfirmVerifierSet, // Updates the signing threshold. The threshold currently in use does not change. // The verifier set must be updated and confirmed for the change to take effect. - // Callable only by governance. + #[permission(Governance)] UpdateSigningThreshold { new_signing_threshold: MajorityThreshold, }, - UpdateAdmin { - new_admin_address: String, - }, + #[permission(Governance)] + UpdateAdmin { new_admin_address: String }, } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - #[returns(GetProofResponse)] - GetProof { multisig_session_id: Uint64 }, + #[returns(ProofResponse)] + Proof { multisig_session_id: Uint64 }, /// Returns a `VerifierSetResponse` with the current verifier set id and the verifier set itself. #[returns(Option)] @@ -96,7 +101,7 @@ pub enum ProofStatus { } #[cw_serde] -pub struct GetProofResponse { +pub struct ProofResponse { pub multisig_session_id: Uint64, pub message_ids: Vec, pub payload: Payload, diff --git a/contracts/multisig-prover/src/payload.rs b/contracts/multisig-prover/src/payload.rs index db53b665b..b757e4ad4 100644 --- a/contracts/multisig-prover/src/payload.rs +++ b/contracts/multisig-prover/src/payload.rs @@ -1,18 +1,15 @@ +use axelar_wasm_std::hash::Hash; use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_json, HexBinary, StdResult}; use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey}; use error_stack::Result; -use sha3::{Digest, Keccak256}; - -use axelar_wasm_std::hash::Hash; use multisig::msg::SignerWithSig; use multisig::verifier_set::VerifierSet; -use router_api::{CrossChainId, Message}; +use router_api::{CrossChainId, Message, FIELD_DELIMITER}; +use sha3::{Digest, Keccak256}; -use crate::{ - encoding::{abi, Encoder}, - error::ContractError, -}; +use crate::encoding::{abi, Encoder}; +use crate::error::ContractError; #[cw_serde] pub enum Payload { @@ -22,19 +19,29 @@ pub enum Payload { impl Payload { /// id returns the unique identifier for the payload, which can be either - /// - the hash of comma separated sorted message ids - /// - the hash of the verifier set + /// - Hash of 0 followed by '_' separated message ids + /// - Hash of 1 followed by the verifier set hash pub fn id(&self) -> PayloadId { - match self { + let hash = match self { Payload::Messages(msgs) => { - let message_ids = msgs - .iter() - .map(|msg| msg.cc_id.clone()) - .collect::>(); + let message_ids: Vec = + msgs.iter().map(|msg| msg.cc_id.to_string()).collect(); - message_ids.as_slice().into() + message_ids.join(&FIELD_DELIMITER.to_string()).into() } - Payload::VerifierSet(verifier_set) => verifier_set.hash().as_slice().into(), + Payload::VerifierSet(verifier_set) => verifier_set.hash().to_vec(), + }; + + let mut id = vec![self.variant_to_u8()]; + id.extend(hash); + + Keccak256::digest(id).to_vec().into() + } + + fn variant_to_u8(&self) -> u8 { + match self { + Payload::Messages(_) => 0, + Payload::VerifierSet(_) => 1, } } @@ -79,8 +86,8 @@ impl Payload { #[cw_serde] pub struct PayloadId(HexBinary); -impl From<&[u8]> for PayloadId { - fn from(id: &[u8]) -> Self { +impl From> for PayloadId { + fn from(id: Vec) -> Self { Self(id.into()) } } @@ -104,32 +111,22 @@ impl KeyDeserialize for PayloadId { } } -impl From<&[CrossChainId]> for PayloadId { - fn from(ids: &[CrossChainId]) -> Self { - let mut message_ids = ids.iter().map(|id| id.to_string()).collect::>(); - message_ids.sort(); - - Keccak256::digest(message_ids.join(",")).as_slice().into() - } -} - #[cfg(test)] mod test { - use router_api::CrossChainId; - - use crate::{payload::PayloadId, test::test_data}; + use crate::payload::Payload; + use crate::test::test_data; #[test] - fn test_payload_id() { - let messages = test_data::messages(); - let mut message_ids: Vec = - messages.into_iter().map(|msg| msg.cc_id).collect(); + fn payload_messages_id_unchanged() { + let payload = Payload::Messages(test_data::messages()); - let res: PayloadId = message_ids.as_slice().into(); + goldie::assert_json!(payload.id()); + } - message_ids.reverse(); - let res2: PayloadId = message_ids.as_slice().into(); + #[test] + fn payload_verifier_set_id_unchanged() { + let payload = Payload::VerifierSet(test_data::curr_verifier_set()); - assert_eq!(res, res2); + goldie::assert_json!(payload.id()); } } diff --git a/contracts/multisig-prover/src/state.rs b/contracts/multisig-prover/src/state.rs index 98dd34fe4..babfc35f5 100644 --- a/contracts/multisig-prover/src/state.rs +++ b/contracts/multisig-prover/src/state.rs @@ -1,21 +1,17 @@ +use axelar_wasm_std::hash::Hash; +use axelar_wasm_std::MajorityThreshold; use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; use cw_storage_plus::{Item, Map}; - -use axelar_wasm_std::{hash::Hash, MajorityThreshold}; use multisig::key::KeyType; use multisig::verifier_set::VerifierSet; use router_api::ChainName; -use crate::{ - encoding::Encoder, - payload::{Payload, PayloadId}, -}; +use crate::encoding::Encoder; +use crate::payload::{Payload, PayloadId}; #[cw_serde] pub struct Config { - pub admin: Addr, - pub governance: Addr, pub gateway: Addr, pub multisig: Addr, pub coordinator: Addr, diff --git a/contracts/multisig-prover/src/test/test_data.rs b/contracts/multisig-prover/src/test/test_data.rs index f817fe1ea..efd0eb1e6 100644 --- a/contracts/multisig-prover/src/test/test_data.rs +++ b/contracts/multisig-prover/src/test/test_data.rs @@ -3,11 +3,9 @@ use std::collections::BTreeMap; use axelar_wasm_std::{nonempty, MajorityThreshold, Participant, Threshold}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, HexBinary, Uint128, Uint64}; -use multisig::{ - key::{KeyType, PublicKey, Signature}, - msg::Signer, - verifier_set::VerifierSet, -}; +use multisig::key::{KeyType, Signature}; +use multisig::msg::Signer; +use multisig::verifier_set::VerifierSet; use router_api::{CrossChainId, Message}; pub fn new_verifier_set() -> VerifierSet { @@ -15,7 +13,7 @@ pub fn new_verifier_set() -> VerifierSet { Signer { address: Addr::unchecked("axelarvaloper1x86a8prx97ekkqej2x636utrdu23y8wupp9gk5"), weight: Uint128::from(10u128), - pub_key: PublicKey::Ecdsa( + pub_key: multisig::key::PublicKey::Ecdsa( HexBinary::from_hex( "03d123ce370b163acd576be0e32e436bb7e63262769881d35fa3573943bf6c6f81", ) @@ -25,7 +23,7 @@ pub fn new_verifier_set() -> VerifierSet { Signer { address: Addr::unchecked("axelarvaloper1ff675m593vve8yh82lzhdnqfpu7m23cxstr6h4"), weight: Uint128::from(10u128), - pub_key: PublicKey::Ecdsa( + pub_key: multisig::key::PublicKey::Ecdsa( HexBinary::from_hex( "03c6ddb0fcee7b528da1ef3c9eed8d51eeacd7cc28a8baa25c33037c5562faa6e4", ) @@ -35,7 +33,7 @@ pub fn new_verifier_set() -> VerifierSet { Signer { address: Addr::unchecked("axelarvaloper12cwre2gdhyytc3p97z9autzg27hmu4gfzz4rxc"), weight: Uint128::from(10u128), - pub_key: PublicKey::Ecdsa( + pub_key: multisig::key::PublicKey::Ecdsa( HexBinary::from_hex( "0274b5d2a4c55d7edbbf9cc210c4d25adbb6194d6b444816235c82984bee518255", ) @@ -45,7 +43,7 @@ pub fn new_verifier_set() -> VerifierSet { Signer { address: Addr::unchecked("axelarvaloper1vs9rdplntrf7ceqdkznjmanrr59qcpjq6le0yw"), weight: Uint128::from(10u128), - pub_key: PublicKey::Ecdsa( + pub_key: multisig::key::PublicKey::Ecdsa( HexBinary::from_hex( "02a670f57de55b8b39b4cb051e178ca8fb3fe3a78cdde7f8238baf5e6ce1893185", ) @@ -55,7 +53,7 @@ pub fn new_verifier_set() -> VerifierSet { Signer { address: Addr::unchecked("axelarvaloper1hz0slkejw96dukw87fztjkvwjdpcu20jewg6mw"), weight: Uint128::from(10u128), - pub_key: PublicKey::Ecdsa( + pub_key: multisig::key::PublicKey::Ecdsa( HexBinary::from_hex( "028584592624e742ba154c02df4c0b06e4e8a957ba081083ea9fe5309492aa6c7b", ) @@ -78,12 +76,11 @@ pub fn new_verifier_set() -> VerifierSet { pub fn messages() -> Vec { vec![Message { - cc_id: CrossChainId { - chain: "ganache-1".parse().unwrap(), - id: "0xff822c88807859ff226b58e24f24974a70f04b9442501ae38fd665b3c68f3834-0" - .parse() - .unwrap(), - }, + cc_id: CrossChainId::new( + "ganache-1", + "0xff822c88807859ff226b58e24f24974a70f04b9442501ae38fd665b3c68f3834-0", + ) + .unwrap(), source_address: "0x52444f1835Adc02086c37Cb226561605e2E1699b" .parse() .unwrap(), @@ -204,7 +201,7 @@ pub fn verifier_set_from_pub_keys(pub_keys: Vec<&str>) -> VerifierSet { address: Addr::unchecked(format!("verifier{i}")), weight: nonempty::Uint128::one(), }, - PublicKey::Ecdsa(HexBinary::from_hex(pub_keys[i]).unwrap()), + multisig::key::PublicKey::Ecdsa(HexBinary::from_hex(pub_keys[i]).unwrap()), ) }) .collect(); diff --git a/contracts/multisig-prover/src/test/test_utils.rs b/contracts/multisig-prover/src/test/test_utils.rs index 9e3dec6ed..1553a98a4 100644 --- a/contracts/multisig-prover/src/test/test_utils.rs +++ b/contracts/multisig-prover/src/test/test_utils.rs @@ -1,6 +1,9 @@ use axelar_wasm_std::VerificationStatus; use cosmwasm_std::{from_json, to_json_binary, Addr, QuerierResult, Uint128, WasmQuery}; -use multisig::{msg::Signer, multisig::Multisig, types::MultisigState, verifier_set::VerifierSet}; +use multisig::msg::Signer; +use multisig::multisig::Multisig; +use multisig::types::MultisigState; +use multisig::verifier_set::VerifierSet; use service_registry::state::{ AuthorizationState, BondingState, Verifier, WeightedVerifier, VERIFIER_WEIGHT, }; @@ -46,10 +49,10 @@ fn multisig_mock_querier_handler( operators: Vec, ) -> QuerierResult { let result = match msg { - multisig::msg::QueryMsg::GetMultisig { session_id: _ } => { - to_json_binary(&mock_get_multisig(operators)) + multisig::msg::QueryMsg::Multisig { session_id: _ } => { + to_json_binary(&mock_multisig(operators)) } - multisig::msg::QueryMsg::GetPublicKey { + multisig::msg::QueryMsg::PublicKey { verifier_address, key_type: _, } => to_json_binary( @@ -65,7 +68,7 @@ fn multisig_mock_querier_handler( Ok(result.into()).into() } -fn mock_get_multisig(operators: Vec) -> Multisig { +fn mock_multisig(operators: Vec) -> Multisig { let quorum = test_data::quorum(); let signers = operators @@ -114,7 +117,7 @@ fn service_registry_mock_querier_handler( operators: Vec, ) -> QuerierResult { let result = match msg { - service_registry::msg::QueryMsg::GetService { service_name } => { + service_registry::msg::QueryMsg::Service { service_name } => { to_json_binary(&service_registry::state::Service { name: service_name.to_string(), coordinator_contract: Addr::unchecked(COORDINATOR_ADDRESS), @@ -126,7 +129,7 @@ fn service_registry_mock_querier_handler( description: "verifiers".to_string(), }) } - service_registry::msg::QueryMsg::GetActiveVerifiers { + service_registry::msg::QueryMsg::ActiveVerifiers { service_name: _, chain_name: _, } => to_json_binary( diff --git a/contracts/multisig-prover/src/testdata/payload_messages_id_unchanged.golden b/contracts/multisig-prover/src/testdata/payload_messages_id_unchanged.golden new file mode 100644 index 000000000..7b45c5ed4 --- /dev/null +++ b/contracts/multisig-prover/src/testdata/payload_messages_id_unchanged.golden @@ -0,0 +1 @@ +"068d17b0fe521c092561cd0fcf63928fc148b6fb9000fa1558030c1e55590a49" \ No newline at end of file diff --git a/contracts/multisig-prover/src/testdata/payload_verifier_set_id_unchanged.golden b/contracts/multisig-prover/src/testdata/payload_verifier_set_id_unchanged.golden new file mode 100644 index 000000000..9fcd783ad --- /dev/null +++ b/contracts/multisig-prover/src/testdata/payload_verifier_set_id_unchanged.golden @@ -0,0 +1 @@ +"76dc1094e9f8dbb6a98c077e3a67c889d49820e98e8560f0a91e3531d41d4168" \ No newline at end of file diff --git a/contracts/rewards/.cargo/config b/contracts/multisig/.cargo/config.toml similarity index 100% rename from contracts/rewards/.cargo/config rename to contracts/multisig/.cargo/config.toml diff --git a/contracts/multisig/Cargo.toml b/contracts/multisig/Cargo.toml index a37642c6d..784604575 100644 --- a/contracts/multisig/Cargo.toml +++ b/contracts/multisig/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "multisig" -version = "0.4.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" description = "Multisig contract" @@ -40,8 +40,7 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] -axelar-wasm-std = { workspace = true } -axelar-wasm-std-derive = { workspace = true } +axelar-wasm-std = { workspace = true, features = ["derive"] } cosmwasm-crypto = "1.2.7" cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } @@ -54,6 +53,7 @@ error-stack = { workspace = true } getrandom = { version = "0.2", default-features = false, features = ["custom"] } itertools = "0.11.0" k256 = { version = "0.13.1", features = ["ecdsa"] } +msgs-derive = { workspace = true } report = { workspace = true } rewards = { workspace = true, features = ["library"] } router-api = { workspace = true } @@ -66,6 +66,8 @@ thiserror = { workspace = true } [dev-dependencies] curve25519-dalek = "4.1.1" cw-multi-test = "0.15.1" +goldie = { workspace = true } +hex = "0.4" [lints] workspace = true diff --git a/contracts/multisig/src/bin/schema.rs b/contracts/multisig/src/bin/schema.rs index 923fac67b..6b8f5bdfd 100644 --- a/contracts/multisig/src/bin/schema.rs +++ b/contracts/multisig/src/bin/schema.rs @@ -1,5 +1,4 @@ use cosmwasm_schema::write_api; - use multisig::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { diff --git a/contracts/multisig/src/contract.rs b/contracts/multisig/src/contract.rs index 35a07e939..02c70059a 100644 --- a/contracts/multisig/src/contract.rs +++ b/contracts/multisig/src/contract.rs @@ -1,21 +1,26 @@ +use std::collections::HashMap; + +use axelar_wasm_std::{killswitch, permission_control, FnExt}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, HexBinary, MessageInfo, Response, - StdResult, Uint64, + to_json_binary, Addr, Binary, Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdError, + StdResult, Storage, Uint64, }; - -use crate::{ - events::Event, - msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, - state::{ - get_verifier_set, Config, CONFIG, SIGNING_SESSIONS, SIGNING_SESSION_COUNTER, VERIFIER_SETS, - }, - types::{MsgToSign, MultisigState}, - ContractError, +use error_stack::{report, ResultExt}; +use itertools::Itertools; +use router_api::ChainName; + +use crate::events::Event; +use crate::msg::{ExecuteMsg, InstantiateMsg, MigrationMsg, QueryMsg}; +use crate::state::{ + verifier_set, Config, CONFIG, SIGNING_SESSIONS, SIGNING_SESSION_COUNTER, VERIFIER_SETS, }; +use crate::types::{MsgToSign, MultisigState}; +use crate::ContractError; mod execute; +mod migrations; mod query; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); @@ -25,8 +30,24 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); pub fn migrate( deps: DepsMut, _env: Env, - _msg: Empty, -) -> Result { + msg: MigrationMsg, +) -> Result { + let admin = deps.api.addr_validate(&msg.admin_address)?; + let authorized_callers = msg + .authorized_callers + .into_iter() + .map(|(contract_address, chain_name)| { + deps.api + .addr_validate(&contract_address) + .map(|addr| (addr, chain_name)) + }) + .try_collect()?; + + migrations::v0_4_1::migrate(deps.storage, admin, authorized_callers) + .change_context(ContractError::Migration)?; + + // this needs to be the last thing to do during migration, + // because previous migration steps should check the old version cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; Ok(Response::default()) @@ -38,11 +59,18 @@ pub fn instantiate( _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> Result { +) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + let admin = deps.api.addr_validate(&msg.admin_address)?; + let governance = deps.api.addr_validate(&msg.governance_address)?; + + permission_control::set_admin(deps.storage, &admin)?; + permission_control::set_governance(deps.storage, &governance)?; + + killswitch::init(deps.storage, killswitch::State::Disengaged)?; + let config = Config { - governance: deps.api.addr_validate(&msg.governance_address)?, rewards_contract: deps.api.addr_validate(&msg.rewards_address)?, block_expiry: msg.block_expiry, }; @@ -59,16 +87,18 @@ pub fn execute( env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result { - match msg { +) -> Result { + match msg.ensure_permissions( + deps.storage, + &info.sender, + can_start_signing_session(&info.sender), + )? { ExecuteMsg::StartSigningSession { verifier_set_id, msg, chain_name, sig_verifier, } => { - execute::require_authorized_caller(&deps, info.sender)?; - let sig_verifier = sig_verifier .map(|addr| deps.api.addr_validate(&addr)) .transpose()?; @@ -77,7 +107,7 @@ pub fn execute( env, verifier_set_id, msg.try_into() - .map_err(axelar_wasm_std::ContractError::from)?, + .map_err(axelar_wasm_std::error::ContractError::from)?, chain_name, sig_verifier, ) @@ -93,78 +123,112 @@ pub fn execute( public_key, signed_sender_address, } => execute::register_pub_key(deps, info, public_key, signed_sender_address), - ExecuteMsg::AuthorizeCaller { contract_address } => { - execute::require_governance(&deps, info.sender)?; - execute::authorize_caller(deps, contract_address) + ExecuteMsg::AuthorizeCallers { contracts } => { + let contracts = validate_contract_addresses(&deps, contracts)?; + execute::authorize_callers(deps, contracts) } - ExecuteMsg::UnauthorizeCaller { contract_address } => { - execute::require_governance(&deps, info.sender)?; - execute::unauthorize_caller(deps, contract_address) + ExecuteMsg::UnauthorizeCallers { contracts } => { + let contracts = validate_contract_addresses(&deps, contracts)?; + execute::unauthorize_callers(deps, contracts) } + ExecuteMsg::DisableSigning => execute::disable_signing(deps), + ExecuteMsg::EnableSigning => execute::enable_signing(deps), + }? + .then(Ok) +} + +fn validate_contract_addresses( + deps: &DepsMut, + contracts: HashMap, +) -> Result, StdError> { + contracts + .into_iter() + .map(|(contract_address, chain_name)| { + deps.api + .addr_validate(&contract_address) + .map(|addr| (addr, chain_name)) + }) + .try_collect() +} + +fn can_start_signing_session( + sender: &Addr, +) -> impl FnOnce(&dyn Storage, &ExecuteMsg) -> error_stack::Result + '_ +{ + |storage, msg| match msg { + ExecuteMsg::StartSigningSession { chain_name, .. } => { + execute::require_authorized_caller(storage, sender, chain_name) + .change_context(permission_control::Error::Unauthorized) + } + _ => Err(report!(permission_control::Error::WrongVariant)), } - .map_err(axelar_wasm_std::ContractError::from) } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::GetMultisig { session_id } => { - to_json_binary(&query::get_multisig(deps, session_id)?) - } - QueryMsg::GetVerifierSet { verifier_set_id } => { - to_json_binary(&query::get_verifier_set(deps, verifier_set_id)?) + QueryMsg::Multisig { session_id } => to_json_binary(&query::multisig(deps, session_id)?), + QueryMsg::VerifierSet { verifier_set_id } => { + to_json_binary(&query::verifier_set(deps, verifier_set_id)?) } - QueryMsg::GetPublicKey { + QueryMsg::PublicKey { verifier_address, key_type, - } => to_json_binary(&query::get_public_key( + } => to_json_binary(&query::public_key( deps, deps.api.addr_validate(&verifier_address)?, key_type, )?), + QueryMsg::IsCallerAuthorized { + contract_address, + chain_name, + } => to_json_binary(&query::caller_authorized( + deps, + deps.api.addr_validate(&contract_address)?, + chain_name, + )?), } } #[cfg(feature = "test")] #[cfg(test)] mod tests { + use std::collections::HashMap; use std::vec; - use cosmwasm_std::{ - from_json, - testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, - Addr, Empty, OwnedDeps, WasmMsg, + use cosmwasm_std::testing::{ + mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, }; - use serde_json::from_str; - + use cosmwasm_std::{from_json, Addr, Empty, OwnedDeps, WasmMsg}; + use permission_control::Permission; use router_api::ChainName; - - use crate::{ - key::{KeyType, PublicKey, Signature}, - multisig::Multisig, - state::load_session_signatures, - test::common::{build_verifier_set, TestSigner}, - test::common::{ecdsa_test_data, ed25519_test_data}, - types::MultisigState, - verifier_set::VerifierSet, - }; + use serde_json::from_str; use super::*; + use crate::key::{KeyType, PublicKey, Signature}; + use crate::multisig::Multisig; + use crate::state::load_session_signatures; + use crate::test::common::{build_verifier_set, ecdsa_test_data, ed25519_test_data, TestSigner}; + use crate::types::MultisigState; + use crate::verifier_set::VerifierSet; const INSTANTIATOR: &str = "inst"; const PROVER: &str = "prover"; const REWARDS_CONTRACT: &str = "rewards"; + const GOVERNANCE: &str = "governance"; + const ADMIN: &str = "admin"; const SIGNATURE_BLOCK_EXPIRY: u64 = 100; - fn do_instantiate(deps: DepsMut) -> Result { + fn do_instantiate(deps: DepsMut) -> Result { let info = mock_info(INSTANTIATOR, &[]); let env = mock_env(); let msg = InstantiateMsg { - governance_address: "governance".parse().unwrap(), + governance_address: GOVERNANCE.parse().unwrap(), + admin_address: ADMIN.parse().unwrap(), rewards_address: REWARDS_CONTRACT.to_string(), - block_expiry: SIGNATURE_BLOCK_EXPIRY, + block_expiry: SIGNATURE_BLOCK_EXPIRY.try_into().unwrap(), }; instantiate(deps, env, info, msg) @@ -173,7 +237,7 @@ mod tests { fn generate_verifier_set( key_type: KeyType, deps: DepsMut, - ) -> Result<(Response, VerifierSet), axelar_wasm_std::ContractError> { + ) -> Result<(Response, VerifierSet), axelar_wasm_std::error::ContractError> { let info = mock_info(PROVER, &[]); let env = mock_env(); @@ -195,7 +259,7 @@ mod tests { query( deps, env, - QueryMsg::GetVerifierSet { + QueryMsg::VerifierSet { verifier_set_id: verifier_set_id.to_string(), }, ) @@ -206,7 +270,7 @@ mod tests { sender: &str, verifier_set_id: &str, chain_name: ChainName, - ) -> Result { + ) -> Result { let info = mock_info(sender, &[]); let env = mock_env(); @@ -225,7 +289,7 @@ mod tests { env: Env, session_id: Uint64, signer: &TestSigner, - ) -> Result { + ) -> Result { let msg = ExecuteMsg::SubmitSignature { session_id, signature: signer.signature.clone(), @@ -238,7 +302,7 @@ mod tests { verifier: Addr, public_key: PublicKey, signed_sender_address: HexBinary, - ) -> Result { + ) -> Result { let msg = ExecuteMsg::RegisterPublicKey { public_key, signed_sender_address, @@ -246,27 +310,57 @@ mod tests { execute(deps, mock_env(), mock_info(verifier.as_str(), &[]), msg) } - fn do_authorize_caller( + fn do_authorize_callers( deps: DepsMut, - contract_address: Addr, - ) -> Result { - let config = CONFIG.load(deps.storage)?; - let info = mock_info(config.governance.as_str(), &[]); + contracts: Vec<(Addr, ChainName)>, + ) -> Result { + let info = mock_info(GOVERNANCE, &[]); let env = mock_env(); - let msg = ExecuteMsg::AuthorizeCaller { contract_address }; + let msg = ExecuteMsg::AuthorizeCallers { + contracts: contracts + .into_iter() + .map(|(addr, chain_name)| (addr.to_string(), chain_name)) + .collect(), + }; execute(deps, env, info, msg) } fn do_unauthorize_caller( deps: DepsMut, - contract_address: Addr, - ) -> Result { - let config = CONFIG.load(deps.storage)?; - let info = mock_info(config.governance.as_str(), &[]); + contracts: Vec<(Addr, ChainName)>, + ) -> Result { + let info = mock_info(GOVERNANCE, &[]); let env = mock_env(); - let msg = ExecuteMsg::UnauthorizeCaller { contract_address }; + let msg = ExecuteMsg::UnauthorizeCallers { + contracts: contracts + .into_iter() + .map(|(addr, chain_name)| (addr.to_string(), chain_name)) + .collect(), + }; + execute(deps, env, info, msg) + } + + fn do_disable_signing( + deps: DepsMut, + sender: &str, + ) -> Result { + let info = mock_info(sender, &[]); + let env = mock_env(); + + let msg = ExecuteMsg::DisableSigning; + execute(deps, env, info, msg) + } + + fn do_enable_signing( + deps: DepsMut, + sender: &str, + ) -> Result { + let info = mock_info(sender, &[]); + let env = mock_env(); + + let msg = ExecuteMsg::EnableSigning; execute(deps, env, info, msg) } @@ -279,7 +373,7 @@ mod tests { query( deps, env, - QueryMsg::GetPublicKey { + QueryMsg::PublicKey { verifier_address: verifier.to_string(), key_type, }, @@ -306,7 +400,7 @@ mod tests { } // TODO: move to external crate? - fn get_event_attribute<'a>( + fn event_attribute<'a>( event: &'a cosmwasm_std::Event, attribute_name: &'a str, ) -> Option<&'a str> { @@ -348,6 +442,18 @@ mod tests { let session_counter = SIGNING_SESSION_COUNTER.load(deps.as_ref().storage).unwrap(); + assert_eq!( + permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked(ADMIN)) + .unwrap(), + Permission::Admin.into() + ); + + assert_eq!( + permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked(GOVERNANCE)) + .unwrap(), + Permission::Governance.into() + ); + assert_eq!(session_counter, Uint64::zero()); } @@ -386,18 +492,18 @@ mod tests { #[test] fn start_signing_session() { let (mut deps, ecdsa_subkey, ed25519_subkey) = setup(); - do_authorize_caller(deps.as_mut(), Addr::unchecked(PROVER)).unwrap(); + let chain_name: ChainName = "mock-chain".parse().unwrap(); + do_authorize_callers( + deps.as_mut(), + vec![(Addr::unchecked(PROVER), chain_name.clone())], + ) + .unwrap(); for (i, subkey) in [ecdsa_subkey.clone(), ed25519_subkey.clone()] .into_iter() .enumerate() { - let res = do_start_signing_session( - deps.as_mut(), - PROVER, - &subkey, - "mock-chain".parse().unwrap(), - ); + let res = do_start_signing_session(deps.as_mut(), PROVER, &subkey, chain_name.clone()); assert!(res.is_ok()); @@ -406,7 +512,7 @@ mod tests { .unwrap(); let verifier_set_id = subkey.to_string(); - let verifier_set = get_verifier_set(deps.as_ref().storage, &verifier_set_id).unwrap(); + let verifier_set = verifier_set(deps.as_ref().storage, &verifier_set_id).unwrap(); let message = match subkey { _ if subkey == ecdsa_subkey => ecdsa_test_data::message(), _ if subkey == ed25519_subkey => ed25519_test_data::message(), @@ -428,25 +534,30 @@ mod tests { let event = res.events.first().unwrap(); assert_eq!(event.ty, "signing_started".to_string()); assert_eq!( - get_event_attribute(event, "session_id").unwrap(), + event_attribute(event, "session_id").unwrap(), session.id.to_string() ); assert_eq!( - get_event_attribute(event, "verifier_set_id").unwrap(), + event_attribute(event, "verifier_set_id").unwrap(), session.verifier_set_id ); assert_eq!( - verifier_set.get_pub_keys(), - from_str(get_event_attribute(event, "pub_keys").unwrap()).unwrap() + verifier_set.pub_keys(), + from_str(event_attribute(event, "pub_keys").unwrap()).unwrap() ); - assert_eq!(get_event_attribute(event, "msg").unwrap(), message.to_hex()); + assert_eq!(event_attribute(event, "msg").unwrap(), message.to_hex()); } } #[test] fn start_signing_session_wrong_sender() { let (mut deps, ecdsa_subkey, ed25519_subkey) = setup(); - do_authorize_caller(deps.as_mut(), Addr::unchecked(PROVER)).unwrap(); + let chain_name: ChainName = "mock-chain".parse().unwrap(); + do_authorize_callers( + deps.as_mut(), + vec![(Addr::unchecked(PROVER), chain_name.clone())], + ) + .unwrap(); let sender = "someone else"; @@ -455,22 +566,25 @@ mod tests { deps.as_mut(), sender, &verifier_set_id, - "mock-chain".parse().unwrap(), + chain_name.clone(), ); assert!(res .unwrap_err() .to_string() - .contains(&ContractError::Unauthorized.to_string())); + .contains(&permission_control::Error::Unauthorized.to_string())); } } #[test] fn submit_signature() { let (mut deps, ecdsa_subkey, ed25519_subkey) = setup(); - do_authorize_caller(deps.as_mut(), Addr::unchecked(PROVER)).unwrap(); - let chain_name: ChainName = "mock-chain".parse().unwrap(); + do_authorize_callers( + deps.as_mut(), + vec![(Addr::unchecked(PROVER), chain_name.clone())], + ) + .unwrap(); for (key_type, verifier_set_id, signers, session_id) in signature_test_data(&ecdsa_subkey, &ed25519_subkey) @@ -519,15 +633,15 @@ mod tests { let event = res.events.first().unwrap(); assert_eq!(event.ty, "signature_submitted".to_string()); assert_eq!( - get_event_attribute(event, "session_id").unwrap(), + event_attribute(event, "session_id").unwrap(), session_id.to_string() ); assert_eq!( - get_event_attribute(event, "participant").unwrap(), + event_attribute(event, "participant").unwrap(), signer.address.into_string() ); assert_eq!( - get_event_attribute(event, "signature").unwrap(), + event_attribute(event, "signature").unwrap(), signer.signature.to_hex() ); } @@ -536,13 +650,17 @@ mod tests { #[test] fn submit_signature_completes_session() { let (mut deps, ecdsa_subkey, ed25519_subkey) = setup(); - do_authorize_caller(deps.as_mut(), Addr::unchecked(PROVER)).unwrap(); + let chain_name: ChainName = "mock-chain".parse().unwrap(); + do_authorize_callers( + deps.as_mut(), + vec![(Addr::unchecked(PROVER), chain_name.clone())], + ) + .unwrap(); for (key_type, subkey, signers, session_id) in signature_test_data(&ecdsa_subkey, &ed25519_subkey) { - do_start_signing_session(deps.as_mut(), PROVER, subkey, "mock-chain".parse().unwrap()) - .unwrap(); + do_start_signing_session(deps.as_mut(), PROVER, subkey, chain_name.clone()).unwrap(); let signer = signers.first().unwrap().to_owned(); do_sign(deps.as_mut(), mock_env(), session_id, &signer).unwrap(); @@ -580,7 +698,7 @@ mod tests { let event = res.events.get(1).unwrap(); assert_eq!(event.ty, "signing_completed".to_string()); assert_eq!( - get_event_attribute(event, "session_id").unwrap(), + event_attribute(event, "session_id").unwrap(), session_id.to_string() ); } @@ -589,9 +707,12 @@ mod tests { #[test] fn submit_signature_before_expiry() { let (mut deps, ecdsa_subkey, ed25519_subkey) = setup(); - do_authorize_caller(deps.as_mut(), Addr::unchecked(PROVER)).unwrap(); - let chain_name: ChainName = "mock-chain".parse().unwrap(); + do_authorize_callers( + deps.as_mut(), + vec![(Addr::unchecked(PROVER), chain_name.clone())], + ) + .unwrap(); for (_key_type, subkey, signers, session_id) in signature_test_data(&ecdsa_subkey, &ed25519_subkey) @@ -635,13 +756,18 @@ mod tests { #[test] fn submit_signature_after_expiry() { let (mut deps, ecdsa_subkey, ed25519_subkey) = setup(); - do_authorize_caller(deps.as_mut(), Addr::unchecked(PROVER)).unwrap(); + + let chain_name: ChainName = "mock-chain".parse().unwrap(); + do_authorize_callers( + deps.as_mut(), + vec![(Addr::unchecked(PROVER), chain_name.clone())], + ) + .unwrap(); for (_key_type, subkey, signers, session_id) in signature_test_data(&ecdsa_subkey, &ed25519_subkey) { - do_start_signing_session(deps.as_mut(), PROVER, subkey, "mock-chain".parse().unwrap()) - .unwrap(); + do_start_signing_session(deps.as_mut(), PROVER, subkey, chain_name.clone()).unwrap(); let signer = signers.first().unwrap().to_owned(); do_sign(deps.as_mut(), mock_env(), session_id, &signer).unwrap(); @@ -658,7 +784,7 @@ mod tests { assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from(ContractError::SigningSessionClosed { + axelar_wasm_std::error::ContractError::from(ContractError::SigningSessionClosed { session_id }) .to_string() @@ -669,14 +795,13 @@ mod tests { #[test] fn submit_signature_wrong_session_id() { let (mut deps, ecdsa_subkey, _) = setup(); - do_authorize_caller(deps.as_mut(), Addr::unchecked(PROVER)).unwrap(); - do_start_signing_session( + let chain_name: ChainName = "mock-chain".parse().unwrap(); + do_authorize_callers( deps.as_mut(), - PROVER, - &ecdsa_subkey, - "mock-chain".parse().unwrap(), + vec![(Addr::unchecked(PROVER), chain_name.clone())], ) .unwrap(); + do_start_signing_session(deps.as_mut(), PROVER, &ecdsa_subkey, chain_name.clone()).unwrap(); let invalid_session_id = Uint64::zero(); let signer = ecdsa_test_data::signers().first().unwrap().to_owned(); @@ -684,7 +809,7 @@ mod tests { assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from(ContractError::SigningSessionNotFound { + axelar_wasm_std::error::ContractError::from(ContractError::SigningSessionNotFound { session_id: invalid_session_id }) .to_string() @@ -694,7 +819,12 @@ mod tests { #[test] fn query_signing_session() { let (mut deps, ecdsa_subkey, ed25519_subkey) = setup(); - do_authorize_caller(deps.as_mut(), Addr::unchecked(PROVER)).unwrap(); + let chain_name: ChainName = "mock-chain".parse().unwrap(); + do_authorize_callers( + deps.as_mut(), + vec![(Addr::unchecked(PROVER), chain_name.clone())], + ) + .unwrap(); for (_key_type, subkey, signers, session_id) in signature_test_data(&ecdsa_subkey, &ed25519_subkey) @@ -714,7 +844,7 @@ mod tests { let expected_completed_at = env.block.height; do_sign(deps.as_mut(), env, session_id, signers.get(1).unwrap()).unwrap(); - let msg = QueryMsg::GetMultisig { session_id }; + let msg = QueryMsg::Multisig { session_id }; let res = query(deps.as_ref(), mock_env(), msg); assert!(res.is_ok()); @@ -906,7 +1036,7 @@ mod tests { ); assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from( + axelar_wasm_std::error::ContractError::from( ContractError::InvalidPublicKeyRegistrationSignature ) .to_string() @@ -925,7 +1055,7 @@ mod tests { ); assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from( + axelar_wasm_std::error::ContractError::from( ContractError::InvalidPublicKeyRegistrationSignature ) .to_string() @@ -959,43 +1089,101 @@ mod tests { assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from(ContractError::DuplicatePublicKey).to_string() + axelar_wasm_std::error::ContractError::from(ContractError::DuplicatePublicKey) + .to_string() ); } #[test] - fn authorize_and_unauthorize_caller() { + fn authorize_and_unauthorize_callers() { let (mut deps, ecdsa_subkey, ed25519_subkey) = setup(); + let prover_address = Addr::unchecked(PROVER); + let chain_name: ChainName = "mock-chain".parse().unwrap(); // authorize - do_authorize_caller(deps.as_mut(), Addr::unchecked(PROVER)).unwrap(); + do_authorize_callers( + deps.as_mut(), + vec![(prover_address.clone(), chain_name.clone())], + ) + .unwrap(); for verifier_set_id in [ecdsa_subkey.clone(), ed25519_subkey.clone()] { let res = do_start_signing_session( deps.as_mut(), PROVER, &verifier_set_id, - "mock-chain".parse().unwrap(), + chain_name.clone(), ); assert!(res.is_ok()); } + let caller_authorization_status = + query::caller_authorized(deps.as_ref(), prover_address.clone(), chain_name.clone()) + .unwrap(); + assert!(caller_authorization_status); + // unauthorize - do_unauthorize_caller(deps.as_mut(), Addr::unchecked(PROVER)).unwrap(); + do_unauthorize_caller( + deps.as_mut(), + vec![(prover_address.clone(), chain_name.clone())], + ) + .unwrap(); for verifier_set_id in [ecdsa_subkey, ed25519_subkey] { let res = do_start_signing_session( deps.as_mut(), PROVER, &verifier_set_id, - "mock-chain".parse().unwrap(), + chain_name.clone(), ); assert!(res .unwrap_err() .to_string() - .contains(&ContractError::Unauthorized.to_string())); + .contains(&permission_control::Error::Unauthorized.to_string())); } + + let caller_authorization_status = + query::caller_authorized(deps.as_ref(), prover_address, chain_name.clone()).unwrap(); + assert!(!caller_authorization_status); + } + + #[test] + fn authorize_and_unauthorize_many_callers() { + let (mut deps, _, _) = setup(); + + let contracts = vec![ + (Addr::unchecked("addr1"), "chain1".parse().unwrap()), + (Addr::unchecked("addr2"), "chain2".parse().unwrap()), + (Addr::unchecked("addr3"), "chain3".parse().unwrap()), + ]; + do_authorize_callers(deps.as_mut(), contracts.clone()).unwrap(); + assert!(contracts + .iter() + .all(|(addr, chain_name)| query::caller_authorized( + deps.as_ref(), + addr.clone(), + chain_name.clone() + ) + .unwrap())); + let (authorized, unauthorized) = contracts.split_at(1); + do_unauthorize_caller(deps.as_mut(), unauthorized.to_vec()).unwrap(); + assert!(unauthorized + .iter() + .all(|(addr, chain_name)| !query::caller_authorized( + deps.as_ref(), + addr.clone(), + chain_name.clone() + ) + .unwrap())); + assert!(authorized + .iter() + .all(|(addr, chain_name)| query::caller_authorized( + deps.as_ref(), + addr.clone(), + chain_name.clone() + ) + .unwrap())); } #[test] @@ -1005,14 +1193,18 @@ mod tests { let info = mock_info("user", &[]); let env = mock_env(); - let msg = ExecuteMsg::AuthorizeCaller { - contract_address: Addr::unchecked(PROVER), + let msg = ExecuteMsg::AuthorizeCallers { + contracts: HashMap::from([(PROVER.to_string(), "mock-chain".parse().unwrap())]), }; let res = execute(deps.as_mut(), env, info, msg); assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from(ContractError::Unauthorized).to_string() + permission_control::Error::PermissionDenied { + expected: Permission::Governance.into(), + actual: Permission::NoPrivilege.into() + } + .to_string() ); } @@ -1023,14 +1215,134 @@ mod tests { let info = mock_info("user", &[]); let env = mock_env(); - let msg = ExecuteMsg::UnauthorizeCaller { - contract_address: Addr::unchecked(PROVER), + let msg = ExecuteMsg::UnauthorizeCallers { + contracts: HashMap::from([(PROVER.to_string(), "mock-chain".parse().unwrap())]), }; let res = execute(deps.as_mut(), env, info, msg); assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from(ContractError::Unauthorized).to_string() + permission_control::Error::PermissionDenied { + expected: Permission::Elevated.into(), + actual: Permission::NoPrivilege.into() + } + .to_string() ); } + + #[test] + fn disable_enable_signing() { + let (mut deps, ecdsa_subkey, ed25519_subkey) = setup(); + let prover_address = Addr::unchecked(PROVER); + let chain_name: ChainName = "mock-chain".parse().unwrap(); + + // authorize + do_authorize_callers( + deps.as_mut(), + vec![(prover_address.clone(), chain_name.clone())], + ) + .unwrap(); + + do_disable_signing(deps.as_mut(), ADMIN).unwrap(); + + for verifier_set_id in [ecdsa_subkey.clone(), ed25519_subkey.clone()] { + let res = do_start_signing_session( + deps.as_mut(), + PROVER, + &verifier_set_id, + chain_name.clone(), + ); + + assert_eq!( + res.unwrap_err().to_string(), + ContractError::SigningDisabled.to_string() + ); + } + + do_enable_signing(deps.as_mut(), ADMIN).unwrap(); + + for verifier_set_id in [ecdsa_subkey.clone(), ed25519_subkey.clone()] { + let res = do_start_signing_session( + deps.as_mut(), + PROVER, + &verifier_set_id, + "mock-chain".parse().unwrap(), + ); + + assert!(res.is_ok()); + } + } + + #[test] + fn disable_signing_after_session_creation() { + let (mut deps, ecdsa_subkey, ed25519_subkey) = setup(); + let chain_name: ChainName = "mock-chain".parse().unwrap(); + do_authorize_callers( + deps.as_mut(), + vec![(Addr::unchecked(PROVER), chain_name.clone())], + ) + .unwrap(); + + for (_, verifier_set_id, signers, session_id) in + signature_test_data(&ecdsa_subkey, &ed25519_subkey) + { + do_start_signing_session(deps.as_mut(), PROVER, verifier_set_id, chain_name.clone()) + .unwrap(); + + do_disable_signing(deps.as_mut(), ADMIN).unwrap(); + + let signer = signers.first().unwrap().to_owned(); + + let res = do_sign(deps.as_mut(), mock_env(), session_id, &signer); + + assert_eq!( + res.unwrap_err().to_string(), + ContractError::SigningDisabled.to_string() + ); + + do_enable_signing(deps.as_mut(), ADMIN).unwrap(); + assert!(do_sign(deps.as_mut(), mock_env(), session_id, &signer).is_ok()); + } + } + + #[test] + fn disable_enable_signing_has_correct_permissions() { + let mut deps = setup().0; + + assert!(do_disable_signing(deps.as_mut(), "user1").is_err()); + assert!(do_disable_signing(deps.as_mut(), ADMIN).is_ok()); + assert!(do_enable_signing(deps.as_mut(), "user").is_err()); + assert!(do_enable_signing(deps.as_mut(), ADMIN).is_ok()); + assert!(do_disable_signing(deps.as_mut(), GOVERNANCE).is_ok()); + assert!(do_enable_signing(deps.as_mut(), GOVERNANCE).is_ok()); + } + + #[test] + fn start_signing_session_wrong_chain() { + let (mut deps, ecdsa_subkey, ed25519_subkey) = setup(); + let chain_name: ChainName = "mock-chain".parse().unwrap(); + do_authorize_callers( + deps.as_mut(), + vec![(Addr::unchecked(PROVER), chain_name.clone())], + ) + .unwrap(); + + let wrong_chain_name: ChainName = "some-other-chain".parse().unwrap(); + + for verifier_set_id in [ecdsa_subkey, ed25519_subkey] { + let res = do_start_signing_session( + deps.as_mut(), + PROVER, + &verifier_set_id, + wrong_chain_name.clone(), + ); + + assert!(res.unwrap_err().to_string().contains( + &ContractError::WrongChainName { + expected: chain_name.clone() + } + .to_string() + )); + } + } } diff --git a/contracts/multisig/src/contract/execute.rs b/contracts/multisig/src/contract/execute.rs index 3fc0a3cbf..9993622e7 100644 --- a/contracts/multisig/src/contract/execute.rs +++ b/contracts/multisig/src/contract/execute.rs @@ -1,19 +1,15 @@ -use cosmwasm_std::{OverflowError, OverflowOperation, WasmMsg}; +use std::collections::HashMap; + +use cosmwasm_std::{ensure, OverflowError, OverflowOperation, Storage, WasmMsg}; use router_api::ChainName; use sha3::{Digest, Keccak256}; use signature_verifier_api::client::SignatureVerifier; -use crate::signing::validate_session_signature; -use crate::state::{load_session_signatures, save_pub_key, save_signature}; -use crate::verifier_set::VerifierSet; -use crate::{ - key::{KeyTyped, PublicKey, Signature}, - signing::SigningSession, - state::AUTHORIZED_CALLERS, -}; -use error_stack::ResultExt; - use super::*; +use crate::key::{KeyTyped, PublicKey, Signature}; +use crate::signing::{validate_session_signature, SigningSession}; +use crate::state::{load_session_signatures, save_pub_key, save_signature, AUTHORIZED_CALLERS}; +use crate::verifier_set::VerifierSet; pub fn start_signing_session( deps: DepsMut, @@ -23,8 +19,14 @@ pub fn start_signing_session( chain_name: ChainName, sig_verifier: Option, ) -> Result { + ensure!( + killswitch::is_contract_active(deps.storage), + ContractError::SigningDisabled + ); + let config = CONFIG.load(deps.storage)?; - let verifier_set = get_verifier_set(deps.storage, &verifier_set_id)?; + + let verifier_set = verifier_set(deps.storage, &verifier_set_id)?; let session_id = SIGNING_SESSION_COUNTER.update( deps.storage, @@ -39,7 +41,7 @@ pub fn start_signing_session( let expires_at = env .block .height - .checked_add(config.block_expiry) + .checked_add(config.block_expiry.into()) .ok_or_else(|| { OverflowError::new( OverflowOperation::Add, @@ -62,7 +64,7 @@ pub fn start_signing_session( let event = Event::SigningStarted { session_id, verifier_set_id, - pub_keys: verifier_set.get_pub_keys(), + pub_keys: verifier_set.pub_keys(), msg, chain_name, expires_at, @@ -80,6 +82,11 @@ pub fn submit_signature( session_id: Uint64, signature: HexBinary, ) -> Result { + ensure!( + killswitch::is_contract_active(deps.storage), + ContractError::SigningDisabled + ); + let config = CONFIG.load(deps.storage)?; let mut session = SIGNING_SESSIONS .load(deps.storage, session_id.into()) @@ -171,35 +178,66 @@ pub fn register_pub_key( } pub fn require_authorized_caller( - deps: &DepsMut, - contract_address: Addr, -) -> error_stack::Result<(), ContractError> { - AUTHORIZED_CALLERS - .load(deps.storage, &contract_address) - .change_context(ContractError::Unauthorized) + storage: &dyn Storage, + contract_address: &Addr, + chain_name: &ChainName, +) -> Result { + let expected_chain_name = AUTHORIZED_CALLERS.load(storage, contract_address)?; + if expected_chain_name != *chain_name { + return Err(ContractError::WrongChainName { + expected: expected_chain_name, + }); + } + Ok(contract_address.clone()) } -pub fn authorize_caller(deps: DepsMut, contract_address: Addr) -> Result { - AUTHORIZED_CALLERS.save(deps.storage, &contract_address, &())?; - - Ok(Response::new().add_event(Event::CallerAuthorized { contract_address }.into())) +pub fn authorize_callers( + deps: DepsMut, + contracts: HashMap, +) -> Result { + contracts + .iter() + .map(|(contract_address, chain_name)| { + AUTHORIZED_CALLERS.save(deps.storage, contract_address, chain_name) + }) + .try_collect()?; + + Ok( + Response::new().add_events(contracts.into_iter().map(|(contract_address, chain_name)| { + Event::CallerAuthorized { + contract_address, + chain_name, + } + .into() + })), + ) } -pub fn unauthorize_caller( +pub fn unauthorize_callers( deps: DepsMut, - contract_address: Addr, + contracts: HashMap, ) -> Result { - AUTHORIZED_CALLERS.remove(deps.storage, &contract_address); + contracts.iter().for_each(|(contract_address, _)| { + AUTHORIZED_CALLERS.remove(deps.storage, contract_address) + }); + + Ok( + Response::new().add_events(contracts.into_iter().map(|(contract_address, chain_name)| { + Event::CallerUnauthorized { + contract_address, + chain_name, + } + .into() + })), + ) +} - Ok(Response::new().add_event(Event::CallerUnauthorized { contract_address }.into())) +pub fn enable_signing(deps: DepsMut) -> Result { + killswitch::disengage(deps.storage, Event::SigningEnabled).map_err(|err| err.into()) } -pub fn require_governance(deps: &DepsMut, sender: Addr) -> Result<(), ContractError> { - let config = CONFIG.load(deps.storage)?; - if config.governance != sender { - return Err(ContractError::Unauthorized); - } - Ok(()) +pub fn disable_signing(deps: DepsMut) -> Result { + killswitch::engage(deps.storage, Event::SigningDisabled).map_err(|err| err.into()) } fn signing_response( @@ -212,7 +250,7 @@ fn signing_response( let rewards_msg = WasmMsg::Execute { contract_addr: rewards_contract, msg: to_json_binary(&rewards::msg::ExecuteMsg::RecordParticipation { - chain_name: session.chain_name, + chain_name: session.chain_name.clone(), event_id: session .id .to_string() @@ -240,6 +278,7 @@ fn signing_response( Event::SigningCompleted { session_id: session.id, completed_at, + chain_name: session.chain_name, } .into(), ) diff --git a/contracts/multisig/src/contract/migrations/mod.rs b/contracts/multisig/src/contract/migrations/mod.rs new file mode 100644 index 000000000..080e953e7 --- /dev/null +++ b/contracts/multisig/src/contract/migrations/mod.rs @@ -0,0 +1 @@ +pub mod v0_4_1; diff --git a/contracts/multisig/src/contract/migrations/v0_4_1.rs b/contracts/multisig/src/contract/migrations/v0_4_1.rs new file mode 100644 index 000000000..24570ab1e --- /dev/null +++ b/contracts/multisig/src/contract/migrations/v0_4_1.rs @@ -0,0 +1,369 @@ +#![allow(deprecated)] + +use axelar_wasm_std::killswitch::State; +use axelar_wasm_std::{killswitch, nonempty, permission_control}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Order, StdError, Storage}; +use cw2::VersionError; +use cw_storage_plus::Item; +use itertools::Itertools; +use router_api::ChainName; + +use crate::contract::CONTRACT_NAME; +use crate::signing::SigningSession; +use crate::state::{AUTHORIZED_CALLERS, SIGNING_SESSIONS, VERIFIER_SETS}; + +const BASE_VERSION: &str = "0.4.1"; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Std(#[from] StdError), + #[error(transparent)] + Version(#[from] VersionError), + #[error(transparent)] + NonEmpty(#[from] nonempty::Error), +} + +pub fn migrate( + storage: &mut dyn Storage, + admin: Addr, + authorized_callers: Vec<(Addr, ChainName)>, +) -> Result<(), Error> { + cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; + + killswitch::init(storage, State::Disengaged)?; + + let config = ensure_expiry_height_is_not_zero(storage)?; + permission_control::set_governance(storage, &config.governance)?; + permission_control::set_admin(storage, &admin)?; + migrate_config(storage, config)?; + migrate_authorized_callers(storage, authorized_callers)?; + migrate_signing_sessions(storage)?; + migrate_verifier_set_ids(storage)?; + Ok(()) +} + +fn migrate_authorized_callers( + storage: &mut dyn Storage, + authorized_callers: Vec<(Addr, ChainName)>, +) -> Result<(), Error> { + AUTHORIZED_CALLERS.clear(storage); + authorized_callers + .iter() + .map(|(contract_address, chain_name)| { + AUTHORIZED_CALLERS.save(storage, contract_address, chain_name) + }) + .try_collect()?; + Ok(()) +} + +pub fn migrate_signing_sessions(storage: &mut dyn Storage) -> Result<(), Error> { + let all: Vec<_> = SIGNING_SESSIONS + .range(storage, None, None, Order::Ascending) + .collect::, _>>()?; + + for (session_id, session) in all { + let verifier_set = VERIFIER_SETS.load(storage, &session.verifier_set_id)?; + let new_session = SigningSession { + verifier_set_id: verifier_set.id(), + ..session + }; + SIGNING_SESSIONS.save(storage, session_id, &new_session)?; + } + + Ok(()) +} + +pub fn migrate_verifier_set_ids(storage: &mut dyn Storage) -> Result<(), Error> { + let all: Vec<_> = VERIFIER_SETS + .range(storage, None, None, Order::Ascending) + .collect::, _>>()?; + + for v in all { + VERIFIER_SETS.remove(storage, &v.0); + VERIFIER_SETS.save(storage, &v.1.id(), &v.1)?; + } + + Ok(()) +} + +fn ensure_expiry_height_is_not_zero(storage: &mut dyn Storage) -> Result { + CONFIG.update(storage, |mut config| { + if config.block_expiry == 0 { + config.block_expiry = 10; + } + Ok(config) + }) +} + +fn migrate_config(storage: &mut dyn Storage, config: Config) -> Result<(), Error> { + let new_config = crate::state::Config { + rewards_contract: config.rewards_contract, + block_expiry: nonempty::Uint64::try_from(config.block_expiry)?, + }; + + CONFIG.remove(storage); + crate::state::CONFIG.save(storage, &new_config)?; + Ok(()) +} + +#[cw_serde] +#[deprecated(since = "0.4.1", note = "only used during migration")] +struct Config { + pub governance: Addr, + pub rewards_contract: Addr, + pub block_expiry: u64, // number of blocks after which a signing session expires +} + +#[deprecated(since = "0.4.1", note = "only used during migration")] +const CONFIG: Item = Item::new("config"); + +#[cfg(test)] +mod tests { + use axelar_wasm_std::nonempty; + use cosmwasm_schema::cw_serde; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{Addr, DepsMut, Env, HexBinary, MessageInfo, Response, Uint64}; + use router_api::ChainName; + + use crate::contract::migrations::v0_4_1::{self, BASE_VERSION}; + use crate::contract::{execute, query, CONTRACT_NAME}; + use crate::msg::ExecuteMsg::{DisableSigning, SubmitSignature}; + use crate::signing::SigningSession; + use crate::state::{SIGNING_SESSIONS, SIGNING_SESSION_COUNTER, VERIFIER_SETS}; + use crate::test::common::build_verifier_set; + use crate::test::common::ecdsa_test_data::signers; + use crate::ContractError; + + #[test] + fn migrate_checks_contract_version() { + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + governance_address: "governance".to_string(), + rewards_address: "rewards".to_string(), + block_expiry: 100, + }, + ) + .unwrap(); + + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap(); + + assert!(v0_4_1::migrate(deps.as_mut().storage, Addr::unchecked("admin"), vec![]).is_err()); + + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, BASE_VERSION).unwrap(); + + assert!(v0_4_1::migrate(deps.as_mut().storage, Addr::unchecked("admin"), vec![]).is_ok()); + } + + #[test] + fn migrate_config() { + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + governance_address: "governance".to_string(), + rewards_address: "rewards".to_string(), + block_expiry: 0, + }, + ) + .unwrap(); + + assert!(v0_4_1::migrate(deps.as_mut().storage, Addr::unchecked("admin"), vec![]).is_ok()); + + assert!(v0_4_1::CONFIG.load(deps.as_mut().storage).is_err()); + + let new_config = crate::state::CONFIG.load(deps.as_mut().storage); + assert!(new_config.is_ok()); + let new_config = new_config.unwrap(); + assert_eq!( + new_config.block_expiry, + nonempty::Uint64::try_from(10).unwrap() + ); + } + + #[test] + fn permissions_are_set_after_migration_and_contract_can_be_disabled() { + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + governance_address: "governance".to_string(), + rewards_address: "rewards".to_string(), + block_expiry: 100, + }, + ) + .unwrap(); + + assert!(v0_4_1::migrate(deps.as_mut().storage, Addr::unchecked("admin"), vec![]).is_ok()); + + // contract is enabled + assert!(!execute( + deps.as_mut(), + mock_env(), + mock_info("any_addr", &[]), + SubmitSignature { + session_id: 0u64.into(), + signature: HexBinary::from_hex("04").unwrap(), + } + ) + .unwrap_err() + .to_string() + .contains(&ContractError::SigningDisabled.to_string())); + + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info("any_addr", &[]), + DisableSigning + ) + .is_err()); + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + DisableSigning + ) + .is_ok()); + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info("governance", &[]), + DisableSigning + ) + .is_ok()); + + // contract is disabled + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info("any_addr", &[]), + SubmitSignature { + session_id: 0u64.into(), + signature: HexBinary::from_hex("04").unwrap(), + } + ) + .unwrap_err() + .to_string() + .contains(&ContractError::SigningDisabled.to_string())); + } + + #[test] + fn callers_are_authorized() { + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + governance_address: "governance".to_string(), + rewards_address: "rewards".to_string(), + block_expiry: 100, + }, + ) + .unwrap(); + + let prover = Addr::unchecked("prover1"); + let chain_name: ChainName = "mock-chain".parse().unwrap(); + assert!(v0_4_1::migrate( + deps.as_mut().storage, + Addr::unchecked("admin"), + vec![(prover.clone(), chain_name.clone())] + ) + .is_ok()); + assert!(query::caller_authorized(deps.as_ref(), prover, chain_name).unwrap()); + } + + #[test] + fn should_be_able_to_migrate_verifier_set_ids() { + let mut deps = mock_dependencies(); + + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + governance_address: "governance".to_string(), + rewards_address: "rewards".to_string(), + block_expiry: 100, + }, + ) + .unwrap(); + let signers = signers(); + let verifier_set = build_verifier_set(crate::key::KeyType::Ecdsa, &signers); + VERIFIER_SETS + .save(&mut deps.storage, "foobar", &verifier_set) + .unwrap(); + let signing_session = SigningSession { + id: Uint64::one(), + verifier_set_id: "foobar".to_string(), + chain_name: "ethereum".parse().unwrap(), + msg: HexBinary::from([2; 32]).try_into().unwrap(), + state: crate::types::MultisigState::Pending, + expires_at: 100, + sig_verifier: None, + }; + SIGNING_SESSIONS + .save( + &mut deps.storage, + signing_session.id.u64(), + &signing_session, + ) + .unwrap(); + + v0_4_1::migrate(deps.as_mut().storage, Addr::unchecked("admin"), vec![]).unwrap(); + + let new_verifier_set = VERIFIER_SETS + .load(&deps.storage, &verifier_set.id()) + .unwrap(); + assert_eq!(new_verifier_set, verifier_set); + + let expected_signing_session = SigningSession { + verifier_set_id: verifier_set.id(), + ..signing_session + }; + let new_signing_session = SIGNING_SESSIONS + .load(&deps.storage, expected_signing_session.id.u64()) + .unwrap(); + assert_eq!(new_signing_session, expected_signing_session); + } + + #[deprecated(since = "0.4.1", note = "only used to test the migration")] + fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, + ) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, BASE_VERSION)?; + + let config = v0_4_1::Config { + governance: deps.api.addr_validate(&msg.governance_address)?, + rewards_contract: deps.api.addr_validate(&msg.rewards_address)?, + block_expiry: msg.block_expiry, + }; + + v0_4_1::CONFIG.save(deps.storage, &config)?; + + SIGNING_SESSION_COUNTER.save(deps.storage, &Uint64::zero())?; + + Ok(Response::default()) + } + + #[cw_serde] + #[deprecated(since = "0.4.1", note = "only used to test the migration")] + struct InstantiateMsg { + // the governance address is allowed to modify the authorized caller list for this contract + pub governance_address: String, + pub rewards_address: String, + pub block_expiry: u64, + } +} diff --git a/contracts/multisig/src/contract/query.rs b/contracts/multisig/src/contract/query.rs index e79b1100d..8279883ee 100644 --- a/contracts/multisig/src/contract/query.rs +++ b/contracts/multisig/src/contract/query.rs @@ -1,13 +1,12 @@ -use crate::{ - key::{KeyType, PublicKey}, - multisig::Multisig, - state::{load_pub_key, load_session_signatures}, - verifier_set::VerifierSet, -}; +use router_api::ChainName; use super::*; +use crate::key::{KeyType, PublicKey}; +use crate::multisig::Multisig; +use crate::state::{load_pub_key, load_session_signatures, AUTHORIZED_CALLERS}; +use crate::verifier_set::VerifierSet; -pub fn get_multisig(deps: Deps, session_id: Uint64) -> StdResult { +pub fn multisig(deps: Deps, session_id: Uint64) -> StdResult { let session = SIGNING_SESSIONS.load(deps.storage, session_id.into())?; let verifier_set = VERIFIER_SETS.load(deps.storage, &session.verifier_set_id)?; @@ -20,11 +19,16 @@ pub fn get_multisig(deps: Deps, session_id: Uint64) -> StdResult { }) } -pub fn get_verifier_set(deps: Deps, verifier_set_id: String) -> StdResult { +pub fn verifier_set(deps: Deps, verifier_set_id: String) -> StdResult { VERIFIER_SETS.load(deps.storage, &verifier_set_id) } -pub fn get_public_key(deps: Deps, verifier: Addr, key_type: KeyType) -> StdResult { +pub fn public_key(deps: Deps, verifier: Addr, key_type: KeyType) -> StdResult { let raw = load_pub_key(deps.storage, verifier, key_type)?; Ok(PublicKey::try_from((key_type, raw)).expect("could not decode pub key")) } + +pub fn caller_authorized(deps: Deps, address: Addr, chain_name: ChainName) -> StdResult { + let is_authorized = AUTHORIZED_CALLERS.may_load(deps.storage, &address)? == Some(chain_name); + Ok(is_authorized) +} diff --git a/contracts/multisig/src/ed25519.rs b/contracts/multisig/src/ed25519.rs index 29c2159a7..550e3ace7 100644 --- a/contracts/multisig/src/ed25519.rs +++ b/contracts/multisig/src/ed25519.rs @@ -14,9 +14,8 @@ pub fn ed25519_verify(msg_hash: &[u8], sig: &[u8], pub_key: &[u8]) -> Result for cosmwasm_std::Event { @@ -74,9 +77,11 @@ impl From for cosmwasm_std::Event { Event::SigningCompleted { session_id, completed_at, + chain_name, } => cosmwasm_std::Event::new("signing_completed") .add_attribute("session_id", session_id) - .add_attribute("completed_at", completed_at.to_string()), + .add_attribute("completed_at", completed_at.to_string()) + .add_attribute("chain", chain_name), Event::PublicKeyRegistered { verifier, public_key, @@ -89,14 +94,20 @@ impl From for cosmwasm_std::Event { "public_key", to_string(&public_key).expect("failed to serialize public key"), ), - Event::CallerAuthorized { contract_address } => { - cosmwasm_std::Event::new("caller_authorized") - .add_attribute("contract_address", contract_address) - } - Event::CallerUnauthorized { contract_address } => { - cosmwasm_std::Event::new("caller_unauthorized") - .add_attribute("contract_address", contract_address) - } + Event::CallerAuthorized { + contract_address, + chain_name, + } => cosmwasm_std::Event::new("caller_authorized") + .add_attribute("contract_address", contract_address) + .add_attribute("chain_name", chain_name), + Event::CallerUnauthorized { + contract_address, + chain_name, + } => cosmwasm_std::Event::new("caller_unauthorized") + .add_attribute("contract_address", contract_address) + .add_attribute("chain_name", chain_name), + Event::SigningEnabled => cosmwasm_std::Event::new("signing_enabled"), + Event::SigningDisabled => cosmwasm_std::Event::new("signing_disabled"), } } } diff --git a/contracts/multisig/src/key.rs b/contracts/multisig/src/key.rs index f2793e0b9..fb73bfc80 100644 --- a/contracts/multisig/src/key.rs +++ b/contracts/multisig/src/key.rs @@ -1,14 +1,15 @@ -use crate::{ - ed25519::{ed25519_verify, ED25519_SIGNATURE_LEN}, - secp256k1::ecdsa_verify, - ContractError, -}; +use std::fmt::Display; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{HexBinary, StdError, StdResult}; use cw_storage_plus::{KeyDeserialize, PrimaryKey}; use enum_display_derive::Display; -use serde::{de::Error, Deserialize, Deserializer}; -use std::fmt::Display; +use serde::de::Error; +use serde::{Deserialize, Deserializer}; + +use crate::ed25519::{ed25519_verify, ED25519_SIGNATURE_LEN}; +use crate::secp256k1::ecdsa_verify; +use crate::ContractError; const ECDSA_COMPRESSED_PUBKEY_LEN: usize = 33; @@ -130,7 +131,7 @@ where { let pk: HexBinary = Deserialize::deserialize(deserializer)?; PublicKey::try_from((KeyType::Ecdsa, pk.clone())) - .map_err(|err| D::Error::custom(format!("failed to deserialize public key: {}", err)))?; + .map_err(|err| Error::custom(format!("failed to deserialize public key: {}", err)))?; Ok(pk) } @@ -140,7 +141,7 @@ where { let pk: HexBinary = Deserialize::deserialize(deserializer)?; PublicKey::try_from((KeyType::Ed25519, pk.clone())) - .map_err(|e| D::Error::custom(format!("failed to deserialize public key: {}", e)))?; + .map_err(|e| Error::custom(format!("failed to deserialize public key: {}", e)))?; Ok(pk) } @@ -312,8 +313,8 @@ impl AsRef<[u8]> for Signature { impl From for HexBinary { fn from(original: PublicKey) -> Self { match original { - PublicKey::Ecdsa(sig) => sig, - PublicKey::Ed25519(sig) => sig, + PublicKey::Ecdsa(key) => key, + PublicKey::Ed25519(key) => key, } } } @@ -323,14 +324,11 @@ mod ecdsa_tests { use cosmwasm_std::HexBinary; use k256::{AffinePoint, EncodedPoint}; - use crate::{ - key::{validate_and_normalize_public_key, Signature}, - test::common::ecdsa_test_data, - types::MsgToSign, - ContractError, - }; - use super::{KeyType, PublicKey}; + use crate::key::{validate_and_normalize_public_key, Signature}; + use crate::test::common::ecdsa_test_data; + use crate::types::MsgToSign; + use crate::ContractError; #[test] fn deserialize_ecdsa_key() { @@ -491,14 +489,11 @@ mod ed25519_tests { use cosmwasm_std::HexBinary; use curve25519_dalek::edwards::CompressedEdwardsY; - use crate::{ - key::{validate_and_normalize_public_key, Signature}, - test::common::ed25519_test_data, - types::MsgToSign, - ContractError, - }; - use super::{KeyType, PublicKey}; + use crate::key::{validate_and_normalize_public_key, Signature}; + use crate::test::common::ed25519_test_data; + use crate::types::MsgToSign; + use crate::ContractError; #[test] fn deserialize_ed25519_key() { diff --git a/contracts/multisig/src/msg.rs b/contracts/multisig/src/msg.rs index fbae987b5..213d76c81 100644 --- a/contracts/multisig/src/msg.rs +++ b/contracts/multisig/src/msg.rs @@ -1,24 +1,37 @@ +use std::collections::HashMap; + +use axelar_wasm_std::nonempty; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, HexBinary, Uint128, Uint64}; +use msgs_derive::EnsurePermissions; use router_api::ChainName; -use crate::{ - key::{KeyType, PublicKey, Signature}, - multisig::Multisig, - verifier_set::VerifierSet, -}; +use crate::key::{KeyType, PublicKey, Signature}; +use crate::multisig::Multisig; +use crate::verifier_set::VerifierSet; + +#[cw_serde] +pub struct MigrationMsg { + pub admin_address: String, + pub authorized_callers: HashMap, +} #[cw_serde] pub struct InstantiateMsg { - // the governance address is allowed to modify the authorized caller list for this contract + /// the governance address is allowed to modify the authorized caller list for this contract pub governance_address: String, + /// The admin address (or governance) is allowed to disable signing and enable signing + pub admin_address: String, pub rewards_address: String, - pub block_expiry: u64, + /// number of blocks after which a signing session expires + pub block_expiry: nonempty::Uint64, } #[cw_serde] +#[derive(EnsurePermissions)] pub enum ExecuteMsg { - // Can only be called by an authorized contract. + /// Can only be called by an authorized contract. + #[permission(Specific(authorized))] StartSigningSession { verifier_set_id: String, msg: HexBinary, @@ -31,43 +44,60 @@ pub enum ExecuteMsg { /// [signature_verifier_api::msg] sig_verifier: Option, }, + #[permission(Any)] SubmitSignature { session_id: Uint64, signature: HexBinary, }, - RegisterVerifierSet { - verifier_set: VerifierSet, - }, + #[permission(Any)] + RegisterVerifierSet { verifier_set: VerifierSet }, + #[permission(Any)] RegisterPublicKey { public_key: PublicKey, - /* To prevent anyone from registering a public key that belongs to someone else, we require the sender - to sign their own address using the private key */ + /// To prevent anyone from registering a public key that belongs to someone else, we require the sender + /// to sign their own address using the private key signed_sender_address: HexBinary, }, - // Authorizes a contract to call StartSigningSession. Callable only by governance - AuthorizeCaller { - contract_address: Addr, + /// Authorizes a set of contracts to call StartSigningSession. + #[permission(Governance)] + AuthorizeCallers { + contracts: HashMap, }, - // Unauthorizes a contract, so it can no longer call StartSigningSession. Callable only by governance - UnauthorizeCaller { - contract_address: Addr, + /// Unauthorizes a set of contracts, so they can no longer call StartSigningSession. + #[permission(Elevated)] + UnauthorizeCallers { + contracts: HashMap, }, + + /// Emergency command to stop all amplifier signing + #[permission(Elevated)] + DisableSigning, + + /// Resumes routing after an emergency shutdown + #[permission(Elevated)] + EnableSigning, } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { #[returns(Multisig)] - GetMultisig { session_id: Uint64 }, + Multisig { session_id: Uint64 }, #[returns(VerifierSet)] - GetVerifierSet { verifier_set_id: String }, + VerifierSet { verifier_set_id: String }, #[returns(PublicKey)] - GetPublicKey { + PublicKey { verifier_address: String, key_type: KeyType, }, + + #[returns(bool)] + IsCallerAuthorized { + contract_address: String, + chain_name: ChainName, + }, } #[cw_serde] diff --git a/contracts/multisig/src/multisig.rs b/contracts/multisig/src/multisig.rs index 413e90a75..8e876d364 100644 --- a/contracts/multisig/src/multisig.rs +++ b/contracts/multisig/src/multisig.rs @@ -4,12 +4,10 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Uint128; use itertools::Itertools; -use crate::{ - key::Signature, - msg::{Signer, SignerWithSig}, - types::MultisigState, - verifier_set::VerifierSet, -}; +use crate::key::Signature; +use crate::msg::{Signer, SignerWithSig}; +use crate::types::MultisigState; +use crate::verifier_set::VerifierSet; #[cw_serde] pub struct Multisig { @@ -51,13 +49,11 @@ impl Multisig { mod test { use cosmwasm_std::{Addr, HexBinary, Uint128}; - use crate::{ - key::{PublicKey, Signature}, - msg::Signer, - multisig::Multisig, - types::MultisigState, - verifier_set::VerifierSet, - }; + use crate::key::{PublicKey, Signature}; + use crate::msg::Signer; + use crate::multisig::Multisig; + use crate::types::MultisigState; + use crate::verifier_set::VerifierSet; #[test] fn optimize_signatures() { diff --git a/contracts/multisig/src/secp256k1.rs b/contracts/multisig/src/secp256k1.rs index c82071575..abedd433d 100644 --- a/contracts/multisig/src/secp256k1.rs +++ b/contracts/multisig/src/secp256k1.rs @@ -14,9 +14,8 @@ pub fn ecdsa_verify(msg_hash: &[u8], sig: &[u8], pub_key: &[u8]) -> Result, verifier_set: &Verifi #[cfg(test)] mod tests { - use cosmwasm_std::{testing::MockQuerier, to_json_binary, Addr, HexBinary, QuerierWrapper}; - - use crate::{ - key::KeyType, - test::common::build_verifier_set, - test::common::{ecdsa_test_data, ed25519_test_data}, - }; + use cosmwasm_std::testing::MockQuerier; + use cosmwasm_std::{to_json_binary, Addr, HexBinary, QuerierWrapper}; use super::*; + use crate::key::KeyType; + use crate::test::common::{build_verifier_set, ecdsa_test_data, ed25519_test_data}; pub struct TestConfig { pub verifier_set: VerifierSet, diff --git a/contracts/multisig/src/state.rs b/contracts/multisig/src/state.rs index 66e5cf529..43979c058 100644 --- a/contracts/multisig/src/state.rs +++ b/contracts/multisig/src/state.rs @@ -1,21 +1,20 @@ use std::collections::HashMap; +use axelar_wasm_std::nonempty; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, HexBinary, Order, StdResult, Storage, Uint64}; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, UniqueIndex}; +use router_api::ChainName; -use crate::{ - key::{KeyType, KeyTyped, PublicKey, Signature}, - signing::SigningSession, - verifier_set::VerifierSet, - ContractError, -}; +use crate::key::{KeyType, KeyTyped, PublicKey, Signature}; +use crate::signing::SigningSession; +use crate::verifier_set::VerifierSet; +use crate::ContractError; #[cw_serde] pub struct Config { - pub governance: Addr, pub rewards_contract: Addr, - pub block_expiry: u64, // number of blocks after which a signing session expires + pub block_expiry: nonempty::Uint64, // number of blocks after which a signing session expires } pub const CONFIG: Item = Item::new("config"); @@ -58,7 +57,7 @@ pub fn save_signature( type VerifierSetId = str; pub const VERIFIER_SETS: Map<&VerifierSetId, VerifierSet> = Map::new("verifier_sets"); -pub fn get_verifier_set( +pub fn verifier_set( store: &dyn Storage, verifier_set_id: &str, ) -> Result { @@ -111,16 +110,15 @@ pub fn save_pub_key( } // The keys represent the addresses that can start a signing session. -pub const AUTHORIZED_CALLERS: Map<&Addr, ()> = Map::new("authorized_callers"); +pub const AUTHORIZED_CALLERS: Map<&Addr, ChainName> = Map::new("authorized_callers"); #[cfg(test)] mod tests { use cosmwasm_std::testing::mock_dependencies; - use crate::test::common::ecdsa_test_data; - use super::*; + use crate::test::common::ecdsa_test_data; #[test] fn should_fail_if_duplicate_public_key() { diff --git a/contracts/multisig/src/test/common.rs b/contracts/multisig/src/test/common.rs index ff32de0c6..9b0ad9335 100644 --- a/contracts/multisig/src/test/common.rs +++ b/contracts/multisig/src/test/common.rs @@ -1,10 +1,8 @@ use axelar_wasm_std::Participant; use cosmwasm_std::{Addr, HexBinary, Uint128}; -use crate::{ - key::{KeyType, PublicKey}, - verifier_set::VerifierSet, -}; +use crate::key::{KeyType, PublicKey}; +use crate::verifier_set::VerifierSet; #[derive(Clone)] pub struct TestSigner { diff --git a/contracts/multisig/src/testdata/verifier_set_hash_unchanged.golden b/contracts/multisig/src/testdata/verifier_set_hash_unchanged.golden new file mode 100644 index 000000000..4c871bd0e --- /dev/null +++ b/contracts/multisig/src/testdata/verifier_set_hash_unchanged.golden @@ -0,0 +1 @@ +"1dbf312c423dd70771c471c71d18e3d756aaab6d5b313b41289e002017610e84" \ No newline at end of file diff --git a/contracts/multisig/src/testdata/verifier_set_id_unchanged.golden b/contracts/multisig/src/testdata/verifier_set_id_unchanged.golden new file mode 100644 index 000000000..4c871bd0e --- /dev/null +++ b/contracts/multisig/src/testdata/verifier_set_id_unchanged.golden @@ -0,0 +1 @@ +"1dbf312c423dd70771c471c71d18e3d756aaab6d5b313b41289e002017610e84" \ No newline at end of file diff --git a/contracts/multisig/src/types.rs b/contracts/multisig/src/types.rs index f20a77904..c36c996f0 100644 --- a/contracts/multisig/src/types.rs +++ b/contracts/multisig/src/types.rs @@ -50,9 +50,8 @@ impl TryFrom for MsgToSign { #[cfg(test)] mod tests { - use crate::test::common::ecdsa_test_data; - use super::*; + use crate::test::common::ecdsa_test_data; #[test] fn test_try_from_hexbinary_to_message() { diff --git a/contracts/multisig/src/verifier_set.rs b/contracts/multisig/src/verifier_set.rs index bde7c84af..1e3881ef6 100644 --- a/contracts/multisig/src/verifier_set.rs +++ b/contracts/multisig/src/verifier_set.rs @@ -1,15 +1,17 @@ use std::collections::{BTreeMap, HashMap}; -use crate::{key::PublicKey, msg::Signer}; use axelar_wasm_std::hash::Hash; use axelar_wasm_std::Participant; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, HexBinary, Uint128}; use sha3::{Digest, Keccak256}; +use crate::key::PublicKey; +use crate::msg::Signer; + #[cw_serde] pub struct VerifierSet { - // An ordered map with the signer's address as the key, and the signer as the value. + // An ordered map with the signer's axelar address as the key, and the signer as the value. pub signers: BTreeMap, pub threshold: Uint128, // for hash uniqueness. The same exact verifier set could be in use at two different times, @@ -47,6 +49,9 @@ impl VerifierSet { pub fn hash(&self) -> Hash { let mut hasher = Keccak256::new(); + // Length prefix the bytes to be hashed to prevent hash collisions + hasher.update(self.signers.len().to_be_bytes()); + self.signers.values().for_each(|signer| { hasher.update(signer.address.as_bytes()); hasher.update(signer.pub_key.as_ref()); @@ -63,7 +68,7 @@ impl VerifierSet { HexBinary::from(self.hash()).to_hex() } - pub fn get_pub_keys(&self) -> HashMap { + pub fn pub_keys(&self) -> HashMap { self.signers .iter() .map(|(address, signer)| (address.clone(), signer.pub_key.clone())) @@ -74,3 +79,27 @@ impl VerifierSet { self.signers.contains_key(signer.as_str()) } } + +#[cfg(test)] +mod tests { + use crate::key::KeyType; + use crate::test::common::{build_verifier_set, ecdsa_test_data}; + + // If this test fails, it means the verifier set hash has changed and therefore a migration is needed. + #[test] + fn verifier_set_hash_unchanged() { + let signers = ecdsa_test_data::signers(); + let verifier_set = build_verifier_set(KeyType::Ecdsa, &signers); + + goldie::assert_json!(hex::encode(verifier_set.hash())); + } + + // If this test fails, it means the verifier set hash has changed and therefore a migration is needed. + #[test] + fn verifier_set_id_unchanged() { + let signers = ecdsa_test_data::signers(); + let verifier_set = build_verifier_set(KeyType::Ecdsa, &signers); + + goldie::assert_json!(verifier_set.id()); + } +} diff --git a/contracts/router/.cargo/config b/contracts/nexus-gateway/.cargo/config.toml similarity index 100% rename from contracts/router/.cargo/config rename to contracts/nexus-gateway/.cargo/config.toml diff --git a/contracts/nexus-gateway/Cargo.toml b/contracts/nexus-gateway/Cargo.toml index 17f30bbc9..ae172897c 100644 --- a/contracts/nexus-gateway/Cargo.toml +++ b/contracts/nexus-gateway/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nexus-gateway" -version = "0.3.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,8 +13,7 @@ name = "nexus-gateway-schema" path = "src/bin/schema.rs" [dependencies] -axelar-wasm-std = { workspace = true } -axelar-wasm-std-derive = { workspace = true } +axelar-wasm-std = { workspace = true, features = ["derive"] } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } @@ -22,6 +21,7 @@ cw2 = { workspace = true } error-stack = { workspace = true } hex = "0.4.3" mockall = "0.11.4" +msgs-derive = { workspace = true } report = { workspace = true } router-api = { workspace = true } schemars = "0.8.15" diff --git a/contracts/nexus-gateway/src/bin/schema.rs b/contracts/nexus-gateway/src/bin/schema.rs index cb8b8c93c..a3a20bcbe 100644 --- a/contracts/nexus-gateway/src/bin/schema.rs +++ b/contracts/nexus-gateway/src/bin/schema.rs @@ -1,5 +1,4 @@ use cosmwasm_schema::write_api; - use nexus_gateway::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { diff --git a/contracts/nexus-gateway/src/contract.rs b/contracts/nexus-gateway/src/contract.rs index 71983bf06..622d2efa2 100644 --- a/contracts/nexus-gateway/src/contract.rs +++ b/contracts/nexus-gateway/src/contract.rs @@ -1,12 +1,13 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{DepsMut, Empty, Env, MessageInfo, Response}; -use error_stack::ResultExt; +use cosmwasm_std::{Addr, DepsMut, Empty, Env, MessageInfo, Response, Storage}; +use error_stack::{Report, ResultExt}; +use crate::contract::execute::{route_to_nexus, route_to_router}; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg}; -use crate::nexus; -use crate::state::{Config, GatewayStore, Store}; +use crate::state::Config; +use crate::{nexus, state}; mod execute; @@ -18,7 +19,7 @@ pub fn migrate( deps: DepsMut, _env: Env, _msg: Empty, -) -> Result { +) -> Result { // any version checks should be done before here cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; @@ -32,15 +33,13 @@ pub fn instantiate( _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> Result { +) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; let nexus = deps.api.addr_validate(&msg.nexus)?; let router = deps.api.addr_validate(&msg.router)?; - GatewayStore::new(deps.storage) - .save_config(Config { nexus, router }) - .expect("config must be saved"); + state::save_config(deps.storage, Config { nexus, router }).expect("config must be saved"); Ok(Response::default()) } @@ -51,47 +50,41 @@ pub fn execute( _env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result, axelar_wasm_std::ContractError> { - let contract = Contract::new(GatewayStore::new(deps.storage)); - - let res = match msg { - ExecuteMsg::RouteMessages(msgs) => contract - .route_to_nexus(info.sender, msgs) - .change_context(ContractError::RouteToNexus)?, - ExecuteMsg::RouteMessagesFromNexus(msgs) => contract - .route_to_router(info.sender, msgs) - .change_context(ContractError::RouteToRouter)?, +) -> Result, axelar_wasm_std::error::ContractError> { + let res = match msg.ensure_permissions(deps.storage, &info.sender, match_router, match_nexus)? { + ExecuteMsg::RouteMessages(msgs) => { + route_to_nexus(deps.storage, msgs).change_context(ContractError::RouteToNexus)? + } + ExecuteMsg::RouteMessagesFromNexus(msgs) => { + route_to_router(deps.storage, msgs).change_context(ContractError::RouteToRouter)? + } }; Ok(res) } -struct Contract -where - S: Store, -{ - store: S, - config: Config, +fn match_router(storage: &dyn Storage, _: &ExecuteMsg) -> Result> { + Ok(state::load_config(storage)?.router) } -impl Contract -where - S: Store, -{ - pub fn new(store: S) -> Self { - let config = store.load_config().expect("config must be loaded"); - - Self { store, config } - } +fn match_nexus(storage: &dyn Storage, _: &ExecuteMsg) -> Result> { + Ok(state::load_config(storage)?.nexus) } #[cfg(test)] mod tests { - - use cosmwasm_std::testing::{mock_dependencies, mock_env}; + use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; + use axelar_wasm_std::{err_contains, permission_control}; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{from_json, Addr, CosmosMsg, WasmMsg}; + use hex::decode; + use router_api::CrossChainId; use super::*; + const NEXUS: &str = "nexus"; + const ROUTER: &str = "router"; + #[test] fn migrate_sets_contract_version() { let mut deps = mock_dependencies(); @@ -102,4 +95,228 @@ mod tests { assert_eq!(contract_version.contract, "nexus-gateway"); assert_eq!(contract_version.version, CONTRACT_VERSION); } + + #[test] + fn route_to_router_unauthorized() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("unauthorized", &[]), + ExecuteMsg::RouteMessagesFromNexus(vec![]), + ); + + assert!(res.is_err_and(|err| err_contains!( + err.report, + permission_control::Error, + permission_control::Error::AddressNotWhitelisted { .. } + ))); + } + + #[test] + fn route_to_router_with_no_msg() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(NEXUS, &[]), + ExecuteMsg::RouteMessagesFromNexus(vec![]), + ); + + assert!(res.is_ok_and(|res| res.messages.is_empty())); + } + + #[test] + fn route_to_router_with_msgs() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(NEXUS, &[]), + ExecuteMsg::RouteMessagesFromNexus(nexus_messages()), + ); + + assert!(res.is_ok_and(|res| { + if res.messages.len() != 1 { + return false; + } + + match &res.messages[0].msg { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr, + msg, + funds, + }) => { + if let Ok(router_api::msg::ExecuteMsg::RouteMessages(msgs)) = from_json(msg) { + return *contract_addr == Addr::unchecked(ROUTER) + && msgs.len() == 2 + && funds.is_empty(); + } + + false + } + _ => false, + } + })); + } + + #[test] + fn route_to_nexus_unauthorized() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("unauthorized", &[]), + ExecuteMsg::RouteMessages(vec![]), + ); + + assert!(res.is_err_and(|err| err_contains!( + err.report, + permission_control::Error, + permission_control::Error::AddressNotWhitelisted { .. } + ))); + } + + #[test] + fn route_to_nexus_with_no_msg() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ROUTER, &[]), + ExecuteMsg::RouteMessages(vec![]), + ); + + assert!(res.is_ok_and(|res| res.messages.is_empty())); + } + + #[test] + fn route_to_nexus_with_msgs_only_route_once() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + let msgs = router_messages(); + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ROUTER, &[]), + ExecuteMsg::RouteMessages(msgs.clone()), + ); + + assert!(res.is_ok_and(|res| res.messages.len() == 2)); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ROUTER, &[]), + ExecuteMsg::RouteMessages(msgs.clone()), + ); + + assert!(res.is_ok_and(|res| res.messages.is_empty())); + } + + fn nexus_messages() -> Vec { + let msg_ids = [ + HexTxHashAndEventIndex { + tx_hash: vec![0x2f; 32].try_into().unwrap(), + event_index: 100, + }, + HexTxHashAndEventIndex { + tx_hash: vec![0x23; 32].try_into().unwrap(), + event_index: 1000, + }, + ]; + let msgs = vec![ + nexus::Message { + source_chain: "sourceChain".parse().unwrap(), + source_address: "0xb860".parse().unwrap(), + destination_address: "0xD419".parse().unwrap(), + destination_chain: "destinationChain".parse().unwrap(), + payload_hash: decode( + "bb9b5566c2f4876863333e481f4698350154259ffe6226e283b16ce18a64bcf1", + ) + .unwrap() + .try_into() + .unwrap(), + source_tx_id: msg_ids[0].tx_hash.to_vec().try_into().unwrap(), + source_tx_index: msg_ids[0].event_index as u64, + id: msg_ids[0].to_string(), + }, + nexus::Message { + source_chain: "sourceChain".parse().unwrap(), + source_address: "0xc860".parse().unwrap(), + destination_address: "0xA419".parse().unwrap(), + destination_chain: "destinationChain".parse().unwrap(), + payload_hash: decode( + "cb9b5566c2f4876853333e481f4698350154259ffe6226e283b16ce18a64bcf1", + ) + .unwrap() + .try_into() + .unwrap(), + source_tx_id: msg_ids[1].tx_hash.to_vec().try_into().unwrap(), + source_tx_index: msg_ids[1].event_index as u64, + id: msg_ids[1].to_string(), + }, + ]; + msgs + } + + fn router_messages() -> Vec { + let msgs = vec![ + router_api::Message { + cc_id: CrossChainId { + source_chain: "sourceChain".parse().unwrap(), + message_id: "0x2fe4:0".parse().unwrap(), + }, + source_address: "0xb860".parse().unwrap(), + destination_address: "0xD419".parse().unwrap(), + destination_chain: "destinationChain".parse().unwrap(), + payload_hash: decode( + "bb9b5566c2f4876863333e481f4698350154259ffe6226e283b16ce18a64bcf1", + ) + .unwrap() + .try_into() + .unwrap(), + }, + router_api::Message { + cc_id: CrossChainId { + source_chain: "sourceChain".parse().unwrap(), + message_id: "0x6b33:10".parse().unwrap(), + }, + source_address: "0x70725".parse().unwrap(), + destination_address: "0x7FAD".parse().unwrap(), + destination_chain: "destinationChain".parse().unwrap(), + payload_hash: decode( + "bb9b5566c2f4876863333e481f4698350154259ffe6226e283b16ce18a64bcf1", + ) + .unwrap() + .try_into() + .unwrap(), + }, + ]; + msgs + } + + fn instantiate_contract(deps: DepsMut) { + instantiate( + deps, + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + nexus: NEXUS.to_string(), + router: ROUTER.to_string(), + }, + ) + .unwrap(); + } } diff --git a/contracts/nexus-gateway/src/contract/execute.rs b/contracts/nexus-gateway/src/contract/execute.rs index 5b6129098..523b830f1 100644 --- a/contracts/nexus-gateway/src/contract/execute.rs +++ b/contracts/nexus-gateway/src/contract/execute.rs @@ -1,345 +1,47 @@ -use cosmwasm_std::{to_json_binary, Addr, Response, WasmMsg}; -use error_stack::report; +use cosmwasm_std::{to_json_binary, Response, Storage, WasmMsg}; use crate::error::ContractError; -use crate::nexus; -use crate::state::Store; - -use super::Contract; +use crate::{nexus, state}; type Result = error_stack::Result; -impl Contract -where - S: Store, -{ - pub fn route_to_router( - self, - sender: Addr, - msgs: Vec, - ) -> Result> { - if sender != self.config.nexus { - return Err(report!(ContractError::Unauthorized)); - } - - let msgs: Vec<_> = msgs - .into_iter() - .map(router_api::Message::try_from) - .collect::>>()?; - if msgs.is_empty() { - return Ok(Response::default()); - } - - Ok(Response::new().add_message(WasmMsg::Execute { - contract_addr: self.config.router.to_string(), - msg: to_json_binary(&router_api::msg::ExecuteMsg::RouteMessages(msgs)) - .expect("must serialize route-messages message"), - funds: vec![], - })) - } - - pub fn route_to_nexus( - mut self, - sender: Addr, - msgs: Vec, - ) -> Result> { - if sender != self.config.router { - return Err(report!(ContractError::Unauthorized)); - } - - let msgs = msgs - .into_iter() - .filter_map(|msg| match self.store.is_message_routed(&msg.cc_id) { - Ok(true) => None, - Ok(false) => Some(Ok(msg)), - Err(err) => Some(Err(err)), - }) - .collect::>>()?; - - msgs.iter() - .try_for_each(|msg| self.store.set_message_routed(&msg.cc_id))?; - - let msgs: Vec = msgs.into_iter().map(Into::into).collect(); - - Ok(Response::new().add_messages(msgs)) - } +pub fn route_to_router( + storage: &dyn Storage, + msgs: Vec, +) -> Result> { + let msgs: Vec<_> = msgs + .into_iter() + .map(router_api::Message::try_from) + .collect::>>()?; + if msgs.is_empty() { + return Ok(Response::default()); + } + + Ok(Response::new().add_message(WasmMsg::Execute { + contract_addr: state::load_config(storage)?.router.to_string(), + msg: to_json_binary(&router_api::msg::ExecuteMsg::RouteMessages(msgs)) + .expect("must serialize route-messages message"), + funds: vec![], + })) } -#[cfg(test)] -mod test { - use axelar_wasm_std::msg_id::tx_hash_event_index::HexTxHashAndEventIndex; - use cosmwasm_std::{from_json, CosmosMsg}; - use hex::decode; - - use router_api::CrossChainId; - - use crate::state::{Config, MockStore}; - - use super::*; - - #[test] - fn route_to_router_unauthorized() { - let mut store = MockStore::new(); - let config = Config { - nexus: Addr::unchecked("nexus"), - router: Addr::unchecked("router"), - }; - store - .expect_load_config() - .returning(move || Ok(config.clone())); - let contract = Contract::new(store); - - let res = contract.route_to_router(Addr::unchecked("unauthorized"), vec![]); - - assert!(res.is_err_and(|err| matches!(err.current_context(), ContractError::Unauthorized))); - } - - #[test] - fn route_to_router_with_no_msg() { - let mut store = MockStore::new(); - let config = Config { - nexus: Addr::unchecked("nexus"), - router: Addr::unchecked("router"), - }; - store - .expect_load_config() - .returning(move || Ok(config.clone())); - let contract = Contract::new(store); - - let res = contract.route_to_router(Addr::unchecked("nexus"), vec![]); - - assert!(res.is_ok_and(|res| res.messages.is_empty())); - } - - #[test] - fn route_to_router_with_msgs() { - let mut store = MockStore::new(); - let config = Config { - nexus: Addr::unchecked("nexus"), - router: Addr::unchecked("router"), - }; - store - .expect_load_config() - .returning(move || Ok(config.clone())); - let contract = Contract::new(store); - - let msg_ids = [ - HexTxHashAndEventIndex { - tx_hash: vec![0x2f; 32].try_into().unwrap(), - event_index: 100, - }, - HexTxHashAndEventIndex { - tx_hash: vec![0x23; 32].try_into().unwrap(), - event_index: 1000, - }, - ]; - let msgs = vec![ - nexus::Message { - source_chain: "sourceChain".parse().unwrap(), - source_address: "0xb860".parse().unwrap(), - destination_address: "0xD419".parse().unwrap(), - destination_chain: "destinationChain".parse().unwrap(), - payload_hash: decode( - "bb9b5566c2f4876863333e481f4698350154259ffe6226e283b16ce18a64bcf1", - ) - .unwrap() - .try_into() - .unwrap(), - source_tx_id: msg_ids[0].tx_hash.to_vec().try_into().unwrap(), - source_tx_index: msg_ids[0].event_index as u64, - id: msg_ids[0].to_string(), - }, - nexus::Message { - source_chain: "sourceChain".parse().unwrap(), - source_address: "0xc860".parse().unwrap(), - destination_address: "0xA419".parse().unwrap(), - destination_chain: "destinationChain".parse().unwrap(), - payload_hash: decode( - "cb9b5566c2f4876853333e481f4698350154259ffe6226e283b16ce18a64bcf1", - ) - .unwrap() - .try_into() - .unwrap(), - source_tx_id: msg_ids[1].tx_hash.to_vec().try_into().unwrap(), - source_tx_index: msg_ids[1].event_index as u64, - id: msg_ids[1].to_string(), - }, - ]; - let res = contract.route_to_router(Addr::unchecked("nexus"), msgs); - - assert!(res.is_ok_and(|res| { - if res.messages.len() != 1 { - return false; - } - - match &res.messages[0].msg { - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr, - msg, - funds, - }) => { - if let Ok(router_api::msg::ExecuteMsg::RouteMessages(msgs)) = from_json(msg) { - return *contract_addr == Addr::unchecked("router") - && msgs.len() == 2 - && funds.is_empty(); - } - - false - } - _ => false, - } - })); - } - - #[test] - fn route_to_nexus_unauthorized() { - let mut store = MockStore::new(); - let config = Config { - nexus: Addr::unchecked("nexus"), - router: Addr::unchecked("router"), - }; - store - .expect_load_config() - .returning(move || Ok(config.clone())); - let contract = Contract::new(store); - - let res = contract.route_to_nexus(Addr::unchecked("unauthorized"), vec![]); - - assert!(res.is_err_and(|err| matches!(err.current_context(), ContractError::Unauthorized))); - } - - #[test] - fn route_to_nexus_with_no_msg() { - let mut store = MockStore::new(); - let config = Config { - nexus: Addr::unchecked("nexus"), - router: Addr::unchecked("router"), - }; - store - .expect_load_config() - .returning(move || Ok(config.clone())); - let contract = Contract::new(store); - - let res = contract.route_to_nexus(Addr::unchecked("router"), vec![]); - - assert!(res.is_ok_and(|res| res.messages.is_empty())); - } - - #[test] - fn route_to_nexus_with_msgs_that_have_not_been_routed() { - let mut store = MockStore::new(); - let config = Config { - nexus: Addr::unchecked("nexus"), - router: Addr::unchecked("router"), - }; - store - .expect_load_config() - .returning(move || Ok(config.clone())); - store - .expect_is_message_routed() - .times(2) - .returning(|_| Ok(false)); - store - .expect_set_message_routed() - .times(2) - .returning(|_| Ok(())); - let contract = Contract::new(store); - - let msgs = vec![ - router_api::Message { - cc_id: CrossChainId { - chain: "sourceChain".parse().unwrap(), - id: "0x2fe4:0".parse().unwrap(), - }, - source_address: "0xb860".parse().unwrap(), - destination_address: "0xD419".parse().unwrap(), - destination_chain: "destinationChain".parse().unwrap(), - payload_hash: decode( - "bb9b5566c2f4876863333e481f4698350154259ffe6226e283b16ce18a64bcf1", - ) - .unwrap() - .try_into() - .unwrap(), - }, - router_api::Message { - cc_id: CrossChainId { - chain: "sourceChain".parse().unwrap(), - id: "0x6b33:10".parse().unwrap(), - }, - source_address: "0x0725".parse().unwrap(), - destination_address: "0x7FAD".parse().unwrap(), - destination_chain: "destinationChain".parse().unwrap(), - payload_hash: decode( - "bb9b5566c2f4876863333e481f4698350154259ffe6226e283b16ce18a64bcf1", - ) - .unwrap() - .try_into() - .unwrap(), - }, - ]; - let res = contract.route_to_nexus(Addr::unchecked("router"), msgs); - - assert!(res.is_ok_and(|res| res.messages.len() == 2)); - } - - #[test] - fn route_to_nexus_with_msgs_that_have_been_routed() { - let mut store = MockStore::new(); - let config = Config { - nexus: Addr::unchecked("nexus"), - router: Addr::unchecked("router"), - }; - store - .expect_load_config() - .returning(move || Ok(config.clone())); - store - .expect_is_message_routed() - .once() - .returning(|_| Ok(false)); - store - .expect_is_message_routed() - .once() - .returning(|_| Ok(true)); - store - .expect_set_message_routed() - .once() - .returning(|_| Ok(())); - let contract = Contract::new(store); - - let msgs = vec![ - router_api::Message { - cc_id: CrossChainId { - chain: "sourceChain".parse().unwrap(), - id: "0x2fe4:0".parse().unwrap(), - }, - source_address: "0xb860".parse().unwrap(), - destination_address: "0xD419".parse().unwrap(), - destination_chain: "destinationChain".parse().unwrap(), - payload_hash: decode( - "bb9b5566c2f4876863333e481f4698350154259ffe6226e283b16ce18a64bcf1", - ) - .unwrap() - .try_into() - .unwrap(), - }, - router_api::Message { - cc_id: CrossChainId { - chain: "sourceChain".parse().unwrap(), - id: "0x6b33:10".parse().unwrap(), - }, - source_address: "0x70725".parse().unwrap(), - destination_address: "0x7FAD".parse().unwrap(), - destination_chain: "destinationChain".parse().unwrap(), - payload_hash: decode( - "bb9b5566c2f4876863333e481f4698350154259ffe6226e283b16ce18a64bcf1", - ) - .unwrap() - .try_into() - .unwrap(), - }, - ]; - let res = contract.route_to_nexus(Addr::unchecked("router"), msgs); - - assert!(res.is_ok_and(|res| res.messages.len() == 1)); - } +pub fn route_to_nexus( + storage: &mut dyn Storage, + msgs: Vec, +) -> Result> { + let msgs = msgs + .into_iter() + .filter_map(|msg| match state::is_message_routed(storage, &msg.cc_id) { + Ok(true) => None, + Ok(false) => Some(Ok(msg)), + Err(err) => Some(Err(err)), + }) + .collect::>>()?; + + msgs.iter() + .try_for_each(|msg| state::set_message_routed(storage, &msg.cc_id))?; + + let msgs: Vec = msgs.into_iter().map(Into::into).collect(); + + Ok(Response::new().add_messages(msgs)) } diff --git a/contracts/nexus-gateway/src/error.rs b/contracts/nexus-gateway/src/error.rs index 385d2d5b4..44d26d157 100644 --- a/contracts/nexus-gateway/src/error.rs +++ b/contracts/nexus-gateway/src/error.rs @@ -1,13 +1,9 @@ +use axelar_wasm_std::IntoContractError; use cosmwasm_std::HexBinary; use thiserror::Error; -use axelar_wasm_std_derive::IntoContractError; - #[derive(Error, Debug, PartialEq, IntoContractError)] pub enum ContractError { - #[error("caller is not authorized")] - Unauthorized, - #[error("store failed saving/loading data")] StoreFailure, diff --git a/contracts/nexus-gateway/src/msg.rs b/contracts/nexus-gateway/src/msg.rs index 058fb4291..a95cc7f46 100644 --- a/contracts/nexus-gateway/src/msg.rs +++ b/contracts/nexus-gateway/src/msg.rs @@ -1,4 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; +use msgs_derive::EnsurePermissions; use crate::nexus; @@ -9,8 +10,11 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(EnsurePermissions)] pub enum ExecuteMsg { + #[permission(Specific(router))] RouteMessages(Vec), + #[permission(Specific(nexus))] RouteMessagesFromNexus(Vec), } diff --git a/contracts/nexus-gateway/src/nexus.rs b/contracts/nexus-gateway/src/nexus.rs index 26baad411..32f509f8e 100644 --- a/contracts/nexus-gateway/src/nexus.rs +++ b/contracts/nexus-gateway/src/nexus.rs @@ -1,9 +1,10 @@ use std::str::FromStr; -use axelar_wasm_std::{msg_id::tx_hash_event_index::HexTxHashAndEventIndex, nonempty}; +use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; +use axelar_wasm_std::nonempty; use cosmwasm_std::{CosmosMsg, CustomMsg}; use error_stack::{Report, Result, ResultExt}; -use router_api::{Address, ChainName, CrossChainId}; +use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -13,7 +14,7 @@ use crate::error::ContractError; // this matches the message type defined in the nexus module // https://github.com/axelarnetwork/axelar-core/blob/6c887df3797ba660093061662aff04e325b9c429/x/nexus/exported/types.pb.go#L405 pub struct Message { - pub source_chain: ChainName, + pub source_chain: ChainNameRaw, pub source_address: Address, pub destination_chain: ChainName, pub destination_address: Address, @@ -40,17 +41,17 @@ impl From for Message { fn from(msg: router_api::Message) -> Self { // fallback to using all 0's as the tx ID if it's not in the expected format let (source_tx_id, source_tx_index) = - parse_message_id(&msg.cc_id.id).unwrap_or((vec![0; 32].try_into().unwrap(), 0)); + parse_message_id(&msg.cc_id.message_id).unwrap_or((vec![0; 32].try_into().unwrap(), 0)); Self { - source_chain: msg.cc_id.chain.clone(), + source_chain: msg.cc_id.source_chain, source_address: msg.source_address, destination_chain: msg.destination_chain, destination_address: msg.destination_address, payload_hash: msg.payload_hash, source_tx_id, source_tx_index, - id: msg.cc_id.id.to_string(), + id: msg.cc_id.message_id.into(), } } } @@ -61,9 +62,9 @@ impl TryFrom for router_api::Message { fn try_from(msg: Message) -> Result { Ok(Self { cc_id: CrossChainId { - chain: msg.source_chain, - id: nonempty::String::try_from(msg.id.clone()) - .change_context(ContractError::InvalidMessageId(msg.id.to_string()))?, + source_chain: msg.source_chain, + message_id: nonempty::String::try_from(msg.id.as_str()) + .change_context(ContractError::InvalidMessageId(msg.id))?, }, source_address: msg.source_address, destination_chain: msg.destination_chain, @@ -83,10 +84,7 @@ impl From for CosmosMsg { mod test { use std::vec; - use axelar_wasm_std::msg_id::{ - base_58_event_index::Base58TxDigestAndEventIndex, - tx_hash_event_index::HexTxHashAndEventIndex, - }; + use axelar_wasm_std::msg_id::{Base58TxDigestAndEventIndex, HexTxHashAndEventIndex}; use router_api::CrossChainId; use super::Message; @@ -111,23 +109,24 @@ mod test { let router_msg = router_api::Message::try_from(msg.clone()); assert!(router_msg.is_ok()); let router_msg = router_msg.unwrap(); - assert_eq!(router_msg.cc_id.chain, msg.source_chain); - assert_eq!(router_msg.cc_id.id.to_string(), msg.id); + let router_msg_cc_id = router_msg.cc_id; + assert_eq!(router_msg_cc_id.source_chain, msg.source_chain); + assert_eq!(router_msg_cc_id.message_id.to_string(), msg.id); } #[test] fn should_convert_router_message_to_nexus_message() { let msg = router_api::Message { - cc_id: CrossChainId { - chain: "ethereum".parse().unwrap(), - id: HexTxHashAndEventIndex { + cc_id: CrossChainId::new( + "ethereum", + HexTxHashAndEventIndex { tx_hash: [2; 32], event_index: 1, } .to_string() - .try_into() - .unwrap(), - }, + .as_str(), + ) + .unwrap(), source_address: "something".parse().unwrap(), destination_chain: "polygon".parse().unwrap(), destination_address: "something else".parse().unwrap(), @@ -136,11 +135,16 @@ mod test { let nexus_msg = Message::from(msg.clone()); - assert_eq!(nexus_msg.id, msg.cc_id.id.to_string()); + let router_msg_cc_id = msg.cc_id; + + assert_eq!(nexus_msg.id, *router_msg_cc_id.message_id); assert_eq!(nexus_msg.destination_address, msg.destination_address); assert_eq!(nexus_msg.destination_chain, msg.destination_chain); assert_eq!(nexus_msg.source_address, msg.source_address); - assert_eq!(nexus_msg.source_chain, msg.cc_id.chain); + assert_eq!( + nexus_msg.source_chain, + router_msg_cc_id.source_chain.clone() + ); assert_eq!(nexus_msg.source_tx_id, vec![2; 32].try_into().unwrap()); assert_eq!(nexus_msg.source_tx_index, 1); } @@ -148,16 +152,16 @@ mod test { #[test] fn should_convert_router_message_with_non_hex_msg_id_to_nexus_message() { let msg = router_api::Message { - cc_id: CrossChainId { - chain: "ethereum".parse().unwrap(), - id: Base58TxDigestAndEventIndex { + cc_id: CrossChainId::new( + "ethereum", + Base58TxDigestAndEventIndex { tx_digest: [2; 32], event_index: 1, } .to_string() - .try_into() - .unwrap(), - }, + .as_str(), + ) + .unwrap(), source_address: "something".parse().unwrap(), destination_chain: "polygon".parse().unwrap(), destination_address: "something else".parse().unwrap(), @@ -166,13 +170,18 @@ mod test { let nexus_msg = Message::from(msg.clone()); - assert_eq!(nexus_msg.id, msg.cc_id.id.to_string()); + let router_msg_cc_id = msg.cc_id; + + assert_eq!(nexus_msg.id, *router_msg_cc_id.message_id); assert_eq!(nexus_msg.source_tx_id, vec![0; 32].try_into().unwrap()); assert_eq!(nexus_msg.source_tx_index, 0); assert_eq!(nexus_msg.destination_address, msg.destination_address); assert_eq!(nexus_msg.destination_chain, msg.destination_chain); assert_eq!(nexus_msg.source_address, msg.source_address); - assert_eq!(nexus_msg.source_chain, msg.cc_id.chain); + assert_eq!( + nexus_msg.source_chain, + router_msg_cc_id.source_chain.clone() + ); } } diff --git a/contracts/nexus-gateway/src/state.rs b/contracts/nexus-gateway/src/state.rs index 1936b770d..77d5daf63 100644 --- a/contracts/nexus-gateway/src/state.rs +++ b/contracts/nexus-gateway/src/state.rs @@ -2,59 +2,38 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Storage}; use cw_storage_plus::{Item, Map}; use error_stack::{self, ResultExt}; -use mockall::automock; use router_api::CrossChainId; use crate::error::ContractError; const CONFIG: Item = Item::new("config"); -const ROUTED_MESSAGE_IDS: Map = Map::new("routed_message_ids"); +const ROUTED_MESSAGE_IDS: Map<&CrossChainId, ()> = Map::new("routed_message_ids"); type Result = error_stack::Result; -#[automock] -pub trait Store { - fn save_config(&mut self, config: Config) -> Result<()>; - fn load_config(&self) -> Result; - fn set_message_routed(&mut self, id: &CrossChainId) -> Result<()>; - fn is_message_routed(&self, id: &CrossChainId) -> Result; +pub(crate) fn save_config(storage: &mut dyn Storage, config: Config) -> Result<()> { + CONFIG + .save(storage, &config) + .change_context(ContractError::StoreFailure) } -pub struct GatewayStore<'a> { - storage: &'a mut dyn Storage, +pub(crate) fn load_config(storage: &dyn Storage) -> Result { + CONFIG + .load(storage) + .change_context(ContractError::StoreFailure) } -impl<'a> GatewayStore<'a> { - pub fn new(storage: &'a mut dyn Storage) -> Self { - Self { storage } - } +pub(crate) fn set_message_routed(storage: &mut dyn Storage, id: &CrossChainId) -> Result<()> { + ROUTED_MESSAGE_IDS + .save(storage, id, &()) + .change_context(ContractError::StoreFailure) } -impl Store for GatewayStore<'_> { - fn save_config(&mut self, config: Config) -> Result<()> { - CONFIG - .save(self.storage, &config) - .change_context(ContractError::StoreFailure) - } - - fn load_config(&self) -> Result { - CONFIG - .load(self.storage) - .change_context(ContractError::StoreFailure) - } - - fn set_message_routed(&mut self, id: &CrossChainId) -> Result<()> { - ROUTED_MESSAGE_IDS - .save(self.storage, id.clone(), &()) - .change_context(ContractError::StoreFailure) - } - - fn is_message_routed(&self, id: &CrossChainId) -> Result { - ROUTED_MESSAGE_IDS - .may_load(self.storage, id.clone()) - .map(|result| result.is_some()) - .change_context(ContractError::StoreFailure) - } +pub(crate) fn is_message_routed(storage: &dyn Storage, id: &CrossChainId) -> Result { + ROUTED_MESSAGE_IDS + .may_load(storage, id) + .map(|result| result.is_some()) + .change_context(ContractError::StoreFailure) } #[cw_serde] diff --git a/contracts/service-registry/.cargo/config b/contracts/rewards/.cargo/config.toml similarity index 100% rename from contracts/service-registry/.cargo/config rename to contracts/rewards/.cargo/config.toml diff --git a/contracts/rewards/Cargo.toml b/contracts/rewards/Cargo.toml index 8d955565d..1db688d5e 100644 --- a/contracts/rewards/Cargo.toml +++ b/contracts/rewards/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rewards" -version = "0.4.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" description = "Validator rewards contract" @@ -33,14 +33,14 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] -axelar-wasm-std = { workspace = true } -axelar-wasm-std-derive = { workspace = true } +axelar-wasm-std = { workspace = true, features = ["derive"] } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } error-stack = { workspace = true } itertools = "0.11.0" +msgs-derive = { workspace = true } report = { workspace = true } router-api = { workspace = true } thiserror = { workspace = true } diff --git a/contracts/rewards/src/bin/schema.rs b/contracts/rewards/src/bin/schema.rs index e0a5ec117..c7b7ba02e 100644 --- a/contracts/rewards/src/bin/schema.rs +++ b/contracts/rewards/src/bin/schema.rs @@ -1,5 +1,4 @@ use cosmwasm_schema::write_api; - use rewards::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { diff --git a/contracts/rewards/src/contract.rs b/contracts/rewards/src/contract.rs index 41f6f24b0..142cf5d1a 100644 --- a/contracts/rewards/src/contract.rs +++ b/contracts/rewards/src/contract.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std::nonempty; +use axelar_wasm_std::{nonempty, permission_control}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -7,13 +7,13 @@ use cosmwasm_std::{ use error_stack::ResultExt; use itertools::Itertools; -use crate::{ - error::ContractError, - msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, - state::{self, Config, Epoch, ParamsSnapshot, PoolId, CONFIG, PARAMS}, -}; +use crate::contract::migrations::v0_4_0; +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::state::{self, Config, Epoch, ParamsSnapshot, PoolId, CONFIG, PARAMS}; mod execute; +mod migrations; mod query; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); @@ -24,7 +24,9 @@ pub fn migrate( deps: DepsMut, _env: Env, _msg: Empty, -) -> Result { +) -> Result { + v0_4_0::migrate(deps.storage)?; + // any version checks should be done before here cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; @@ -38,15 +40,15 @@ pub fn instantiate( env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> Result { +) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; let governance = deps.api.addr_validate(&msg.governance_address)?; + permission_control::set_governance(deps.storage, &governance)?; CONFIG.save( deps.storage, &Config { - governance, rewards_denom: msg.rewards_denom, }, )?; @@ -71,8 +73,8 @@ pub fn execute( env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result { - match msg { +) -> Result { + match msg.ensure_permissions(deps.storage, &info.sender)? { ExecuteMsg::RecordParticipation { chain_name, event_id, @@ -81,7 +83,7 @@ pub fn execute( let verifier_address = deps.api.addr_validate(&verifier_address)?; let pool_id = PoolId { chain_name, - contract: info.sender.clone(), + contract: info.sender, }; execute::record_participation( deps.storage, @@ -89,8 +91,7 @@ pub fn execute( verifier_address, pool_id, env.block.height, - ) - .map_err(axelar_wasm_std::ContractError::from)?; + )?; Ok(Response::new()) } @@ -120,8 +121,7 @@ pub fn execute( deps.api.addr_validate(pool_id.contract.as_str())?; let rewards = - execute::distribute_rewards(deps.storage, pool_id, env.block.height, epoch_count) - .map_err(axelar_wasm_std::ContractError::from)?; + execute::distribute_rewards(deps.storage, pool_id, env.block.height, epoch_count)?; let msgs = rewards .into_iter() @@ -137,7 +137,7 @@ pub fn execute( Ok(Response::new().add_messages(msgs)) } ExecuteMsg::UpdateParams { params } => { - execute::update_params(deps.storage, params, env.block.height, info.sender)?; + execute::update_params(deps.storage, params, env.block.height)?; Ok(Response::new()) } @@ -149,19 +149,19 @@ pub fn query( deps: Deps, env: Env, msg: QueryMsg, -) -> Result { +) -> Result { match msg { QueryMsg::RewardsPool { pool_id } => { let pool = query::rewards_pool(deps.storage, pool_id, env.block.height)?; to_json_binary(&pool) .change_context(ContractError::SerializeResponse) - .map_err(axelar_wasm_std::ContractError::from) + .map_err(axelar_wasm_std::error::ContractError::from) } QueryMsg::VerifierParticipation { pool_id, epoch_num } => { let tally = query::participation(deps.storage, pool_id, epoch_num, env.block.height)?; to_json_binary(&tally) .change_context(ContractError::SerializeResponse) - .map_err(axelar_wasm_std::ContractError::from) + .map_err(axelar_wasm_std::error::ContractError::from) } } } @@ -173,19 +173,19 @@ mod tests { use cw_multi_test::{App, ContractWrapper, Executor}; use router_api::ChainName; + use super::*; use crate::msg::{ExecuteMsg, InstantiateMsg, Params, QueryMsg, RewardsPool}; use crate::state::PoolId; - use super::*; - #[test] fn migrate_sets_contract_version() { let mut deps = mock_dependencies(); + v0_4_0::tests::instantiate_contract(deps.as_mut(), "denom"); migrate(deps.as_mut(), mock_env(), Empty {}).unwrap(); let contract_version = cw2::get_contract_version(deps.as_mut().storage).unwrap(); - assert_eq!(contract_version.contract, "rewards"); + assert_eq!(contract_version.contract, CONTRACT_NAME); assert_eq!(contract_version.version, CONTRACT_VERSION); } @@ -266,7 +266,7 @@ mod tests { contract_address.clone(), &ExecuteMsg::RecordParticipation { chain_name: chain_name.clone(), - event_id: "some event".to_string().try_into().unwrap(), + event_id: "some event".try_into().unwrap(), verifier_address: verifier.to_string(), }, &[], @@ -278,7 +278,7 @@ mod tests { contract_address.clone(), &ExecuteMsg::RecordParticipation { chain_name: chain_name.clone(), - event_id: "some other event".to_string().try_into().unwrap(), + event_id: "some other event".try_into().unwrap(), verifier_address: verifier.to_string(), }, &[], diff --git a/contracts/rewards/src/contract/execute.rs b/contracts/rewards/src/contract/execute.rs index 81ec427e3..08eef8eed 100644 --- a/contracts/rewards/src/contract/execute.rs +++ b/contracts/rewards/src/contract/execute.rs @@ -4,22 +4,13 @@ use axelar_wasm_std::{nonempty, FnExt}; use cosmwasm_std::{Addr, OverflowError, OverflowOperation, Storage, Uint128}; use error_stack::{Report, Result}; -use crate::{ - error::ContractError, - msg::Params, - state::{self, Config, Epoch, EpochTally, Event, ParamsSnapshot, PoolId, StorageState}, -}; +use crate::error::ContractError; +use crate::msg::Params; +use crate::state::{self, Epoch, EpochTally, Event, ParamsSnapshot, PoolId, StorageState}; const DEFAULT_EPOCHS_TO_PROCESS: u64 = 10; const EPOCH_PAYOUT_DELAY: u64 = 2; -fn require_governance(config: Config, sender: Addr) -> Result<(), ContractError> { - if config.governance != sender { - return Err(ContractError::Unauthorized.into()); - } - Ok(()) -} - pub(crate) fn record_participation( storage: &mut dyn Storage, event_id: nonempty::String, @@ -74,7 +65,7 @@ pub(crate) fn distribute_rewards( .map_or(0, |last_processed| last_processed.saturating_add(1)); let to = std::cmp::min( - (from.saturating_add(epoch_process_limit)).saturating_sub(1), // for process limit =1 "from" and "to" must be equal + from.saturating_add(epoch_process_limit).saturating_sub(1), // for process limit =1 "from" and "to" must be equal cur_epoch.epoch_num.saturating_sub(EPOCH_PAYOUT_DELAY), ); @@ -127,9 +118,7 @@ pub(crate) fn update_params( storage: &mut dyn Storage, new_params: Params, block_height: u64, - sender: Addr, ) -> Result<(), ContractError> { - require_governance(state::load_config(storage), sender)?; let cur_epoch = Epoch::current(&state::load_params(storage), block_height)?; // If the param update reduces the epoch duration such that the current epoch immediately ends, // start a new epoch at this block, incrementing the current epoch number by 1. @@ -214,24 +203,17 @@ fn merge_rewards( #[cfg(test)] mod test { - use super::*; - use std::collections::HashMap; use axelar_wasm_std::nonempty; - use cosmwasm_std::{ - testing::{mock_dependencies, MockApi, MockQuerier, MockStorage}, - Addr, OwnedDeps, Uint128, Uint64, - }; + use cosmwasm_std::testing::{mock_dependencies, MockApi, MockQuerier, MockStorage}; + use cosmwasm_std::{Addr, OwnedDeps, Uint128, Uint64}; use router_api::ChainName; - use crate::{ - error::ContractError, - msg::Params, - state::{ - self, Config, Epoch, EpochTally, Event, ParamsSnapshot, PoolId, RewardsPool, CONFIG, - }, - }; + use super::*; + use crate::error::ContractError; + use crate::msg::Params; + use crate::state::{self, Config, Epoch, ParamsSnapshot, PoolId, CONFIG}; /// Tests that the current epoch is computed correctly when the expected epoch is the same as the stored epoch #[test] @@ -239,7 +221,7 @@ mod test { let cur_epoch_num = 1u64; let block_height_started = 250u64; let epoch_duration = 100u64; - let (mock_deps, _) = setup(cur_epoch_num, block_height_started, epoch_duration); + let mock_deps = setup(cur_epoch_num, block_height_started, epoch_duration); let current_params = state::load_params(mock_deps.as_ref().storage); let new_epoch = Epoch::current(¤t_params, block_height_started).unwrap(); @@ -263,7 +245,7 @@ mod test { let cur_epoch_num = 1u64; let block_height_started = 250u64; let epoch_duration = 100u64; - let (mock_deps, _) = setup(cur_epoch_num, block_height_started, epoch_duration); + let mock_deps = setup(cur_epoch_num, block_height_started, epoch_duration); let current_params = state::load_params(mock_deps.as_ref().storage); assert!(Epoch::current(¤t_params, block_height_started - 1).is_err()); @@ -276,7 +258,7 @@ mod test { let cur_epoch_num = 1u64; let block_height_started = 250u64; let epoch_duration = 100u64; - let (mock_deps, _) = setup(cur_epoch_num, block_height_started, epoch_duration); + let mock_deps = setup(cur_epoch_num, block_height_started, epoch_duration); // elements are (height, expected epoch number, expected epoch start) let test_cases = vec![ @@ -318,7 +300,7 @@ mod test { let epoch_block_start = 250u64; let epoch_duration = 100u64; - let (mut mock_deps, _) = setup(cur_epoch_num, epoch_block_start, epoch_duration); + let mut mock_deps = setup(cur_epoch_num, epoch_block_start, epoch_duration); let pool_id = PoolId { chain_name: "mock-chain".parse().unwrap(), @@ -372,7 +354,7 @@ mod test { let block_height_started = 250u64; let epoch_duration = 100u64; - let (mut mock_deps, _) = setup(starting_epoch_num, block_height_started, epoch_duration); + let mut mock_deps = setup(starting_epoch_num, block_height_started, epoch_duration); let pool_id = PoolId { chain_name: "mock-chain".parse().unwrap(), @@ -390,7 +372,7 @@ mod test { for (i, verifiers) in verifiers.iter().enumerate() { record_participation( mock_deps.as_mut().storage, - "some event".to_string().try_into().unwrap(), + "some event".try_into().unwrap(), verifiers.clone(), pool_id.clone(), height_at_epoch_end + i as u64, @@ -434,7 +416,7 @@ mod test { let block_height_started = 250u64; let epoch_duration = 100u64; - let (mut mock_deps, _) = setup(cur_epoch_num, block_height_started, epoch_duration); + let mut mock_deps = setup(cur_epoch_num, block_height_started, epoch_duration); let mut simulated_participation = HashMap::new(); simulated_participation.insert( @@ -509,7 +491,7 @@ mod test { let initial_rewards_per_epoch = 100u128; let initial_participation_threshold = (1, 2); let epoch_duration = 100u64; - let (mut mock_deps, config) = setup_with_params( + let mut mock_deps = setup_with_params( initial_epoch_num, initial_epoch_start, epoch_duration, @@ -532,13 +514,7 @@ mod test { let expected_epoch = Epoch::current(&state::load_params(mock_deps.as_ref().storage), cur_height).unwrap(); - update_params( - mock_deps.as_mut().storage, - new_params.clone(), - cur_height, - config.governance.clone(), - ) - .unwrap(); + update_params(mock_deps.as_mut().storage, new_params.clone(), cur_height).unwrap(); let stored = state::load_params(mock_deps.as_ref().storage); assert_eq!(stored.params, new_params); @@ -555,40 +531,13 @@ mod test { assert_eq!(stored.created_at, cur_epoch); } - /// Test that rewards parameters cannot be updated by an address other than governance - #[test] - fn update_params_unauthorized() { - let initial_epoch_num = 1u64; - let initial_epoch_start = 250u64; - let epoch_duration = 100u64; - let (mut mock_deps, _) = setup(initial_epoch_num, initial_epoch_start, epoch_duration); - - let new_params = Params { - rewards_per_epoch: cosmwasm_std::Uint128::from(100u128).try_into().unwrap(), - participation_threshold: (Uint64::new(2), Uint64::new(3)).try_into().unwrap(), - epoch_duration: epoch_duration.try_into().unwrap(), - }; - - let res = update_params( - mock_deps.as_mut().storage, - new_params.clone(), - initial_epoch_start, - Addr::unchecked("some non governance address"), - ); - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().current_context(), - &ContractError::Unauthorized - ); - } - /// Test extending the epoch duration. This should not change the current epoch #[test] fn extend_epoch_duration() { let initial_epoch_num = 1u64; let initial_epoch_start = 250u64; let initial_epoch_duration = 100u64; - let (mut mock_deps, config) = setup( + let mut mock_deps = setup( initial_epoch_num, initial_epoch_start, initial_epoch_duration, @@ -604,17 +553,11 @@ mod test { let new_epoch_duration = initial_epoch_duration * 2; let new_params = Params { - epoch_duration: (new_epoch_duration).try_into().unwrap(), + epoch_duration: new_epoch_duration.try_into().unwrap(), ..initial_params_snapshot.params // keep everything besides epoch duration the same }; - update_params( - mock_deps.as_mut().storage, - new_params.clone(), - cur_height, - config.governance.clone(), - ) - .unwrap(); + update_params(mock_deps.as_mut().storage, new_params.clone(), cur_height).unwrap(); let updated_params_snapshot = state::load_params(mock_deps.as_ref().storage); @@ -647,7 +590,7 @@ mod test { let initial_epoch_num = 1u64; let initial_epoch_start = 256u64; let initial_epoch_duration = 100u64; - let (mut mock_deps, config) = setup( + let mut mock_deps = setup( initial_epoch_num, initial_epoch_start, initial_epoch_duration, @@ -668,13 +611,7 @@ mod test { epoch_duration: new_epoch_duration.try_into().unwrap(), ..initial_params_snapshot.params }; - update_params( - mock_deps.as_mut().storage, - new_params.clone(), - cur_height, - config.governance.clone(), - ) - .unwrap(); + update_params(mock_deps.as_mut().storage, new_params.clone(), cur_height).unwrap(); let updated_params_snapshot = state::load_params(mock_deps.as_ref().storage); @@ -699,7 +636,7 @@ mod test { let initial_epoch_num = 1u64; let initial_epoch_start = 250u64; let initial_epoch_duration = 100u64; - let (mut mock_deps, config) = setup( + let mut mock_deps = setup( initial_epoch_num, initial_epoch_start, initial_epoch_duration, @@ -720,13 +657,7 @@ mod test { epoch_duration: 10.try_into().unwrap(), ..initial_params_snapshot.params }; - update_params( - mock_deps.as_mut().storage, - new_params.clone(), - cur_height, - config.governance.clone(), - ) - .unwrap(); + update_params(mock_deps.as_mut().storage, new_params.clone(), cur_height).unwrap(); let updated_params_snapshot = state::load_params(mock_deps.as_ref().storage); @@ -749,7 +680,7 @@ mod test { let block_height_started = 250u64; let epoch_duration = 100u64; - let (mut mock_deps, _) = setup(cur_epoch_num, block_height_started, epoch_duration); + let mut mock_deps = setup(cur_epoch_num, block_height_started, epoch_duration); let pool_id = PoolId { chain_name: "mock-chain".parse().unwrap(), @@ -790,7 +721,7 @@ mod test { let block_height_started = 250u64; let epoch_duration = 100u64; - let (mut mock_deps, _) = setup(cur_epoch_num, block_height_started, epoch_duration); + let mut mock_deps = setup(cur_epoch_num, block_height_started, epoch_duration); // a vector of (contract, rewards amounts) pairs let test_data = vec![ (Addr::unchecked("contract_1"), vec![100, 200, 50]), @@ -840,7 +771,7 @@ mod test { let rewards_per_epoch = 100u128; let participation_threshold = (2, 3); - let (mut mock_deps, _) = setup_with_params( + let mut mock_deps = setup_with_params( cur_epoch_num, block_height_started, epoch_duration, @@ -940,7 +871,7 @@ mod test { let rewards_per_epoch = 100u128; let participation_threshold = (1, 2); - let (mut mock_deps, _) = setup_with_params( + let mut mock_deps = setup_with_params( cur_epoch_num, block_height_started, epoch_duration, @@ -1019,7 +950,7 @@ mod test { let rewards_per_epoch = 100u128; let participation_threshold = (8, 10); - let (mut mock_deps, _) = setup_with_params( + let mut mock_deps = setup_with_params( cur_epoch_num, block_height_started, epoch_duration, @@ -1098,7 +1029,7 @@ mod test { let rewards_per_epoch = 100u128; let participation_threshold = (8, 10); - let (mut mock_deps, _) = setup_with_params( + let mut mock_deps = setup_with_params( cur_epoch_num, block_height_started, epoch_duration, @@ -1165,7 +1096,7 @@ mod test { let rewards_per_epoch = 100u128; let participation_threshold = (8, 10); - let (mut mock_deps, _) = setup_with_params( + let mut mock_deps = setup_with_params( cur_epoch_num, block_height_started, epoch_duration, @@ -1215,69 +1146,13 @@ mod test { type MockDeps = OwnedDeps; - fn set_initial_storage( - params_store: ParamsSnapshot, - events_store: Vec, - tally_store: Vec, - rewards_store: Vec, - watermark_store: HashMap, - ) -> (MockDeps, Config) { - let mut deps = mock_dependencies(); - let storage = deps.as_mut().storage; - - state::save_params(storage, ¶ms_store).unwrap(); - - events_store.iter().for_each(|event| { - state::save_event(storage, event).unwrap(); - }); - - tally_store.iter().for_each(|tally| { - state::save_epoch_tally(storage, tally).unwrap(); - }); - - rewards_store.iter().for_each(|pool| { - state::save_rewards_pool(storage, pool).unwrap(); - }); - - watermark_store - .into_iter() - .for_each(|(pool_id, epoch_num)| { - state::save_rewards_watermark(storage, pool_id, epoch_num).unwrap(); - }); - - let config = Config { - governance: Addr::unchecked("governance"), - rewards_denom: "AXL".to_string(), - }; - - CONFIG.save(storage, &config).unwrap(); - - (deps, config) - } - - fn setup_with_stores( - params_store: ParamsSnapshot, - events_store: Vec, - tally_store: Vec, - rewards_store: Vec, - watermark_store: HashMap, - ) -> (MockDeps, Config) { - set_initial_storage( - params_store, - events_store, - tally_store, - rewards_store, - watermark_store, - ) - } - fn setup_with_params( cur_epoch_num: u64, block_height_started: u64, epoch_duration: u64, rewards_per_epoch: u128, participation_threshold: (u64, u64), - ) -> (MockDeps, Config) { + ) -> MockDeps { let rewards_per_epoch: nonempty::Uint128 = cosmwasm_std::Uint128::from(rewards_per_epoch) .try_into() .unwrap(); @@ -1295,24 +1170,21 @@ mod test { created_at: current_epoch.clone(), }; - let rewards_store = Vec::new(); - let events_store = Vec::new(); - let tally_store = Vec::new(); - let watermark_store = HashMap::new(); - setup_with_stores( - params_snapshot, - events_store, - tally_store, - rewards_store, - watermark_store, - ) + let mut deps = mock_dependencies(); + let storage = deps.as_mut().storage; + + state::save_params(storage, ¶ms_snapshot).unwrap(); + + let config = Config { + rewards_denom: "AXL".to_string(), + }; + + CONFIG.save(storage, &config).unwrap(); + + deps } - fn setup( - cur_epoch_num: u64, - block_height_started: u64, - epoch_duration: u64, - ) -> (MockDeps, Config) { + fn setup(cur_epoch_num: u64, block_height_started: u64, epoch_duration: u64) -> MockDeps { let participation_threshold = (1, 2); let rewards_per_epoch = 100u128; setup_with_params( diff --git a/contracts/rewards/src/contract/migrations/mod.rs b/contracts/rewards/src/contract/migrations/mod.rs new file mode 100644 index 000000000..a73cc4eb4 --- /dev/null +++ b/contracts/rewards/src/contract/migrations/mod.rs @@ -0,0 +1 @@ +pub mod v0_4_0; diff --git a/contracts/rewards/src/contract/migrations/v0_4_0.rs b/contracts/rewards/src/contract/migrations/v0_4_0.rs new file mode 100644 index 000000000..115da34ae --- /dev/null +++ b/contracts/rewards/src/contract/migrations/v0_4_0.rs @@ -0,0 +1,153 @@ +#![allow(deprecated)] + +use axelar_wasm_std::error::ContractError; +use axelar_wasm_std::permission_control; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Storage}; +use cw_storage_plus::Item; +use router_api::error::Error; + +use crate::contract::CONTRACT_NAME; +use crate::state; + +const BASE_VERSION: &str = "0.4.0"; + +pub fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { + cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; + + set_generalized_permission_control(storage)?; + Ok(()) +} + +fn set_generalized_permission_control(storage: &mut dyn Storage) -> Result<(), Error> { + let old_config = CONFIG.load(storage)?; + + permission_control::set_governance(storage, &old_config.governance).map_err(Error::from)?; + + let new_config = &state::Config { + rewards_denom: old_config.rewards_denom, + }; + state::CONFIG.save(storage, new_config)?; + Ok(()) +} + +#[cw_serde] +#[deprecated(since = "0.4.0", note = "only used during migration")] +pub struct Config { + pub governance: Addr, + pub rewards_denom: String, +} + +#[deprecated(since = "0.4.0", note = "only used during migration")] +pub const CONFIG: Item = Item::new("config"); + +#[cfg(test)] +pub mod tests { + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; + + use crate::contract::migrations::v0_4_0; + use crate::contract::{execute, CONTRACT_NAME}; + use crate::msg::{ExecuteMsg, InstantiateMsg, Params}; + use crate::state; + use crate::state::{Epoch, ParamsSnapshot, PARAMS}; + + #[deprecated(since = "0.4.0", note = "only used during migration tests")] + fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, + ) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, v0_4_0::BASE_VERSION)?; + + let governance = deps.api.addr_validate(&msg.governance_address)?; + + v0_4_0::CONFIG.save( + deps.storage, + &v0_4_0::Config { + governance, + rewards_denom: msg.rewards_denom, + }, + )?; + + PARAMS.save( + deps.storage, + &ParamsSnapshot { + params: msg.params, + created_at: Epoch { + epoch_num: 0, + block_height_started: env.block.height, + }, + }, + )?; + + Ok(Response::new()) + } + + #[test] + fn migrate_checks_contract_version() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut(), "denom"); + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap(); + + assert!(v0_4_0::migrate(deps.as_mut().storage).is_err()); + + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, v0_4_0::BASE_VERSION) + .unwrap(); + + assert!(v0_4_0::migrate(deps.as_mut().storage).is_ok()); + } + + #[test] + fn migrate_config() { + let mut deps = mock_dependencies(); + let denom = "denom".to_string(); + instantiate_contract(deps.as_mut(), &denom); + + v0_4_0::migrate(&mut deps.storage).unwrap(); + + let new_config = state::CONFIG.load(&deps.storage).unwrap(); + assert_eq!(denom, new_config.rewards_denom); + } + + #[test] + fn migrate_governance_permission() { + let mut deps = mock_dependencies(); + + instantiate_contract(deps.as_mut(), "denom"); + + v0_4_0::migrate(&mut deps.storage).unwrap(); + + let msg = ExecuteMsg::UpdateParams { + params: Params { + epoch_duration: 100u64.try_into().unwrap(), + rewards_per_epoch: 1000u128.try_into().unwrap(), + participation_threshold: (1, 2).try_into().unwrap(), + }, + }; + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info("anyone", &[]), + msg.clone(), + ) + .is_err()); + + assert!(execute(deps.as_mut(), mock_env(), mock_info("governance", &[]), msg).is_ok()); + } + + #[deprecated(since = "0.4.0", note = "only used during migration tests")] + pub fn instantiate_contract(deps: DepsMut, denom: impl Into) { + let msg = InstantiateMsg { + governance_address: "governance".to_string(), + rewards_denom: denom.into(), + params: Params { + epoch_duration: 100u64.try_into().unwrap(), + rewards_per_epoch: 1000u128.try_into().unwrap(), + participation_threshold: (1, 2).try_into().unwrap(), + }, + }; + instantiate(deps, mock_env(), mock_info("anyone", &[]), msg).unwrap(); + } +} diff --git a/contracts/rewards/src/contract/query.rs b/contracts/rewards/src/contract/query.rs index 0bfbdafcc..9da88e320 100644 --- a/contracts/rewards/src/contract/query.rs +++ b/contracts/rewards/src/contract/query.rs @@ -1,11 +1,9 @@ use cosmwasm_std::{Storage, Uint64}; use error_stack::Result; -use crate::{ - error::ContractError, - msg, - state::{self, Epoch, PoolId}, -}; +use crate::error::ContractError; +use crate::msg; +use crate::state::{self, Epoch, PoolId}; pub fn rewards_pool( storage: &dyn Storage, @@ -65,15 +63,13 @@ pub fn participation( #[cfg(test)] mod tests { - use cosmwasm_std::{testing::mock_dependencies, Addr, Uint128, Uint64}; + use cosmwasm_std::testing::mock_dependencies; + use cosmwasm_std::{Addr, Uint128, Uint64}; use msg::Participation; - use crate::{ - msg::Params, - state::{EpochTally, ParamsSnapshot, RewardsPool}, - }; - use super::*; + use crate::msg::Params; + use crate::state::{EpochTally, ParamsSnapshot, RewardsPool}; fn setup(storage: &mut dyn Storage, initial_balance: Uint128) -> (ParamsSnapshot, PoolId) { let pool_id = PoolId { diff --git a/contracts/rewards/src/error.rs b/contracts/rewards/src/error.rs index 15a51ef38..169222898 100644 --- a/contracts/rewards/src/error.rs +++ b/contracts/rewards/src/error.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std_derive::IntoContractError; +use axelar_wasm_std::IntoContractError; use cosmwasm_std::OverflowError; use thiserror::Error; diff --git a/contracts/rewards/src/msg.rs b/contracts/rewards/src/msg.rs index cf2c596ac..64d46a317 100644 --- a/contracts/rewards/src/msg.rs +++ b/contracts/rewards/src/msg.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use axelar_wasm_std::{nonempty, Threshold}; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Uint128, Uint64}; +use msgs_derive::EnsurePermissions; use router_api::ChainName; use crate::state::{Epoch, PoolId}; @@ -31,6 +32,7 @@ pub struct Params { } #[cw_serde] +#[derive(EnsurePermissions)] pub enum ExecuteMsg { /// Log a specific verifier as participating in a specific event. Verifier weights are ignored /// @@ -40,6 +42,7 @@ pub enum ExecuteMsg { /// verifier could choose to record the participation, but then the missed message is not recorded in any way. /// A possible solution to this is to add a weight to each event, where the voting verifier specifies the number /// of messages in a batch as well as the number of messages a particular verifier actually participated in. + #[permission(Any)] RecordParticipation { chain_name: ChainName, event_id: nonempty::String, @@ -47,6 +50,7 @@ pub enum ExecuteMsg { }, /// Distribute rewards up to epoch T - 2 (i.e. if we are currently in epoch 10, distribute all undistributed rewards for epochs 0-8) and send the required number of tokens to each verifier + #[permission(Any)] DistributeRewards { pool_id: PoolId, /// Maximum number of historical epochs for which to distribute rewards, starting with the oldest. If not specified, distribute rewards for 10 epochs. @@ -55,9 +59,11 @@ pub enum ExecuteMsg { /// Start a new reward pool for the given contract if none exists. Otherwise, add tokens to an existing reward pool. /// Any attached funds with a denom matching the rewards denom are added to the pool. + #[permission(Any)] AddRewards { pool_id: PoolId }, /// Overwrites the currently stored params. Callable only by governance. + #[permission(Governance)] UpdateParams { params: Params }, } diff --git a/contracts/rewards/src/state.rs b/contracts/rewards/src/state.rs index 070216bc0..e09816885 100644 --- a/contracts/rewards/src/state.rs +++ b/contracts/rewards/src/state.rs @@ -8,11 +8,11 @@ use cw_storage_plus::{Item, Key, KeyDeserialize, Map, Prefixer, PrimaryKey}; use error_stack::{Result, ResultExt}; use router_api::ChainName; -use crate::{error::ContractError, msg::Params}; +use crate::error::ContractError; +use crate::msg::Params; #[cw_serde] pub struct Config { - pub governance: Addr, pub rewards_denom: String, } @@ -207,10 +207,10 @@ impl Epoch { if cur_block_height < last_updated_epoch.block_height_started { Err(ContractError::BlockHeightInPast.into()) } else { - let epochs_elapsed = (cur_block_height - .saturating_sub(last_updated_epoch.block_height_started)) - .checked_div(epoch_duration) - .expect("invalid invariant: epoch duration is zero"); + let epochs_elapsed = cur_block_height + .saturating_sub(last_updated_epoch.block_height_started) + .checked_div(epoch_duration) + .expect("invalid invariant: epoch duration is zero"); Ok(Epoch { epoch_num: last_updated_epoch .epoch_num @@ -400,12 +400,16 @@ impl Deref for StorageState { #[cfg(test)] mod test { + use std::collections::HashMap; + + use cosmwasm_std::testing::mock_dependencies; + use cosmwasm_std::{Addr, Uint128, Uint64}; + use router_api::ChainName; + use super::*; use crate::error::ContractError; - use crate::{msg::Params, state::ParamsSnapshot}; - use cosmwasm_std::{testing::mock_dependencies, Addr, Uint128, Uint64}; - use router_api::ChainName; - use std::collections::HashMap; + use crate::msg::Params; + use crate::state::ParamsSnapshot; /// Test that the rewards are /// - distributed evenly to all verifiers that reach quorum diff --git a/contracts/router/.cargo/config.toml b/contracts/router/.cargo/config.toml new file mode 100644 index 000000000..af5698e58 --- /dev/null +++ b/contracts/router/.cargo/config.toml @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema" diff --git a/contracts/router/Cargo.toml b/contracts/router/Cargo.toml index 8465ff312..506e96210 100644 --- a/contracts/router/Cargo.toml +++ b/contracts/router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "router" -version = "0.3.3" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" description = "Router contract" @@ -33,8 +33,7 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] -axelar-wasm-std = { workspace = true } -axelar-wasm-std-derive = { workspace = true } +axelar-wasm-std = { workspace = true, features = ["derive"] } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } @@ -43,10 +42,12 @@ error-stack = { workspace = true } flagset = { version = "0.4.3", features = ["serde"] } gateway-api = { workspace = true } itertools = { workspace = true } -mockall = "0.11.4" +mockall = { workspace = true } +msgs-derive = { workspace = true } report = { workspace = true } router-api = { workspace = true } serde_json = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] cw-multi-test = "0.15.1" diff --git a/contracts/router/src/bin/schema.rs b/contracts/router/src/bin/schema.rs index ae74ba830..6a15ff65f 100644 --- a/contracts/router/src/bin/schema.rs +++ b/contracts/router/src/bin/schema.rs @@ -1,7 +1,6 @@ use cosmwasm_schema::write_api; -use router_api::msg::{ExecuteMsg, QueryMsg}; - use router::msg::InstantiateMsg; +use router_api::msg::{ExecuteMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/router/src/contract.rs b/contracts/router/src/contract.rs index 8a389f09f..ac911eb2d 100644 --- a/contracts/router/src/contract.rs +++ b/contracts/router/src/contract.rs @@ -1,31 +1,36 @@ -use axelar_wasm_std::permission_control::Permission; -use axelar_wasm_std::{ensure_any_permission, ensure_permission, permission_control}; +use axelar_wasm_std::{killswitch, permission_control, FnExt}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response}; - +use cosmwasm_std::{ + to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, Storage, +}; +use router_api::error::Error; use router_api::msg::{ExecuteMsg, QueryMsg}; +use crate::contract::migrations::v0_3_3; use crate::events::RouterInstantiated; -use crate::migrations; use crate::msg::InstantiateMsg; -use crate::state::{Config, RouterStore, Store}; +use crate::state; +use crate::state::{load_chain_by_gateway, load_config, Config}; mod execute; +mod migrations; mod query; -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - +pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate( deps: DepsMut, _env: Env, _msg: Empty, -) -> Result { - migrations::v0_3_3::migrate(deps.storage)?; +) -> Result { + v0_3_3::migrate(deps.storage)?; + // this needs to be the last thing to do during migration, + // because previous migration steps should check the old version cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::default()) } @@ -35,24 +40,22 @@ pub fn instantiate( _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> Result { +) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; let admin = deps.api.addr_validate(&msg.admin_address)?; let governance = deps.api.addr_validate(&msg.governance_address)?; let nexus_gateway = deps.api.addr_validate(&msg.nexus_gateway)?; + permission_control::set_admin(deps.storage, &admin)?; + permission_control::set_governance(deps.storage, &governance)?; + let config = Config { - admin: admin.clone(), - governance: governance.clone(), nexus_gateway: nexus_gateway.clone(), }; - permission_control::set_admin(deps.storage, &admin)?; - permission_control::set_governance(deps.storage, &governance)?; - RouterStore::new(deps.storage) - .save_config(config) - .expect("must save the config"); + state::save_config(deps.storage, &config)?; + killswitch::init(deps.storage, killswitch::State::Disengaged)?; Ok(Response::new().add_event( RouterInstantiated { @@ -70,48 +73,52 @@ pub fn execute( _env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result { - match msg { +) -> Result { + match msg.ensure_permissions( + deps.storage, + &info.sender, + find_gateway_address(&info.sender), + )? { ExecuteMsg::RegisterChain { chain, gateway_address, msg_id_format, } => { - ensure_permission!(Permission::Governance, deps.storage, &info.sender); - let gateway_address = deps.api.addr_validate(&gateway_address)?; - execute::register_chain(deps, chain, gateway_address, msg_id_format) + execute::register_chain(deps.storage, chain, gateway_address, msg_id_format) } ExecuteMsg::UpgradeGateway { chain, contract_address, } => { - ensure_permission!(Permission::Governance, deps.storage, &info.sender); - let contract_address = deps.api.addr_validate(&contract_address)?; - execute::upgrade_gateway(deps, chain, contract_address) - } - ExecuteMsg::FreezeChain { chain, direction } => { - ensure_permission!(Permission::Admin, deps.storage, &info.sender); - - execute::freeze_chain(deps, chain, direction) - } - ExecuteMsg::UnfreezeChain { chain, direction } => { - ensure_permission!(Permission::Admin, deps.storage, &info.sender); - - execute::unfreeze_chain(deps, chain, direction) + execute::upgrade_gateway(deps.storage, chain, contract_address) } + ExecuteMsg::FreezeChains { chains } => execute::freeze_chains(deps.storage, chains), + ExecuteMsg::UnfreezeChains { chains } => execute::unfreeze_chains(deps.storage, chains), ExecuteMsg::RouteMessages(msgs) => { - ensure_any_permission!(); + Ok(execute::route_messages(deps.storage, info.sender, msgs)?) + } + ExecuteMsg::DisableRouting => execute::disable_routing(deps.storage), + ExecuteMsg::EnableRouting => execute::enable_routing(deps.storage), + }? + .then(Ok) +} - Ok(execute::route_messages( - RouterStore::new(deps.storage), - info.sender, - msgs, - )?) +fn find_gateway_address( + sender: &Addr, +) -> impl FnOnce(&dyn Storage, &ExecuteMsg) -> error_stack::Result + '_ { + move |storage, _| { + let nexus_gateway = load_config(storage)?.nexus_gateway; + if nexus_gateway == sender { + Ok(nexus_gateway) + } else { + load_chain_by_gateway(storage, sender)? + .gateway + .address + .then(Ok) } } - .map_err(axelar_wasm_std::ContractError::from) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -119,33 +126,38 @@ pub fn query( deps: Deps, _env: Env, msg: QueryMsg, -) -> Result { +) -> Result { match msg { - QueryMsg::GetChainInfo(chain) => to_json_binary(&query::get_chain_info(deps, chain)?), + QueryMsg::ChainInfo(chain) => to_json_binary(&query::chain_info(deps.storage, chain)?), QueryMsg::Chains { start_after, limit } => { to_json_binary(&query::chains(deps, start_after, limit)?) } + QueryMsg::IsEnabled => to_json_binary(&killswitch::is_contract_active(deps.storage)), } - .map_err(axelar_wasm_std::ContractError::from) + .map_err(axelar_wasm_std::error::ContractError::from) } #[cfg(test)] mod test { - use std::{collections::HashMap, str::FromStr}; - - use crate::state::CONFIG; - - use super::*; - - use axelar_wasm_std::msg_id::tx_hash_event_index::HexTxHashAndEventIndex; - use cosmwasm_std::{ - testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, - Addr, CosmosMsg, Empty, OwnedDeps, WasmMsg, + use std::collections::HashMap; + use std::str::FromStr; + + use axelar_wasm_std::err_contains; + use axelar_wasm_std::error::ContractError; + use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; + use cosmwasm_std::testing::{ + mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, }; + use cosmwasm_std::{from_json, Addr, CosmosMsg, Empty, OwnedDeps, WasmMsg}; + use permission_control::Permission; + use router_api::error::Error; use router_api::{ - error::Error, ChainName, CrossChainId, GatewayDirection, Message, CHAIN_NAME_DELIMITER, + ChainEndpoint, ChainName, CrossChainId, GatewayDirection, Message, FIELD_DELIMITER, }; + use super::*; + use crate::events; + const ADMIN_ADDRESS: &str = "admin"; const GOVERNANCE_ADDRESS: &str = "governance"; const NEXUS_GATEWAY_ADDRESS: &str = "nexus_gateway"; @@ -154,14 +166,17 @@ mod test { fn setup() -> OwnedDeps { let mut deps = mock_dependencies(); - let config = Config { - admin: Addr::unchecked(ADMIN_ADDRESS), - governance: Addr::unchecked(GOVERNANCE_ADDRESS), - nexus_gateway: Addr::unchecked(NEXUS_GATEWAY_ADDRESS), - }; - CONFIG.save(deps.as_mut().storage, &config).unwrap(); - permission_control::set_admin(deps.as_mut().storage, &config.admin).unwrap(); - permission_control::set_governance(deps.as_mut().storage, &config.governance).unwrap(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + InstantiateMsg { + admin_address: ADMIN_ADDRESS.to_string(), + governance_address: GOVERNANCE_ADDRESS.to_string(), + nexus_gateway: NEXUS_GATEWAY_ADDRESS.to_string(), + }, + ) + .unwrap(); deps } @@ -208,10 +223,7 @@ mod test { } .to_string(); msgs.push(Message { - cc_id: CrossChainId { - id: id.parse().unwrap(), - chain: src_chain.chain_name.clone(), - }, + cc_id: CrossChainId::new(src_chain.chain_name.clone(), id).unwrap(), destination_address: "idc".parse().unwrap(), destination_chain: dest_chain.chain_name.clone(), source_address: "idc".parse().unwrap(), @@ -221,20 +233,23 @@ mod test { msgs } - pub fn assert_contract_err_strings_equal( - actual: impl Into, - expected: impl Into, + pub fn assert_contract_err_string_contains( + actual: impl Into, + expected: impl Into, ) { - assert_eq!(actual.into().to_string(), expected.into().to_string()); + assert!(actual + .into() + .to_string() + .contains(&expected.into().to_string())); } pub fn assert_messages_in_cosmos_msg( contract_addr: String, messages: Vec, - cosmos_msg: CosmosMsg, + cosmos_msg: &CosmosMsg, ) { assert_eq!( - CosmosMsg::Wasm(WasmMsg::Execute { + &CosmosMsg::Wasm(WasmMsg::Execute { contract_addr, msg: to_json_binary(&gateway_api::msg::ExecuteMsg::RouteMessages(messages,)) .unwrap(), @@ -244,31 +259,6 @@ mod test { ); } - #[test] - fn migrate_checks_contract_version() { - let mut deps = mock_dependencies(); - CONFIG - .save( - deps.as_mut().storage, - &Config { - admin: Addr::unchecked("admin"), - governance: Addr::unchecked("governance"), - nexus_gateway: Addr::unchecked("nexus_gateway"), - }, - ) - .unwrap(); - - assert!(migrate(deps.as_mut(), mock_env(), Empty {}).is_err()); - - cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap(); - - assert!(migrate(deps.as_mut(), mock_env(), Empty {}).is_err()); - - cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, CONTRACT_VERSION).unwrap(); - - assert!(migrate(deps.as_mut(), mock_env(), Empty {}).is_ok()); - } - #[test] fn successful_routing() { let mut deps = setup(); @@ -293,7 +283,7 @@ mod test { assert_messages_in_cosmos_msg( polygon.gateway.to_string(), messages.clone(), - res.messages[0].msg.clone(), + &res.messages[0].msg, ); // try to route twice @@ -326,7 +316,58 @@ mod test { ) .unwrap_err(); - assert_contract_err_strings_equal(err, Error::WrongSourceChain); + assert_contract_err_string_contains(err, Error::WrongSourceChain); + } + + #[test] + fn amplifier_messages_must_have_lower_case() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let mut messages = generate_messages(ð, &polygon, &mut 0, 1); + messages + .iter_mut() + .for_each(|msg| msg.cc_id.source_chain = "Ethereum".parse().unwrap()); + + let result = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(messages), + ) + .unwrap_err(); + assert!(err_contains!(result.report, Error, Error::WrongSourceChain)); + } + + #[test] + fn nexus_messages_can_have_upper_case() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + + register_chain(deps.as_mut(), &polygon); + + let mut messages = generate_messages(ð, &polygon, &mut 0, 1); + messages + .iter_mut() + .for_each(|msg| msg.cc_id.source_chain = "Ethereum".parse().unwrap()); + + let result = execute( + deps.as_mut(), + mock_env(), + mock_info(NEXUS_GATEWAY_ADDRESS, &[]), + ExecuteMsg::RouteMessages(messages.clone()), + ); + assert!(result.is_ok()); + assert_messages_in_cosmos_msg( + polygon.gateway.to_string(), + messages, + &result.unwrap().messages[0].msg, + ); } #[test] @@ -385,9 +426,9 @@ mod test { .unwrap() .clone() .into_iter() - .filter(|m| m.cc_id.chain == s.chain_name) + .filter(|m| m.cc_id.source_chain == s.chain_name) .collect::>(), - res.messages[i].msg.clone(), + &res.messages[i].msg, ); } } @@ -409,7 +450,7 @@ mod test { }, ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, permission_control::Error::PermissionDenied { expected: Permission::Governance.into(), @@ -428,7 +469,7 @@ mod test { }, ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, permission_control::Error::PermissionDenied { expected: Permission::Governance.into(), @@ -452,45 +493,45 @@ mod test { deps.as_mut(), mock_env(), mock_info(UNAUTHORIZED_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: chain.chain_name.clone(), - direction: GatewayDirection::Bidirectional, + ExecuteMsg::FreezeChains { + chains: HashMap::from([( + chain.chain_name.clone(), + GatewayDirection::Bidirectional, + )]), }, ) .unwrap_err(); - assert_contract_err_strings_equal( + + assert_contract_err_string_contains( err, permission_control::Error::PermissionDenied { - expected: Permission::Admin.into(), + expected: Permission::Elevated.into(), actual: Permission::NoPrivilege.into(), }, ); - let err = execute( + assert!(execute( deps.as_mut(), mock_env(), mock_info(GOVERNANCE_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: chain.chain_name.clone(), - direction: GatewayDirection::Bidirectional, + ExecuteMsg::FreezeChains { + chains: HashMap::from([( + chain.chain_name.clone(), + GatewayDirection::Bidirectional, + )]), }, ) - .unwrap_err(); - assert_contract_err_strings_equal( - err, - permission_control::Error::PermissionDenied { - expected: Permission::Admin.into(), - actual: Permission::Governance.into(), - }, - ); + .is_ok()); let res = execute( deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: chain.chain_name.clone(), - direction: GatewayDirection::Bidirectional, + ExecuteMsg::FreezeChains { + chains: HashMap::from([( + chain.chain_name.clone(), + GatewayDirection::Bidirectional, + )]), }, ); assert!(res.is_ok()); @@ -499,45 +540,35 @@ mod test { deps.as_mut(), mock_env(), mock_info(UNAUTHORIZED_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: chain.chain_name.clone(), - direction: GatewayDirection::None, + ExecuteMsg::FreezeChains { + chains: HashMap::from([(chain.chain_name.clone(), GatewayDirection::None)]), }, ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, permission_control::Error::PermissionDenied { - expected: Permission::Admin.into(), + expected: Permission::Elevated.into(), actual: Permission::NoPrivilege.into(), }, ); - let err = execute( + assert!(execute( deps.as_mut(), mock_env(), mock_info(GOVERNANCE_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: chain.chain_name.clone(), - direction: GatewayDirection::None, + ExecuteMsg::FreezeChains { + chains: HashMap::from([(chain.chain_name.clone(), GatewayDirection::None)]), }, ) - .unwrap_err(); - assert_contract_err_strings_equal( - err, - permission_control::Error::PermissionDenied { - expected: Permission::Admin.into(), - actual: Permission::Governance.into(), - }, - ); + .is_ok()); let res = execute( deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: chain.chain_name.clone(), - direction: GatewayDirection::None, + ExecuteMsg::FreezeChains { + chains: HashMap::from([(chain.chain_name.clone(), GatewayDirection::None)]), }, ); assert!(res.is_ok()); @@ -555,7 +586,7 @@ mod test { }, ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, permission_control::Error::PermissionDenied { expected: Permission::Governance.into(), @@ -576,7 +607,7 @@ mod test { }, ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, permission_control::Error::PermissionDenied { expected: Permission::Governance.into(), @@ -607,7 +638,7 @@ mod test { register_chain(deps.as_mut(), ð); register_chain(deps.as_mut(), &polygon); - let new_gateway = Addr::unchecked("new_gateway"); + let new_gateway = Addr::unchecked("new-gateway"); let _ = execute( deps.as_mut(), @@ -633,7 +664,7 @@ mod test { assert_messages_in_cosmos_msg( new_gateway.to_string(), messages.clone(), - res.messages[0].msg.clone(), + &res.messages[0].msg, ); } @@ -645,7 +676,7 @@ mod test { register_chain(deps.as_mut(), ð); register_chain(deps.as_mut(), &polygon); - let new_gateway = Addr::unchecked("new_gateway"); + let new_gateway = Addr::unchecked("new-gateway"); let _ = execute( deps.as_mut(), @@ -666,7 +697,7 @@ mod test { ExecuteMsg::RouteMessages(messages.clone()), ) .unwrap_err(); - assert_contract_err_strings_equal(err, Error::GatewayNotRegistered); + assert_contract_err_string_contains(err, Error::GatewayNotRegistered); let res = execute( deps.as_mut(), @@ -680,7 +711,7 @@ mod test { assert_messages_in_cosmos_msg( eth.gateway.to_string(), messages.clone(), - res.messages[0].msg.clone(), + &res.messages[0].msg, ); } @@ -698,7 +729,12 @@ mod test { ExecuteMsg::RouteMessages(vec![message.clone()]), ) .unwrap_err(); - assert_contract_err_strings_equal(err, Error::GatewayNotRegistered); + assert_contract_err_string_contains( + err, + permission_control::Error::WhitelistNotFound { + sender: eth.gateway.clone(), + }, + ); register_chain(deps.as_mut(), ð); register_chain(deps.as_mut(), &polygon); @@ -732,7 +768,7 @@ mod test { }, ) .unwrap_err(); - assert_contract_err_strings_equal(err, Error::ChainAlreadyExists); + assert_contract_err_string_contains(err, Error::ChainAlreadyExists); // case insensitive let err = execute( @@ -749,17 +785,17 @@ mod test { }, ) .unwrap_err(); - assert_contract_err_strings_equal(err, Error::ChainAlreadyExists); + assert_contract_err_string_contains(err, Error::ChainAlreadyExists); } #[test] fn invalid_chain_name() { - assert_contract_err_strings_equal( - ChainName::from_str(format!("bad{}", CHAIN_NAME_DELIMITER).as_str()).unwrap_err(), + assert_contract_err_string_contains( + ChainName::from_str(format!("bad{}", FIELD_DELIMITER).as_str()).unwrap_err(), Error::InvalidChainName, ); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( ChainName::from_str("").unwrap_err(), Error::InvalidChainName, ); @@ -783,7 +819,7 @@ mod test { }, ) .unwrap_err(); - assert_contract_err_strings_equal(err, Error::GatewayAlreadyRegistered); + assert_contract_err_string_contains(err, Error::GatewayAlreadyRegistered); register_chain(deps.as_mut(), &polygon); let err = execute( @@ -797,7 +833,7 @@ mod test { ) .unwrap_err(); - assert_contract_err_strings_equal(err, Error::GatewayAlreadyRegistered); + assert_contract_err_string_contains(err, Error::GatewayAlreadyRegistered); } #[test] @@ -812,9 +848,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, + ExecuteMsg::FreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Incoming)]), }, ) .unwrap(); @@ -828,7 +863,7 @@ mod test { ExecuteMsg::RouteMessages(vec![message.clone()]), ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, Error::ChainFrozen { chain: polygon.chain_name.clone(), @@ -849,16 +884,15 @@ mod test { assert_messages_in_cosmos_msg( polygon.gateway.to_string(), messages.clone(), - res.messages[0].msg.clone(), + &res.messages[0].msg, ); let res = execute( deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, + ExecuteMsg::UnfreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Incoming)]), }, ); assert!(res.is_ok()); @@ -886,9 +920,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, + ExecuteMsg::FreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Outgoing)]), }, ); assert!(res.is_ok()); @@ -902,7 +935,7 @@ mod test { ExecuteMsg::RouteMessages(messages.clone()), ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, Error::ChainFrozen { chain: polygon.chain_name.clone(), @@ -913,9 +946,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, + ExecuteMsg::UnfreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Outgoing)]), }, ); assert!(res.is_ok()); @@ -932,7 +964,7 @@ mod test { assert_messages_in_cosmos_msg( polygon.gateway.to_string(), messages.clone(), - res.messages[0].msg.clone(), + &res.messages[0].msg, ); } @@ -960,9 +992,11 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, + ExecuteMsg::FreezeChains { + chains: HashMap::from([( + polygon.chain_name.clone(), + GatewayDirection::Bidirectional, + )]), }, ); assert!(res.is_ok()); @@ -976,7 +1010,7 @@ mod test { ) .unwrap_err(); // can't route to frozen chain - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, Error::ChainFrozen { chain: polygon.chain_name.clone(), @@ -992,7 +1026,7 @@ mod test { ExecuteMsg::RouteMessages(vec![message.clone()]), ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, Error::ChainFrozen { chain: polygon.chain_name.clone(), @@ -1004,9 +1038,11 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, + ExecuteMsg::UnfreezeChains { + chains: HashMap::from([( + polygon.chain_name.clone(), + GatewayDirection::Bidirectional, + )]), }, ) .unwrap(); @@ -1032,6 +1068,114 @@ mod test { assert!(res.is_ok()); } + #[test] + fn freeze_and_unfreeze_all_chains() { + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + let test_case = HashMap::from([ + (eth.chain_name.clone(), GatewayDirection::Bidirectional), + (polygon.chain_name.clone(), GatewayDirection::Bidirectional), + ]); + + let mut deps = setup(); + + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let chains = query( + deps.as_ref(), + mock_env(), + QueryMsg::Chains { + start_after: None, + limit: None, + }, + ) + .unwrap() + .then(|chains| from_json::>(&chains)) + .unwrap(); + + for chain in chains.iter() { + assert!(!chain.incoming_frozen() && !chain.outgoing_frozen()) + } + + type Check = fn(&Result) -> bool; // clippy complains without the alias about complex types + + // try sender without permission + let permission_control: Vec<(&str, Check)> = vec![ + (UNAUTHORIZED_ADDRESS, Result::is_err), + (GOVERNANCE_ADDRESS, Result::is_ok), + (ADMIN_ADDRESS, Result::is_ok), + ]; + + for permission_case in permission_control.iter() { + let (sender, result_check) = permission_case; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(sender, &[]), + ExecuteMsg::FreezeChains { + chains: test_case.clone(), + }, + ); + assert!(result_check(&res)); + } + + let chains = query( + deps.as_ref(), + mock_env(), + QueryMsg::Chains { + start_after: None, + limit: None, + }, + ) + .unwrap() + .then(|chains| from_json::>(&chains)) + .unwrap(); + + for chain in chains.iter() { + assert!(chain.incoming_frozen() && chain.outgoing_frozen()) + } + + // try sender without permission + let permission_control: Vec<(&str, Check)> = vec![ + (UNAUTHORIZED_ADDRESS, Result::is_err), + (GOVERNANCE_ADDRESS, Result::is_ok), + (ADMIN_ADDRESS, Result::is_ok), + ]; + + for permission_case in permission_control.iter() { + let (sender, result_check) = permission_case; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(sender, &[]), + ExecuteMsg::UnfreezeChains { + chains: HashMap::from([ + (eth.chain_name.clone(), GatewayDirection::Bidirectional), + (polygon.chain_name.clone(), GatewayDirection::Bidirectional), + ]), + }, + ); + assert!(result_check(&res)); + } + + let chains = query( + deps.as_ref(), + mock_env(), + QueryMsg::Chains { + start_after: None, + limit: None, + }, + ) + .unwrap() + .then(|chains| from_json::>(&chains)) + .unwrap(); + + for chain in chains.iter() { + assert!(!chain.incoming_frozen() && !chain.outgoing_frozen()) + } + } + #[test] fn unfreeze_incoming() { let mut deps = setup(); @@ -1044,9 +1188,11 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, + ExecuteMsg::FreezeChains { + chains: HashMap::from([( + polygon.chain_name.clone(), + GatewayDirection::Bidirectional, + )]), }, ); assert!(res.is_ok()); @@ -1058,9 +1204,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, + ExecuteMsg::UnfreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Incoming)]), }, ) .unwrap(); @@ -1084,7 +1229,7 @@ mod test { ) .unwrap_err(); // can't route to the chain - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, Error::ChainFrozen { chain: polygon.chain_name.clone(), @@ -1104,9 +1249,11 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, + ExecuteMsg::FreezeChains { + chains: HashMap::from([( + polygon.chain_name.clone(), + GatewayDirection::Bidirectional, + )]), }, ); assert!(res.is_ok()); @@ -1118,9 +1265,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, + ExecuteMsg::UnfreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Outgoing)]), }, ) .unwrap(); @@ -1134,7 +1280,7 @@ mod test { ExecuteMsg::RouteMessages(vec![message.clone()]), ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, Error::ChainFrozen { chain: polygon.chain_name.clone(), @@ -1164,9 +1310,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, + ExecuteMsg::FreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Incoming)]), }, ) .unwrap(); @@ -1175,9 +1320,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, + ExecuteMsg::FreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Outgoing)]), }, ) .unwrap(); @@ -1192,7 +1336,7 @@ mod test { ExecuteMsg::RouteMessages(vec![message.clone()]), ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, Error::ChainFrozen { chain: polygon.chain_name.clone(), @@ -1208,7 +1352,7 @@ mod test { ExecuteMsg::RouteMessages(vec![message.clone()]), ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, Error::ChainFrozen { chain: polygon.chain_name.clone(), @@ -1228,9 +1372,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, + ExecuteMsg::FreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Outgoing)]), }, ) .unwrap(); @@ -1239,9 +1382,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, + ExecuteMsg::FreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Incoming)]), }, ) .unwrap(); @@ -1256,7 +1398,7 @@ mod test { ExecuteMsg::RouteMessages(vec![message.clone()]), ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, Error::ChainFrozen { chain: polygon.chain_name.clone(), @@ -1272,7 +1414,7 @@ mod test { ExecuteMsg::RouteMessages(vec![message.clone()]), ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, Error::ChainFrozen { chain: polygon.chain_name.clone(), @@ -1292,9 +1434,11 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, + ExecuteMsg::FreezeChains { + chains: HashMap::from([( + polygon.chain_name.clone(), + GatewayDirection::Bidirectional, + )]), }, ); assert!(res.is_ok()); @@ -1304,9 +1448,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, + ExecuteMsg::UnfreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Incoming)]), }, ) .unwrap(); @@ -1316,9 +1459,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, + ExecuteMsg::UnfreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Outgoing)]), }, ) .unwrap(); @@ -1357,9 +1499,11 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, + ExecuteMsg::FreezeChains { + chains: HashMap::from([( + polygon.chain_name.clone(), + GatewayDirection::Bidirectional, + )]), }, ); assert!(res.is_ok()); @@ -1369,9 +1513,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Outgoing, + ExecuteMsg::UnfreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Outgoing)]), }, ) .unwrap(); @@ -1381,9 +1524,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Incoming, + ExecuteMsg::UnfreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::Incoming)]), }, ) .unwrap(); @@ -1422,9 +1564,11 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::FreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::Bidirectional, + ExecuteMsg::FreezeChains { + chains: HashMap::from([( + polygon.chain_name.clone(), + GatewayDirection::Bidirectional, + )]), }, ); assert!(res.is_ok()); @@ -1434,9 +1578,8 @@ mod test { deps.as_mut(), mock_env(), mock_info(ADMIN_ADDRESS, &[]), - ExecuteMsg::UnfreezeChain { - chain: polygon.chain_name.clone(), - direction: GatewayDirection::None, + ExecuteMsg::UnfreezeChains { + chains: HashMap::from([(polygon.chain_name.clone(), GatewayDirection::None)]), }, ) .unwrap(); @@ -1450,7 +1593,7 @@ mod test { ExecuteMsg::RouteMessages(vec![message.clone()]), ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, Error::ChainFrozen { chain: polygon.chain_name.clone(), @@ -1466,11 +1609,196 @@ mod test { ExecuteMsg::RouteMessages(vec![message.clone()]), ) .unwrap_err(); - assert_contract_err_strings_equal( + assert_contract_err_string_contains( err, Error::ChainFrozen { chain: polygon.chain_name.clone(), }, ); } + + #[test] + fn disable_enable_router() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + let nonce = &mut 0; + let messages = &generate_messages(ð, &polygon, nonce, 1); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(messages.clone()), + ); + + assert!(res.is_ok()); + + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::DisableRouting {}, + ) + .unwrap(); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(messages.clone()), + ); + assert!(res.is_err()); + assert_contract_err_string_contains(res.unwrap_err(), Error::RoutingDisabled); + + let _ = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::EnableRouting {}, + ) + .unwrap(); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(eth.gateway.as_str(), &[]), + ExecuteMsg::RouteMessages(messages.clone()), + ); + + assert!(res.is_ok()); + } + + #[test] + fn ensure_correct_permissions_enable_disable_routing() { + let mut deps = setup(); + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(UNAUTHORIZED_ADDRESS, &[]), + ExecuteMsg::EnableRouting {}, + ) + .is_err()); + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::EnableRouting {}, + ) + .is_ok()); + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::EnableRouting {}, + ) + .is_ok()); + + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(UNAUTHORIZED_ADDRESS, &[]), + ExecuteMsg::DisableRouting {}, + ) + .is_err()); + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::DisableRouting {}, + ) + .is_ok()); + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::DisableRouting {}, + ) + .is_ok()); + } + + #[test] + fn events_are_emitted_enable_disable_routing() { + let mut deps = setup(); + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::DisableRouting {}, + ) + .unwrap(); + + assert!(res.events.len() == 1); + assert!(res.events.contains(&events::RoutingDisabled.into())); + + // don't emit event if already disabled + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::DisableRouting {}, + ) + .unwrap(); + + assert!(res.events.is_empty()); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::EnableRouting {}, + ) + .unwrap(); + + assert!(res.events.len() == 1); + assert!(res.events.contains(&events::RoutingEnabled.into())); + + // don't emit event if already enabled + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN_ADDRESS, &[]), + ExecuteMsg::EnableRouting {}, + ) + .unwrap(); + + assert!(res.events.is_empty()); + } + + #[test] + fn is_enabled() { + let mut deps = mock_dependencies(); + let is_enabled = |deps: Deps| { + from_json::(query(deps, mock_env(), QueryMsg::IsEnabled).unwrap()).unwrap() + }; + assert!(!is_enabled(deps.as_ref())); + + killswitch::init(deps.as_mut().storage, killswitch::State::Engaged).unwrap(); + assert!(!is_enabled(deps.as_ref())); + killswitch::engage(deps.as_mut().storage, events::RoutingDisabled).unwrap(); + assert!(!is_enabled(deps.as_ref())); + killswitch::disengage(deps.as_mut().storage, events::RoutingEnabled).unwrap(); + assert!(is_enabled(deps.as_ref())); + } + + #[test] + fn nexus_can_route_messages() { + let mut deps = setup(); + let eth = make_chain("ethereum"); + let polygon = make_chain("polygon"); + + register_chain(deps.as_mut(), ð); + register_chain(deps.as_mut(), &polygon); + + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(NEXUS_GATEWAY_ADDRESS, &[]), + ExecuteMsg::RouteMessages(generate_messages(ð, &polygon, &mut 0, 10)), + ) + .is_ok()); + } } diff --git a/contracts/router/src/contract/execute.rs b/contracts/router/src/contract/execute.rs index 9a4062f19..c31016b57 100644 --- a/contracts/router/src/contract/execute.rs +++ b/contracts/router/src/contract/execute.rs @@ -1,29 +1,31 @@ +use std::collections::HashMap; use std::vec; +use axelar_wasm_std::flagset::FlagSet; +use axelar_wasm_std::killswitch; use axelar_wasm_std::msg_id::{self, MessageIdFormat}; -use cosmwasm_std::{to_json_binary, Addr, DepsMut, Response, StdResult, WasmMsg}; -use error_stack::{report, ResultExt}; +use cosmwasm_std::{to_json_binary, Addr, Event, Response, StdResult, Storage, WasmMsg}; +use error_stack::{ensure, report, ResultExt}; use itertools::Itertools; - -use axelar_wasm_std::flagset::FlagSet; use router_api::error::Error; use router_api::{ChainEndpoint, ChainName, Gateway, GatewayDirection, Message}; use crate::events::{ ChainFrozen, ChainRegistered, ChainUnfrozen, GatewayInfo, GatewayUpgraded, MessageRouted, }; -use crate::state::{chain_endpoints, Config, Store}; +use crate::state::{chain_endpoints, Config}; +use crate::{events, state}; pub fn register_chain( - deps: DepsMut, + storage: &mut dyn Storage, name: ChainName, gateway: Addr, msg_id_format: MessageIdFormat, ) -> Result { - if find_chain_for_gateway(&deps, &gateway)?.is_some() { + if find_chain_for_gateway(storage, &gateway)?.is_some() { return Err(Error::GatewayAlreadyRegistered); } - chain_endpoints().update(deps.storage, name.clone(), |chain| match chain { + chain_endpoints().update(storage, name.clone(), |chain| match chain { Some(_) => Err(Error::ChainAlreadyExists), None => Ok(ChainEndpoint { name: name.clone(), @@ -38,25 +40,24 @@ pub fn register_chain( } pub fn find_chain_for_gateway( - deps: &DepsMut, + storage: &dyn Storage, contract_address: &Addr, ) -> StdResult> { - #[allow(deprecated)] chain_endpoints() .idx .gateway - .find_chain(deps, contract_address) + .load_chain_by_gateway(storage, contract_address) } pub fn upgrade_gateway( - deps: DepsMut, + storage: &mut dyn Storage, chain: ChainName, contract_address: Addr, ) -> Result { - if find_chain_for_gateway(&deps, &contract_address)?.is_some() { + if find_chain_for_gateway(storage, &contract_address)?.is_some() { return Err(Error::GatewayAlreadyRegistered); } - chain_endpoints().update(deps.storage, chain.clone(), |chain| match chain { + chain_endpoints().update(storage, chain.clone(), |chain| match chain { None => Err(Error::ChainNotFound), Some(mut chain) => { chain.gateway.address = contract_address.clone(); @@ -74,47 +75,77 @@ pub fn upgrade_gateway( )) } -pub fn freeze_chain( - deps: DepsMut, +fn freeze_specific_chain( + storage: &mut dyn Storage, chain: ChainName, direction: GatewayDirection, -) -> Result { - chain_endpoints().update(deps.storage, chain.clone(), |chain| match chain { +) -> Result { + chain_endpoints().update(storage, chain.clone(), |chain| match chain { None => Err(Error::ChainNotFound), Some(mut chain) => { *chain.frozen_status |= direction; Ok(chain) } })?; - Ok(Response::new().add_event( - ChainFrozen { - name: chain, - direction, - } - .into(), - )) + + Ok(ChainFrozen { + name: chain, + direction, + }) +} + +pub fn freeze_chains( + storage: &mut dyn Storage, + chains: HashMap, +) -> Result { + let events: Vec<_> = chains + .into_iter() + .map(|(chain, direction)| freeze_specific_chain(storage, chain, direction)) + .map_ok(Event::from) + .try_collect()?; + + Ok(Response::new().add_events(events)) } #[allow(clippy::arithmetic_side_effects)] // flagset operations don't cause under/overflows -pub fn unfreeze_chain( - deps: DepsMut, +fn unfreeze_specific_chain( + storage: &mut dyn Storage, chain: ChainName, direction: GatewayDirection, -) -> Result { - chain_endpoints().update(deps.storage, chain.clone(), |chain| match chain { +) -> Result { + chain_endpoints().update(storage, chain.clone(), |chain| match chain { None => Err(Error::ChainNotFound), Some(mut chain) => { *chain.frozen_status -= direction; Ok(chain) } })?; - Ok(Response::new().add_event( - ChainUnfrozen { - name: chain, - direction, - } - .into(), - )) + + Ok(ChainUnfrozen { + name: chain, + direction, + }) +} + +pub fn unfreeze_chains( + storage: &mut dyn Storage, + chains: HashMap, +) -> Result { + let events: Vec<_> = chains + .into_iter() + .map(|(chain, direction)| unfreeze_specific_chain(storage, chain, direction)) + .map_ok(Event::from) + .try_collect()?; + + Ok(Response::new().add_events(events)) +} + +pub fn disable_routing(storage: &mut dyn Storage) -> Result { + killswitch::engage(storage, events::RoutingDisabled).map_err(|err| err.into()) +} + +pub fn enable_routing(storage: &mut dyn Storage) -> Result { + killswitch::disengage(storage, events::RoutingEnabled).map_err(|err| err.into()) } fn verify_msg_ids( @@ -122,12 +153,12 @@ fn verify_msg_ids( expected_format: &MessageIdFormat, ) -> Result<(), error_stack::Report> { msgs.iter() - .try_for_each(|msg| msg_id::verify_msg_id(&msg.cc_id.id, expected_format)) + .try_for_each(|msg| msg_id::verify_msg_id(&msg.cc_id.message_id, expected_format)) .change_context(Error::InvalidMessageId) } fn validate_msgs( - store: &impl Store, + storage: &dyn Storage, config: Config, sender: &Addr, msgs: Vec, @@ -141,16 +172,17 @@ fn validate_msgs( return Ok(msgs); } - let source_chain = store - .load_chain_by_gateway(sender)? - .ok_or(Error::GatewayNotRegistered)?; + let source_chain = state::load_chain_by_gateway(storage, sender)?; if source_chain.incoming_frozen() { return Err(report!(Error::ChainFrozen { chain: source_chain.name, })); } - if msgs.iter().any(|msg| msg.cc_id.chain != source_chain.name) { + if msgs + .iter() + .any(|msg| msg.cc_id.source_chain != source_chain.name) + { return Err(report!(Error::WrongSourceChain)); } @@ -160,20 +192,25 @@ fn validate_msgs( } pub fn route_messages( - store: impl Store, + storage: &dyn Storage, sender: Addr, msgs: Vec, ) -> error_stack::Result { - let config = store.load_config()?; + ensure!( + killswitch::is_contract_active(storage), + Error::RoutingDisabled + ); + + let config = state::load_config(storage)?; - let msgs = validate_msgs(&store, config.clone(), &sender, msgs)?; + let msgs = validate_msgs(storage, config.clone(), &sender, msgs)?; let wasm_msgs = msgs .iter() .group_by(|msg| msg.destination_chain.to_owned()) .into_iter() .map(|(destination_chain, msgs)| { - let gateway = match store.load_chain_by_chain_name(&destination_chain)? { + let gateway = match state::load_chain_by_chain_name(storage, &destination_chain)? { Some(destination_chain) if destination_chain.outgoing_frozen() => { return Err(report!(Error::ChainFrozen { chain: destination_chain.name, @@ -205,22 +242,22 @@ pub fn route_messages( #[cfg(test)] mod test { - use axelar_wasm_std::msg_id::tx_hash_event_index::HexTxHashAndEventIndex; - use cosmwasm_std::Addr; - use mockall::predicate; - use rand::{Rng, RngCore}; + use std::collections::HashMap; use axelar_wasm_std::flagset::FlagSet; - use cosmwasm_std::testing::mock_dependencies; - use cosmwasm_std::Storage; + use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{Addr, Storage}; + use rand::{random, RngCore}; use router_api::error::Error; use router_api::{ChainEndpoint, ChainName, CrossChainId, Gateway, GatewayDirection, Message}; + use super::{freeze_chains, unfreeze_chains}; + use crate::contract::execute::route_messages; + use crate::contract::instantiate; use crate::events::{ChainFrozen, ChainUnfrozen}; + use crate::msg::InstantiateMsg; use crate::state::chain_endpoints; - use crate::state::{Config, MockStore}; - - use super::{freeze_chain, route_messages, unfreeze_chain}; fn rand_message(source_chain: ChainName, destination_chain: ChainName) -> Message { let mut bytes = [0; 32]; @@ -228,7 +265,7 @@ mod test { let id = HexTxHashAndEventIndex { tx_hash: bytes, - event_index: rand::thread_rng().gen::(), + event_index: random::(), } .to_string(); @@ -244,10 +281,7 @@ mod test { rand::thread_rng().fill_bytes(&mut payload_hash); Message { - cc_id: CrossChainId { - chain: source_chain, - id: id.parse().unwrap(), - }, + cc_id: CrossChainId::new(source_chain, id).unwrap(), source_address, destination_chain, destination_address, @@ -257,27 +291,25 @@ mod test { #[test] fn route_messages_with_not_registered_source_chain() { - let config = Config { - admin: Addr::unchecked("admin"), - governance: Addr::unchecked("governance"), - nexus_gateway: Addr::unchecked("nexus_gateway"), - }; let sender = Addr::unchecked("sender"); let source_chain: ChainName = "ethereum".parse().unwrap(); let destination_chain = "bitcoin".parse().unwrap(); - let mut store = MockStore::new(); - store - .expect_load_config() - .returning(move || Ok(config.clone())); - store - .expect_load_chain_by_gateway() - .once() - .with(predicate::eq(sender.clone())) - .return_once(|_| Ok(None)); + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + admin_address: "admin".to_string(), + governance_address: "governance".to_string(), + nexus_gateway: "nexus_gateway".to_string(), + }, + ) + .unwrap(); assert!(route_messages( - store, + deps.as_mut().storage, sender, vec![rand_message(source_chain, destination_chain)] ) @@ -286,19 +318,23 @@ mod test { #[test] fn route_messages_with_frozen_source_chain() { - let config = Config { - admin: Addr::unchecked("admin"), - governance: Addr::unchecked("governance"), - nexus_gateway: Addr::unchecked("nexus_gateway"), - }; let sender = Addr::unchecked("sender"); let source_chain: ChainName = "ethereum".parse().unwrap(); let destination_chain = "bitcoin".parse().unwrap(); - let mut store = MockStore::new(); - store - .expect_load_config() - .returning(move || Ok(config.clone())); + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + admin_address: "admin".to_string(), + governance_address: "governance".to_string(), + nexus_gateway: "nexus_gateway".to_string(), + }, + ) + .unwrap(); + let chain_endpoint = ChainEndpoint { name: source_chain.clone(), gateway: Gateway { @@ -307,14 +343,12 @@ mod test { frozen_status: FlagSet::from(GatewayDirection::Incoming), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, }; - store - .expect_load_chain_by_gateway() - .once() - .with(predicate::eq(sender.clone())) - .return_once(|_| Ok(Some(chain_endpoint))); + chain_endpoints() + .save(deps.as_mut().storage, source_chain.clone(), &chain_endpoint) + .unwrap(); assert!(route_messages( - store, + deps.as_mut().storage, sender, vec![rand_message(source_chain.clone(), destination_chain)] ) @@ -325,19 +359,23 @@ mod test { #[test] fn route_messages_with_wrong_source_chain() { - let config = Config { - admin: Addr::unchecked("admin"), - governance: Addr::unchecked("governance"), - nexus_gateway: Addr::unchecked("nexus_gateway"), - }; let sender = Addr::unchecked("sender"); let source_chain: ChainName = "ethereum".parse().unwrap(); let destination_chain = "bitcoin".parse().unwrap(); - let mut store = MockStore::new(); - store - .expect_load_config() - .returning(move || Ok(config.clone())); + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + admin_address: "admin".to_string(), + governance_address: "governance".to_string(), + nexus_gateway: "nexus_gateway".to_string(), + }, + ) + .unwrap(); + let chain_endpoint = ChainEndpoint { name: source_chain.clone(), gateway: Gateway { @@ -346,14 +384,12 @@ mod test { frozen_status: FlagSet::from(GatewayDirection::None), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, }; - store - .expect_load_chain_by_gateway() - .once() - .with(predicate::eq(sender.clone())) - .return_once(|_| Ok(Some(chain_endpoint))); + chain_endpoints() + .save(deps.as_mut().storage, source_chain.clone(), &chain_endpoint) + .unwrap(); assert!(route_messages( - store, + deps.as_mut().storage, sender, vec![rand_message("polygon".parse().unwrap(), destination_chain)] ) @@ -362,19 +398,22 @@ mod test { #[test] fn route_messages_with_frozen_destination_chain() { - let config = Config { - admin: Addr::unchecked("admin"), - governance: Addr::unchecked("governance"), - nexus_gateway: Addr::unchecked("nexus_gateway"), - }; let sender = Addr::unchecked("sender"); let source_chain: ChainName = "ethereum".parse().unwrap(); let destination_chain: ChainName = "bitcoin".parse().unwrap(); - let mut store = MockStore::new(); - store - .expect_load_config() - .returning(move || Ok(config.clone())); + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + admin_address: "admin".to_string(), + governance_address: "governance".to_string(), + nexus_gateway: "nexus_gateway".to_string(), + }, + ) + .unwrap(); let source_chain_endpoint = ChainEndpoint { name: source_chain.clone(), gateway: Gateway { @@ -383,26 +422,30 @@ mod test { frozen_status: FlagSet::from(GatewayDirection::None), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, }; - store - .expect_load_chain_by_gateway() - .once() - .with(predicate::eq(sender.clone())) - .return_once(|_| Ok(Some(source_chain_endpoint))); + chain_endpoints() + .save( + deps.as_mut().storage, + source_chain.clone(), + &source_chain_endpoint, + ) + .unwrap(); let destination_chain_endpoint = ChainEndpoint { name: destination_chain.clone(), gateway: Gateway { - address: sender.clone(), + address: Addr::unchecked("destination"), }, frozen_status: FlagSet::from(GatewayDirection::Bidirectional), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, }; - store - .expect_load_chain_by_chain_name() - .once() - .with(predicate::eq(destination_chain.clone())) - .return_once(|_| Ok(Some(destination_chain_endpoint))); + chain_endpoints() + .save( + deps.as_mut().storage, + destination_chain.clone(), + &destination_chain_endpoint, + ) + .unwrap(); - assert!(route_messages(store, sender, vec![rand_message(source_chain, destination_chain.clone())]) + assert!(route_messages(deps.as_mut().storage, sender, vec![rand_message(source_chain, destination_chain.clone())]) .is_err_and(move |err| { matches!(err.current_context(), Error::ChainFrozen { chain } if *chain == destination_chain) })); @@ -410,19 +453,23 @@ mod test { #[test] fn route_messages_from_non_nexus_with_invalid_message_id() { - let config = Config { - admin: Addr::unchecked("admin"), - governance: Addr::unchecked("governance"), - nexus_gateway: Addr::unchecked("nexus_gateway"), - }; let sender = Addr::unchecked("sender"); let source_chain: ChainName = "ethereum".parse().unwrap(); let destination_chain: ChainName = "bitcoin".parse().unwrap(); - let mut store = MockStore::new(); - store - .expect_load_config() - .returning(move || Ok(config.clone())); + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + admin_address: "admin".to_string(), + governance_address: "governance".to_string(), + nexus_gateway: "nexus_gateway".to_string(), + }, + ) + .unwrap(); + let source_chain_endpoint = ChainEndpoint { name: source_chain.clone(), gateway: Gateway { @@ -431,55 +478,64 @@ mod test { frozen_status: FlagSet::from(GatewayDirection::None), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, }; - store - .expect_load_chain_by_gateway() - .once() - .with(predicate::eq(sender.clone())) - .return_once(|_| Ok(Some(source_chain_endpoint))); - - let mut msg = rand_message(source_chain, destination_chain.clone()); - msg.cc_id.id = "foobar".try_into().unwrap(); - assert!(route_messages(store, sender, vec![msg]) + chain_endpoints() + .save( + deps.as_mut().storage, + source_chain.clone(), + &source_chain_endpoint, + ) + .unwrap(); + + let mut msg = rand_message(source_chain.clone(), destination_chain.clone()); + msg.cc_id = CrossChainId::new(source_chain, "foobar").unwrap(); + assert!(route_messages(deps.as_mut().storage, sender, vec![msg]) .is_err_and(move |err| { matches!(err.current_context(), Error::InvalidMessageId) })); } #[test] fn route_messages_from_nexus_with_invalid_message_id() { - let config = Config { - admin: Addr::unchecked("admin"), - governance: Addr::unchecked("governance"), - nexus_gateway: Addr::unchecked("nexus_gateway"), - }; - let sender = config.nexus_gateway.clone(); + let sender = Addr::unchecked("nexus_gateway"); let source_chain: ChainName = "ethereum".parse().unwrap(); let destination_chain: ChainName = "bitcoin".parse().unwrap(); - let mut store = MockStore::new(); - store - .expect_load_config() - .returning(move || Ok(config.clone())); + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + admin_address: "admin".to_string(), + governance_address: "governance".to_string(), + nexus_gateway: "nexus_gateway".to_string(), + }, + ) + .unwrap(); - let mut msg = rand_message(source_chain, destination_chain.clone()); - msg.cc_id.id = "foobar".try_into().unwrap(); - assert!(route_messages(store, sender, vec![msg]) + let mut msg = rand_message(source_chain.clone(), destination_chain.clone()); + msg.cc_id = CrossChainId::new(source_chain, "foobar").unwrap(); + assert!(route_messages(deps.as_mut().storage, sender, vec![msg]) .is_err_and(move |err| { matches!(err.current_context(), Error::InvalidMessageId) })); } #[test] fn route_messages_from_non_nexus_with_incorrect_message_id_format() { - let config = Config { - admin: Addr::unchecked("admin"), - governance: Addr::unchecked("governance"), - nexus_gateway: Addr::unchecked("nexus_gateway"), - }; let sender = Addr::unchecked("sender"); let source_chain: ChainName = "ethereum".parse().unwrap(); let destination_chain: ChainName = "bitcoin".parse().unwrap(); - let mut store = MockStore::new(); - store - .expect_load_config() - .returning(move || Ok(config.clone())); + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + admin_address: "admin".to_string(), + governance_address: "governance".to_string(), + nexus_gateway: "nexus_gateway".to_string(), + }, + ) + .unwrap(); + let source_chain_endpoint = ChainEndpoint { name: source_chain.clone(), gateway: Gateway { @@ -488,40 +544,50 @@ mod test { frozen_status: FlagSet::from(GatewayDirection::None), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::Base58TxDigestAndEventIndex, }; - store - .expect_load_chain_by_gateway() - .once() - .with(predicate::eq(sender.clone())) - .return_once(|_| Ok(Some(source_chain_endpoint))); - - let mut msg = rand_message(source_chain, destination_chain.clone()); - msg.cc_id.id = HexTxHashAndEventIndex { - tx_hash: [0; 32], - event_index: 0, - } - .to_string() - .try_into() + chain_endpoints() + .save( + deps.as_mut().storage, + source_chain.clone(), + &source_chain_endpoint, + ) + .unwrap(); + + let mut msg = rand_message(source_chain.clone(), destination_chain.clone()); + msg.cc_id = CrossChainId::new( + source_chain, + HexTxHashAndEventIndex { + tx_hash: [0; 32], + event_index: 0, + } + .to_string() + .as_str(), + ) .unwrap(); - assert!(route_messages(store, sender, vec![msg]) + + assert!(route_messages(deps.as_mut().storage, sender, vec![msg]) .is_err_and(move |err| { matches!(err.current_context(), Error::InvalidMessageId) })); } #[test] fn route_messages_from_non_nexus_to_non_nexus() { - let config = Config { - admin: Addr::unchecked("admin"), - governance: Addr::unchecked("governance"), - nexus_gateway: Addr::unchecked("nexus_gateway"), - }; let sender = Addr::unchecked("sender"); let source_chain: ChainName = "ethereum".parse().unwrap(); let destination_chain_1: ChainName = "bitcoin".parse().unwrap(); let destination_chain_2: ChainName = "polygon".parse().unwrap(); - let mut store = MockStore::new(); - store - .expect_load_config() - .returning(move || Ok(config.clone())); + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + admin_address: "admin".to_string(), + governance_address: "governance".to_string(), + nexus_gateway: "nexus_gateway".to_string(), + }, + ) + .unwrap(); + let source_chain_endpoint = ChainEndpoint { name: source_chain.clone(), gateway: Gateway { @@ -530,40 +596,46 @@ mod test { frozen_status: FlagSet::from(GatewayDirection::None), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, }; - store - .expect_load_chain_by_gateway() - .once() - .with(predicate::eq(sender.clone())) - .return_once(|_| Ok(Some(source_chain_endpoint))); + chain_endpoints() + .save( + deps.as_mut().storage, + source_chain.clone(), + &source_chain_endpoint, + ) + .unwrap(); let destination_chain_endpoint_1 = ChainEndpoint { name: destination_chain_1.clone(), gateway: Gateway { - address: sender.clone(), + address: Addr::unchecked("destination_1"), }, frozen_status: FlagSet::from(GatewayDirection::None), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, }; - store - .expect_load_chain_by_chain_name() - .once() - .with(predicate::eq(destination_chain_1.clone())) - .return_once(|_| Ok(Some(destination_chain_endpoint_1))); + chain_endpoints() + .save( + deps.as_mut().storage, + destination_chain_1.clone(), + &destination_chain_endpoint_1, + ) + .unwrap(); let destination_chain_endpoint_2 = ChainEndpoint { name: destination_chain_2.clone(), gateway: Gateway { - address: sender.clone(), + address: Addr::unchecked("destination_2"), }, frozen_status: FlagSet::from(GatewayDirection::None), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, }; - store - .expect_load_chain_by_chain_name() - .once() - .with(predicate::eq(destination_chain_2.clone())) - .return_once(|_| Ok(Some(destination_chain_endpoint_2))); + chain_endpoints() + .save( + deps.as_mut().storage, + destination_chain_2.clone(), + &destination_chain_endpoint_2, + ) + .unwrap(); assert!(route_messages( - store, + deps.as_mut().storage, sender, vec![ rand_message(source_chain.clone(), destination_chain_1.clone()), @@ -577,20 +649,24 @@ mod test { #[test] fn route_messages_from_nexus_to_registered_chains() { - let config = Config { - admin: Addr::unchecked("admin"), - governance: Addr::unchecked("governance"), - nexus_gateway: Addr::unchecked("nexus_gateway"), - }; - let sender = config.nexus_gateway.clone(); + let sender = Addr::unchecked("nexus_gateway"); let source_chain: ChainName = "ethereum".parse().unwrap(); let destination_chain_1: ChainName = "bitcoin".parse().unwrap(); let destination_chain_2: ChainName = "polygon".parse().unwrap(); - let mut store = MockStore::new(); - store - .expect_load_config() - .returning(move || Ok(config.clone())); + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + admin_address: "admin".to_string(), + governance_address: "governance".to_string(), + nexus_gateway: "nexus_gateway".to_string(), + }, + ) + .unwrap(); + let destination_chain_endpoint_1 = ChainEndpoint { name: destination_chain_1.clone(), gateway: Gateway { @@ -599,11 +675,13 @@ mod test { frozen_status: FlagSet::from(GatewayDirection::None), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, }; - store - .expect_load_chain_by_chain_name() - .once() - .with(predicate::eq(destination_chain_1.clone())) - .return_once(|_| Ok(Some(destination_chain_endpoint_1))); + chain_endpoints() + .save( + deps.as_mut().storage, + destination_chain_1.clone(), + &destination_chain_endpoint_1, + ) + .unwrap(); let destination_chain_endpoint_2 = ChainEndpoint { name: destination_chain_2.clone(), gateway: Gateway { @@ -612,14 +690,16 @@ mod test { frozen_status: FlagSet::from(GatewayDirection::None), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, }; - store - .expect_load_chain_by_chain_name() - .once() - .with(predicate::eq(destination_chain_2.clone())) - .return_once(|_| Ok(Some(destination_chain_endpoint_2))); + chain_endpoints() + .save( + deps.as_mut().storage, + destination_chain_2.clone(), + &destination_chain_endpoint_2, + ) + .unwrap(); assert!(route_messages( - store, + deps.as_mut().storage, sender, vec![ rand_message(source_chain.clone(), destination_chain_1.clone()), @@ -633,27 +713,25 @@ mod test { #[test] fn route_messages_from_nexus_to_non_registered_chains() { - let config = Config { - admin: Addr::unchecked("admin"), - governance: Addr::unchecked("governance"), - nexus_gateway: Addr::unchecked("nexus_gateway"), - }; - let sender = config.nexus_gateway.clone(); + let sender = Addr::unchecked("nexus_gateway"); let source_chain: ChainName = "ethereum".parse().unwrap(); let destination_chain: ChainName = "bitcoin".parse().unwrap(); - let mut store = MockStore::new(); - store - .expect_load_config() - .returning(move || Ok(config.clone())); - store - .expect_load_chain_by_chain_name() - .once() - .with(predicate::eq(destination_chain.clone())) - .return_once(|_| Ok(None)); + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + admin_address: "admin".to_string(), + governance_address: "governance".to_string(), + nexus_gateway: "nexus_gateway".to_string(), + }, + ) + .unwrap(); assert!(route_messages( - store, + deps.as_mut().storage, sender, vec![rand_message( source_chain.clone(), @@ -665,19 +743,23 @@ mod test { #[test] fn route_messages_from_registered_chain_to_nexus() { - let config = Config { - admin: Addr::unchecked("admin"), - governance: Addr::unchecked("governance"), - nexus_gateway: Addr::unchecked("nexus_gateway"), - }; let sender = Addr::unchecked("sender"); let source_chain: ChainName = "ethereum".parse().unwrap(); let destination_chain: ChainName = "bitcoin".parse().unwrap(); - let mut store = MockStore::new(); - store - .expect_load_config() - .returning(move || Ok(config.clone())); + let mut deps = mock_dependencies(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + admin_address: "admin".to_string(), + governance_address: "governance".to_string(), + nexus_gateway: "nexus_gateway".to_string(), + }, + ) + .unwrap(); + let source_chain_endpoint = ChainEndpoint { name: source_chain.clone(), gateway: Gateway { @@ -686,19 +768,16 @@ mod test { frozen_status: FlagSet::from(GatewayDirection::None), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, }; - store - .expect_load_chain_by_gateway() - .once() - .with(predicate::eq(sender.clone())) - .return_once(|_| Ok(Some(source_chain_endpoint))); - store - .expect_load_chain_by_chain_name() - .once() - .with(predicate::eq(destination_chain.clone())) - .return_once(|_| Ok(None)); + chain_endpoints() + .save( + deps.as_mut().storage, + source_chain.clone(), + &source_chain_endpoint, + ) + .unwrap(); assert!(route_messages( - store, + deps.as_mut().storage, sender, vec![rand_message( source_chain.clone(), @@ -729,8 +808,16 @@ mod test { .unwrap(); // freezing twice produces same result - freeze_chain(deps.as_mut(), chain.clone(), GatewayDirection::Incoming).unwrap(); - freeze_chain(deps.as_mut(), chain.clone(), GatewayDirection::Incoming).unwrap(); + freeze_chains( + deps.as_mut().storage, + HashMap::from([(chain.clone(), GatewayDirection::Incoming)]), + ) + .unwrap(); + freeze_chains( + deps.as_mut().storage, + HashMap::from([(chain.clone(), GatewayDirection::Incoming)]), + ) + .unwrap(); assert_chain_endpoint_frozen_status( deps.as_mut().storage, @@ -738,16 +825,14 @@ mod test { FlagSet::from(GatewayDirection::Incoming), ); - freeze_chain( - deps.as_mut(), - chain.clone(), - GatewayDirection::Bidirectional, + freeze_chains( + deps.as_mut().storage, + HashMap::from([(chain.clone(), GatewayDirection::Bidirectional)]), ) .unwrap(); - freeze_chain( - deps.as_mut(), - chain.clone(), - GatewayDirection::Bidirectional, + freeze_chains( + deps.as_mut().storage, + HashMap::from([(chain.clone(), GatewayDirection::Bidirectional)]), ) .unwrap(); @@ -758,8 +843,16 @@ mod test { ); // unfreezing twice produces same result - unfreeze_chain(deps.as_mut(), chain.clone(), GatewayDirection::Outgoing).unwrap(); - unfreeze_chain(deps.as_mut(), chain.clone(), GatewayDirection::Outgoing).unwrap(); + unfreeze_chains( + deps.as_mut().storage, + HashMap::from([(chain.clone(), GatewayDirection::Outgoing)]), + ) + .unwrap(); + unfreeze_chains( + deps.as_mut().storage, + HashMap::from([(chain.clone(), GatewayDirection::Outgoing)]), + ) + .unwrap(); assert_chain_endpoint_frozen_status( deps.as_mut().storage, @@ -767,16 +860,14 @@ mod test { FlagSet::from(GatewayDirection::Incoming), ); - unfreeze_chain( - deps.as_mut(), - chain.clone(), - GatewayDirection::Bidirectional, + unfreeze_chains( + deps.as_mut().storage, + HashMap::from([(chain.clone(), GatewayDirection::Bidirectional)]), ) .unwrap(); - unfreeze_chain( - deps.as_mut(), - chain.clone(), - GatewayDirection::Bidirectional, + unfreeze_chains( + deps.as_mut().storage, + HashMap::from([(chain.clone(), GatewayDirection::Bidirectional)]), ) .unwrap(); @@ -807,7 +898,11 @@ mod test { ) .unwrap(); - let res = freeze_chain(deps.as_mut(), chain.clone(), GatewayDirection::Incoming).unwrap(); + let res = freeze_chains( + deps.as_mut().storage, + HashMap::from([(chain.clone(), GatewayDirection::Incoming)]), + ) + .unwrap(); assert_eq!(res.events.len(), 1); assert!(res.events.contains( @@ -818,7 +913,11 @@ mod test { .into() )); - let res = unfreeze_chain(deps.as_mut(), chain.clone(), GatewayDirection::Incoming).unwrap(); + let res = unfreeze_chains( + deps.as_mut().storage, + HashMap::from([(chain.clone(), GatewayDirection::Incoming)]), + ) + .unwrap(); assert_eq!(res.events.len(), 1); assert!(res.events.contains( diff --git a/contracts/router/src/contract/migrations/mod.rs b/contracts/router/src/contract/migrations/mod.rs new file mode 100644 index 000000000..ebcb4aab1 --- /dev/null +++ b/contracts/router/src/contract/migrations/mod.rs @@ -0,0 +1 @@ +pub mod v0_3_3; diff --git a/contracts/router/src/contract/migrations/v0_3_3.rs b/contracts/router/src/contract/migrations/v0_3_3.rs new file mode 100644 index 000000000..86c849b26 --- /dev/null +++ b/contracts/router/src/contract/migrations/v0_3_3.rs @@ -0,0 +1,233 @@ +#![allow(deprecated)] + +use axelar_wasm_std::error::ContractError; +use axelar_wasm_std::{killswitch, permission_control}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, StdResult, Storage}; +use cw_storage_plus::Item; +use router_api::error::Error; + +use crate::contract::CONTRACT_NAME; +use crate::state; + +const BASE_VERSION: &str = "0.3.3"; + +pub fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { + cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; + + set_generalized_permission_control(storage)?; + set_router_state(storage)?; + Ok(()) +} + +#[deprecated(since = "0.3.3", note = "only used during migration")] +#[cw_serde] +struct Config { + pub admin: Addr, + pub governance: Addr, + pub nexus_gateway: Addr, +} + +fn set_generalized_permission_control(storage: &mut dyn Storage) -> Result<(), Error> { + let old_config = CONFIG.load(storage)?; + permission_control::set_admin(storage, &old_config.admin) + .and_then(|_| permission_control::set_governance(storage, &old_config.governance)) + .map_err(Error::from)?; + + let new_config = &state::Config { + nexus_gateway: old_config.nexus_gateway, + }; + state::CONFIG.save(storage, new_config)?; + Ok(()) +} + +fn set_router_state(storage: &mut dyn Storage) -> StdResult<()> { + killswitch::init(storage, killswitch::State::Disengaged) +} + +#[deprecated(since = "0.3.3", note = "only used during migration")] +const CONFIG: Item = Item::new("config"); + +#[cfg(test)] +mod test { + use std::collections::HashMap; + + use axelar_wasm_std::error::ContractError; + use axelar_wasm_std::killswitch; + use axelar_wasm_std::msg_id::MessageIdFormat; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; + use router_api::msg::ExecuteMsg; + + use crate::contract::migrations::v0_3_3; + use crate::contract::migrations::v0_3_3::BASE_VERSION; + use crate::contract::{execute, CONTRACT_NAME}; + use crate::events::RouterInstantiated; + use crate::msg::InstantiateMsg; + use crate::state; + + #[test] + fn migrate_checks_contract_version() { + let mut deps = mock_dependencies(); + let _ = instantiate_0_3_3_contract(deps.as_mut()).unwrap(); + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap(); + + assert!(v0_3_3::migrate(deps.as_mut().storage).is_err()); + + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, BASE_VERSION).unwrap(); + + assert!(v0_3_3::migrate(deps.as_mut().storage).is_ok()); + } + + #[test] + fn config_gets_migrated() { + let mut deps = mock_dependencies(); + let instantiate_msg = instantiate_0_3_3_contract(deps.as_mut()).unwrap(); + + assert!(v0_3_3::CONFIG.load(deps.as_mut().storage).is_ok()); + assert!(state::CONFIG.load(deps.as_mut().storage).is_err()); + + assert!(v0_3_3::migrate(deps.as_mut().storage).is_ok()); + + assert!(v0_3_3::CONFIG.load(deps.as_mut().storage).is_err()); + let config = state::CONFIG.load(deps.as_mut().storage); + assert!(config.is_ok()); + assert!(config.unwrap().nexus_gateway == instantiate_msg.nexus_gateway); + } + + #[test] + fn router_is_enabled() { + let mut deps = mock_dependencies(); + let _ = instantiate_0_3_3_contract(deps.as_mut()).unwrap(); + + assert!(v0_3_3::migrate(deps.as_mut().storage).is_ok()); + + assert!(killswitch::is_contract_active(deps.as_mut().storage)); + } + + #[test] + fn migration() { + let mut deps = mock_dependencies(); + let instantiate_msg = instantiate_0_3_3_contract(deps.as_mut()).unwrap(); + + let msg = ExecuteMsg::RegisterChain { + chain: "chain".parse().unwrap(), + gateway_address: "gateway".parse().unwrap(), + msg_id_format: MessageIdFormat::HexTxHashAndEventIndex, + }; + + // before migration no address should be able to execute this message + + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(&instantiate_msg.admin_address, &[]), + msg.clone(), + ) + .is_err()); + + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(&instantiate_msg.governance_address, &[]), + msg.clone(), + ) + .is_err()); + + assert!(v0_3_3::migrate(&mut deps.storage).is_ok()); + + // after migration only governance should be able to register a chain + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(&instantiate_msg.admin_address, &[]), + msg.clone(), + ) + .is_err()); + + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(&instantiate_msg.governance_address, &[]), + msg.clone(), + ) + .is_ok()); + + // check that both admin and governance permissions are set correctly + + let msg = ExecuteMsg::UnfreezeChains { + chains: HashMap::new(), + }; + + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info("no_privilege", &[]), + msg.clone(), + ) + .is_err()); + + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(instantiate_msg.admin_address.as_str(), &[]), + msg.clone(), + ) + .is_ok()); + + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info(instantiate_msg.governance_address.as_str(), &[]), + msg.clone(), + ) + .is_ok()); + } + + fn instantiate_0_3_3_contract(deps: DepsMut) -> Result { + let admin = "admin"; + let governance = "governance"; + let nexus_gateway = "nexus_gateway"; + + let msg = InstantiateMsg { + nexus_gateway: nexus_gateway.to_string(), + admin_address: admin.to_string(), + governance_address: governance.to_string(), + }; + instantiate(deps, mock_env(), mock_info(admin, &[]), msg.clone())?; + Ok(msg) + } + + #[deprecated(since = "0.3.3", note = "only used to test the migration")] + fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, + ) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, BASE_VERSION)?; + + let admin = deps.api.addr_validate(&msg.admin_address)?; + let governance = deps.api.addr_validate(&msg.governance_address)?; + let nexus_gateway = deps.api.addr_validate(&msg.nexus_gateway)?; + + let config = v0_3_3::Config { + admin: admin.clone(), + governance: governance.clone(), + nexus_gateway: nexus_gateway.clone(), + }; + + v0_3_3::CONFIG + .save(deps.storage, &config) + .expect("must save the config"); + + Ok(Response::new().add_event( + RouterInstantiated { + admin, + governance, + nexus_gateway, + } + .into(), + )) + } +} diff --git a/contracts/router/src/contract/query.rs b/contracts/router/src/contract/query.rs index ea3589854..d10f23aae 100644 --- a/contracts/router/src/contract/query.rs +++ b/contracts/router/src/contract/query.rs @@ -1,6 +1,5 @@ -use cosmwasm_std::{Deps, Order}; +use cosmwasm_std::{Deps, Order, Storage}; use cw_storage_plus::Bound; - use error_stack::{Result, ResultExt}; use router_api::error::Error; use router_api::{ChainEndpoint, ChainName}; @@ -10,9 +9,9 @@ use crate::state::chain_endpoints; // Pagination limits const DEFAULT_LIMIT: u32 = u32::MAX; -pub fn get_chain_info(deps: Deps, chain: ChainName) -> Result { +pub fn chain_info(storage: &dyn Storage, chain: ChainName) -> Result { chain_endpoints() - .may_load(deps.storage, chain) + .may_load(storage, chain) .change_context(Error::StoreFailure)? .ok_or(Error::ChainNotFound.into()) } @@ -38,19 +37,19 @@ pub fn chains( #[cfg(test)] mod test { use axelar_wasm_std::flagset::FlagSet; - use cosmwasm_std::{testing::mock_dependencies, Addr}; + use cosmwasm_std::testing::mock_dependencies; + use cosmwasm_std::Addr; use router_api::error::Error; use router_api::{ChainEndpoint, ChainName, Gateway, GatewayDirection}; + use super::chain_info; use crate::state::chain_endpoints; - use super::get_chain_info; - #[test] fn should_get_chain_info() { let mut deps = mock_dependencies(); - let chain_name: ChainName = "Ethereum".to_string().try_into().unwrap(); - let chain_info = ChainEndpoint { + let chain_name: ChainName = "Ethereum".try_into().unwrap(); + let endpoint = ChainEndpoint { name: chain_name.clone(), gateway: Gateway { address: Addr::unchecked("some gateway"), @@ -60,18 +59,18 @@ mod test { }; assert!(chain_endpoints() - .save(deps.as_mut().storage, chain_name.clone(), &chain_info) + .save(deps.as_mut().storage, chain_name.clone(), &endpoint) .is_ok()); - let result = get_chain_info(deps.as_ref(), chain_name); + let result = chain_info(deps.as_ref().storage, chain_name); assert!(result.is_ok()); - assert_eq!(result.unwrap(), chain_info); + assert_eq!(result.unwrap(), endpoint); } #[test] fn get_non_existent_chain_info() { let deps = mock_dependencies(); - let chain_name: ChainName = "Ethereum".to_string().try_into().unwrap(); - let result = get_chain_info(deps.as_ref(), chain_name); + let chain_name: ChainName = "Ethereum".try_into().unwrap(); + let result = chain_info(deps.as_ref().storage, chain_name); assert!(result.is_err()); assert_eq!(result.unwrap_err().current_context(), &Error::ChainNotFound); } diff --git a/contracts/router/src/events.rs b/contracts/router/src/events.rs index 0dad9c8be..29c0c85a0 100644 --- a/contracts/router/src/events.rs +++ b/contracts/router/src/events.rs @@ -21,14 +21,6 @@ pub struct GatewayUpgraded { pub gateway: GatewayInfo, } -pub struct GatewayFrozen { - pub gateway: GatewayInfo, -} - -pub struct GatewayUnfrozen { - pub gateway: GatewayInfo, -} - pub struct ChainFrozen { pub name: ChainName, pub direction: GatewayDirection, @@ -43,6 +35,9 @@ pub struct MessageRouted { pub msg: Message, } +pub struct RoutingDisabled; +pub struct RoutingEnabled; + impl From for Event { fn from(other: RouterInstantiated) -> Self { Event::new("router_instantiated") @@ -75,17 +70,15 @@ impl From for Event { } } -impl From for Event { - fn from(other: GatewayFrozen) -> Self { - let attrs: Vec = other.gateway.into(); - Event::new("gateway_frozen").add_attributes(attrs) +impl From for Event { + fn from(_: RoutingDisabled) -> Self { + Event::new("routing_disabled") } } -impl From for Event { - fn from(other: GatewayUnfrozen) -> Self { - let attrs: Vec = other.gateway.into(); - Event::new("gateway_unfrozen").add_attributes(attrs) +impl From for Event { + fn from(_: RoutingEnabled) -> Self { + Event::new("routing_enabled") } } diff --git a/contracts/router/src/lib.rs b/contracts/router/src/lib.rs index 4c2b778ae..cdacc8154 100644 --- a/contracts/router/src/lib.rs +++ b/contracts/router/src/lib.rs @@ -1,5 +1,4 @@ pub mod contract; pub mod events; -mod migrations; pub mod msg; pub mod state; diff --git a/contracts/router/src/state.rs b/contracts/router/src/state.rs index 7b26e9584..ca014106c 100644 --- a/contracts/router/src/state.rs +++ b/contracts/router/src/state.rs @@ -1,73 +1,42 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, DepsMut, Order, StdResult, Storage}; +use cosmwasm_std::{Addr, Order, StdResult, Storage}; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, MultiIndex}; -use error_stack::ResultExt; -use mockall::automock; +use error_stack::{report, ResultExt}; use router_api::error::Error; use router_api::{ChainEndpoint, ChainName}; -#[automock] -pub trait Store { - fn save_config(&mut self, config: Config) -> error_stack::Result<(), Error>; - fn load_config(&self) -> error_stack::Result; - fn load_chain_by_gateway( - &self, - gateway: &Addr, - ) -> error_stack::Result, Error>; - fn load_chain_by_chain_name( - &self, - chain_name: &ChainName, - ) -> error_stack::Result, Error>; +pub fn save_config(storage: &mut dyn Storage, config: &Config) -> error_stack::Result<(), Error> { + CONFIG + .save(storage, config) + .change_context(Error::StoreFailure) } -pub struct RouterStore<'a> { - storage: &'a mut dyn Storage, +pub fn load_config(storage: &dyn Storage) -> error_stack::Result { + CONFIG.load(storage).change_context(Error::StoreFailure) } -impl Store for RouterStore<'_> { - fn save_config(&mut self, config: Config) -> error_stack::Result<(), Error> { - CONFIG - .save(self.storage, &config) - .change_context(Error::StoreFailure) - } - - fn load_config(&self) -> error_stack::Result { - CONFIG - .load(self.storage) - .change_context(Error::StoreFailure) - } - - fn load_chain_by_gateway( - &self, - gateway: &Addr, - ) -> error_stack::Result, Error> { - chain_endpoints() - .idx - .gateway - .load_chain_by_gateway(self.storage, gateway) - .change_context(Error::StoreFailure) - } - - fn load_chain_by_chain_name( - &self, - chain_name: &ChainName, - ) -> error_stack::Result, Error> { - chain_endpoints() - .may_load(self.storage, chain_name.clone()) - .change_context(Error::StoreFailure) - } +pub fn load_chain_by_chain_name( + storage: &dyn Storage, + chain_name: &ChainName, +) -> error_stack::Result, Error> { + chain_endpoints() + .may_load(storage, chain_name.clone()) + .change_context(Error::StoreFailure) } - -impl<'a> RouterStore<'a> { - pub fn new(storage: &'a mut dyn Storage) -> Self { - Self { storage } - } +pub fn load_chain_by_gateway( + storage: &dyn Storage, + gateway: &Addr, +) -> error_stack::Result { + chain_endpoints() + .idx + .gateway + .load_chain_by_gateway(storage, gateway) + .change_context(Error::StoreFailure)? + .ok_or(report!(Error::GatewayNotRegistered)) } #[cw_serde] pub struct Config { - pub admin: Addr, - pub governance: Addr, pub nexus_gateway: Addr, } @@ -88,16 +57,7 @@ impl<'a> GatewayIndex<'a> { GatewayIndex(MultiIndex::new(idx_fn, pk_namespace, idx_namespace)) } - #[deprecated(note = "use load_chain_by_gateway instead")] - pub fn find_chain( - &self, - deps: &DepsMut, - contract_address: &Addr, - ) -> StdResult> { - self.load_chain_by_gateway(deps.storage, contract_address) - } - - fn load_chain_by_gateway( + pub fn load_chain_by_gateway( &self, storage: &dyn Storage, contract_address: &Addr, diff --git a/contracts/service-registry/.cargo/config.toml b/contracts/service-registry/.cargo/config.toml new file mode 100644 index 000000000..af5698e58 --- /dev/null +++ b/contracts/service-registry/.cargo/config.toml @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema" diff --git a/contracts/service-registry/Cargo.toml b/contracts/service-registry/Cargo.toml index b362086a0..8cc336b6d 100644 --- a/contracts/service-registry/Cargo.toml +++ b/contracts/service-registry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "service-registry" -version = "0.4.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" description = "Contract handling the service registrations" @@ -33,14 +33,14 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] -axelar-wasm-std = { workspace = true } -axelar-wasm-std-derive = { workspace = true } +axelar-wasm-std = { workspace = true, features = ["derive"] } coordinator = { workspace = true, features = ["library"] } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } error-stack = { workspace = true } +msgs-derive = { workspace = true } report = { workspace = true } router-api = { workspace = true } schemars = "0.8.10" diff --git a/contracts/service-registry/src/bin/schema.rs b/contracts/service-registry/src/bin/schema.rs index 8e54a70e3..85da9dcac 100644 --- a/contracts/service-registry/src/bin/schema.rs +++ b/contracts/service-registry/src/bin/schema.rs @@ -1,5 +1,4 @@ use cosmwasm_schema::write_api; - use service_registry::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { diff --git a/contracts/service-registry/src/contract.rs b/contracts/service-registry/src/contract.rs index 065f6c230..04899b415 100644 --- a/contracts/service-registry/src/contract.rs +++ b/contracts/service-registry/src/contract.rs @@ -1,16 +1,18 @@ +use axelar_wasm_std::{permission_control, FnExt}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ to_json_binary, Addr, BankMsg, Binary, Coin, Deps, DepsMut, Empty, Env, MessageInfo, Order, - QueryRequest, Response, Uint128, WasmQuery, + QueryRequest, Response, Storage, Uint128, WasmQuery, }; +use error_stack::{bail, Report, ResultExt}; use crate::error::ContractError; -use crate::migrations; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{AuthorizationState, BondingState, Config, Service, Verifier, CONFIG, SERVICES}; +use crate::state::{AuthorizationState, BondingState, Service, Verifier, SERVICES, VERIFIERS}; mod execute; +mod migrations; mod query; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); @@ -22,15 +24,12 @@ pub fn instantiate( _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> Result { +) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - CONFIG.save( - deps.storage, - &Config { - governance: deps.api.addr_validate(&msg.governance_account)?, - }, - )?; + let governance = deps.api.addr_validate(&msg.governance_account)?; + permission_control::set_governance(deps.storage, &governance)?; + Ok(Response::default()) } @@ -40,8 +39,8 @@ pub fn execute( env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result { - match msg { +) -> Result { + match msg.ensure_permissions(deps.storage, &info.sender, match_verifier(&info.sender))? { ExecuteMsg::RegisterService { service_name, coordinator_contract, @@ -51,25 +50,21 @@ pub fn execute( bond_denom, unbonding_period_days, description, - } => { - execute::require_governance(&deps, info)?; - execute::register_service( - deps, - service_name, - coordinator_contract, - min_num_verifiers, - max_num_verifiers, - min_verifier_bond, - bond_denom, - unbonding_period_days, - description, - ) - } + } => execute::register_service( + deps, + service_name, + coordinator_contract, + min_num_verifiers, + max_num_verifiers, + min_verifier_bond, + bond_denom, + unbonding_period_days, + description, + ), ExecuteMsg::AuthorizeVerifiers { verifiers, service_name, } => { - execute::require_governance(&deps, info)?; let verifiers = verifiers .into_iter() .map(|veriier| deps.api.addr_validate(&veriier)) @@ -85,7 +80,6 @@ pub fn execute( verifiers, service_name, } => { - execute::require_governance(&deps, info)?; let verifiers = verifiers .into_iter() .map(|verifier| deps.api.addr_validate(&verifier)) @@ -101,7 +95,6 @@ pub fn execute( verifiers, service_name, } => { - execute::require_governance(&deps, info)?; let verifiers = verifiers .into_iter() .map(|verifier| deps.api.addr_validate(&verifier)) @@ -130,29 +123,42 @@ pub fn execute( ExecuteMsg::ClaimStake { service_name } => { execute::claim_stake(deps, env, info, service_name) } + }? + .then(Ok) +} + +fn match_verifier( + sender: &Addr, +) -> impl FnOnce(&dyn Storage, &ExecuteMsg) -> Result> + '_ +{ + |storage: &dyn Storage, msg: &ExecuteMsg| { + let service_name = match msg { + ExecuteMsg::RegisterChainSupport { service_name, .. } + | ExecuteMsg::DeregisterChainSupport { service_name, .. } => service_name, + _ => bail!(permission_control::Error::WrongVariant), + }; + VERIFIERS + .load(storage, (service_name, sender)) + .map(|verifier| verifier.address) + .change_context(permission_control::Error::Unauthorized) } - .map_err(axelar_wasm_std::ContractError::from) } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { match msg { - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name, chain_name, - } => to_json_binary(&query::get_active_verifiers( - deps, - service_name, - chain_name, - )?) - .map_err(|err| err.into()), - QueryMsg::GetVerifier { + } => to_json_binary(&query::active_verifiers(deps, service_name, chain_name)?) + .map_err(|err| err.into()), + QueryMsg::Verifier { service_name, verifier, - } => to_json_binary(&query::get_verifier(deps, service_name, verifier)?) + } => to_json_binary(&query::verifier(deps, service_name, verifier)?) .map_err(|err| err.into()), - QueryMsg::GetService { service_name } => { - to_json_binary(&query::get_service(deps, service_name)?).map_err(|err| err.into()) + QueryMsg::Service { service_name } => { + to_json_binary(&query::service(deps, service_name)?).map_err(|err| err.into()) } } } @@ -162,25 +168,27 @@ pub fn migrate( deps: DepsMut, _env: Env, _msg: Empty, -) -> Result { +) -> Result { + migrations::v0_4_1::migrate(deps.storage)?; + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - migrations::v_0_4::migrate_services(deps.storage).map_err(axelar_wasm_std::ContractError::from) + + Ok(Response::default()) } #[cfg(test)] mod test { use std::str::FromStr; - use cosmwasm_std::{ - coins, from_json, - testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, - CosmosMsg, Empty, OwnedDeps, StdResult, + use axelar_wasm_std::error::err_contains; + use cosmwasm_std::testing::{ + mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, }; + use cosmwasm_std::{coins, from_json, CosmosMsg, Empty, OwnedDeps, StdResult}; use router_api::ChainName; - use crate::state::{WeightedVerifier, VERIFIER_WEIGHT}; - use super::*; + use crate::state::{WeightedVerifier, VERIFIER_WEIGHT}; const GOVERNANCE_ADDRESS: &str = "governance"; const UNAUTHORIZED_ADDRESS: &str = "unauthorized"; @@ -211,13 +219,6 @@ mod test { deps } - pub fn assert_contract_err_strings_equal( - actual: impl Into, - expected: impl Into, - ) { - assert_eq!(actual.into().to_string(), expected.into().to_string()); - } - #[test] fn register_service() { let mut deps = setup(); @@ -255,7 +256,11 @@ mod test { }, ) .unwrap_err(); - assert_contract_err_strings_equal(err, ContractError::Unauthorized); + assert!(err_contains!( + err.report, + permission_control::Error, + permission_control::Error::PermissionDenied { .. } + )); } #[test] @@ -301,7 +306,11 @@ mod test { }, ) .unwrap_err(); - assert_contract_err_strings_equal(err, ContractError::Unauthorized); + assert!(err_contains!( + err.report, + permission_control::Error, + permission_control::Error::PermissionDenied { .. } + )); } #[test] @@ -415,7 +424,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name, }, @@ -442,7 +451,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name: ChainName::from_str("random chain").unwrap(), }, @@ -530,7 +539,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name, }, @@ -622,7 +631,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name: chain, }, @@ -718,7 +727,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name: deregistered_chain, }, @@ -734,7 +743,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name: chain.clone(), }, @@ -837,7 +846,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name, }, @@ -949,7 +958,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name, }, @@ -1038,7 +1047,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name, }, @@ -1112,7 +1121,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name, }, @@ -1160,13 +1169,17 @@ mod test { ) .unwrap_err(); - assert_contract_err_strings_equal(err, ContractError::VerifierNotFound); + assert!(err_contains!( + err.report, + permission_control::Error, + permission_control::Error::WhitelistNotFound { .. } + )); let verifiers: Vec = from_json( query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name, }, @@ -1196,7 +1209,11 @@ mod test { ) .unwrap_err(); - assert_contract_err_strings_equal(err, ContractError::ServiceNotFound); + assert!(err_contains!( + err.report, + permission_control::Error, + permission_control::Error::WhitelistNotFound { .. } + )); } #[test] @@ -1272,7 +1289,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name, }, @@ -1319,7 +1336,11 @@ mod test { ) .unwrap_err(); - assert_contract_err_strings_equal(err, ContractError::WrongDenom); + assert!(err_contains!( + err.report, + ContractError, + ContractError::WrongDenom + )); } #[test] @@ -1374,7 +1395,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name, }, @@ -1448,7 +1469,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name, }, @@ -1522,7 +1543,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name, }, @@ -1629,7 +1650,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name, }, @@ -1739,7 +1760,7 @@ mod test { assert!(res.is_err()); assert_eq!( res.unwrap_err().to_string(), - axelar_wasm_std::ContractError::from(ContractError::InvalidBondingState( + axelar_wasm_std::error::ContractError::from(ContractError::InvalidBondingState( BondingState::Unbonding { unbonded_at: unbond_request_env.block.time, amount: min_verifier_bond, @@ -1775,7 +1796,7 @@ mod test { #[test] #[allow(clippy::cast_possible_truncation)] - fn get_active_verifiers_should_not_return_less_than_min() { + fn active_verifiers_should_not_return_less_than_min() { let mut deps = setup(); let verifiers = vec![Addr::unchecked("verifier1"), Addr::unchecked("verifier2")]; @@ -1818,7 +1839,7 @@ mod test { let res = query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name: chain_name.clone(), }, @@ -1855,7 +1876,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name: chain_name.clone(), }, @@ -1878,7 +1899,7 @@ mod test { let res = query( deps.as_ref(), mock_env(), - QueryMsg::GetActiveVerifiers { + QueryMsg::ActiveVerifiers { service_name: service_name.into(), chain_name: chain_name.clone(), }, @@ -1948,7 +1969,11 @@ mod test { }, ) .unwrap_err(); - assert_contract_err_strings_equal(err, ContractError::VerifierJailed); + assert!(err_contains!( + err.report, + ContractError, + ContractError::VerifierJailed + )); // given a verifier passed unbonding period let verifier2 = Addr::unchecked("verifier-2"); @@ -1984,7 +2009,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetVerifier { + QueryMsg::Verifier { service_name: service_name.into(), verifier: verifier2.to_string(), }, @@ -2030,6 +2055,10 @@ mod test { }, ) .unwrap_err(); - assert_contract_err_strings_equal(err, ContractError::VerifierJailed); + assert!(err_contains!( + err.report, + ContractError, + ContractError::VerifierJailed + )); } } diff --git a/contracts/service-registry/src/contract/execute.rs b/contracts/service-registry/src/contract/execute.rs index 3b6068094..65ee8b542 100644 --- a/contracts/service-registry/src/contract/execute.rs +++ b/contracts/service-registry/src/contract/execute.rs @@ -1,16 +1,7 @@ -use crate::state::{self, Verifier}; -use crate::state::{AuthorizationState, VERIFIERS}; use router_api::ChainName; use super::*; - -pub fn require_governance(deps: &DepsMut, info: MessageInfo) -> Result<(), ContractError> { - let config = CONFIG.load(deps.storage)?; - if config.governance != info.sender { - return Err(ContractError::Unauthorized); - } - Ok(()) -} +use crate::state::{self, AuthorizationState, Verifier, VERIFIERS}; #[allow(clippy::too_many_arguments)] pub fn register_service( @@ -132,10 +123,6 @@ pub fn register_chains_support( .may_load(deps.storage, &service_name)? .ok_or(ContractError::ServiceNotFound)?; - VERIFIERS - .may_load(deps.storage, (&service_name, &info.sender))? - .ok_or(ContractError::VerifierNotFound)?; - state::register_chains_support( deps.storage, service_name.clone(), @@ -156,10 +143,6 @@ pub fn deregister_chains_support( .may_load(deps.storage, &service_name)? .ok_or(ContractError::ServiceNotFound)?; - VERIFIERS - .may_load(deps.storage, (&service_name, &info.sender))? - .ok_or(ContractError::VerifierNotFound)?; - state::deregister_chains_support(deps.storage, service_name.clone(), chains, info.sender)?; Ok(Response::new()) @@ -219,6 +202,6 @@ pub fn claim_stake( denom: service.bond_denom, amount: released_bond, }] - .to_vec(), // TODO: isolate coins + .to_vec(), })) } diff --git a/contracts/service-registry/src/contract/migrations/mod.rs b/contracts/service-registry/src/contract/migrations/mod.rs new file mode 100644 index 000000000..080e953e7 --- /dev/null +++ b/contracts/service-registry/src/contract/migrations/mod.rs @@ -0,0 +1 @@ +pub mod v0_4_1; diff --git a/contracts/service-registry/src/contract/migrations/v0_4_1.rs b/contracts/service-registry/src/contract/migrations/v0_4_1.rs new file mode 100644 index 000000000..b06e781c8 --- /dev/null +++ b/contracts/service-registry/src/contract/migrations/v0_4_1.rs @@ -0,0 +1,117 @@ +#![allow(deprecated)] +use axelar_wasm_std::error::ContractError; +use axelar_wasm_std::permission_control; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, StdResult, Storage}; +use cw_storage_plus::Item; + +use crate::contract::CONTRACT_NAME; + +const BASE_VERSION: &str = "0.4.1"; +pub fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { + cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; + + let config = CONFIG.load(storage)?; + delete_config(storage); + migrate_permission_control(storage, config)?; + + Ok(()) +} + +fn migrate_permission_control(storage: &mut dyn Storage, config: Config) -> StdResult<()> { + permission_control::set_governance(storage, &config.governance) +} + +fn delete_config(storage: &mut dyn Storage) { + CONFIG.remove(storage) +} + +#[cw_serde] +#[deprecated(since = "0.4.1", note = "Only used during migrations")] +pub struct Config { + pub governance: Addr, +} + +#[deprecated(since = "0.4.1", note = "Only used during migrations")] +pub const CONFIG: Item = Item::new("config"); + +#[cfg(test)] +mod tests { + use axelar_wasm_std::permission_control; + use axelar_wasm_std::permission_control::Permission; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response}; + + use crate::contract::migrations::v0_4_1; + use crate::contract::CONTRACT_NAME; + use crate::msg::InstantiateMsg; + + const GOVERNANCE: &str = "governance"; + + #[test] + fn migrate_checks_contract_version() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap(); + + assert!(v0_4_1::migrate(deps.as_mut().storage).is_err()); + + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, v0_4_1::BASE_VERSION) + .unwrap(); + + assert!(v0_4_1::migrate(deps.as_mut().storage).is_ok()); + } + #[test] + fn migrate_to_permission_control() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + assert!(v0_4_1::migrate(deps.as_mut().storage).is_ok()); + + assert!( + permission_control::sender_role(&deps.storage, &Addr::unchecked(GOVERNANCE)) + .unwrap() + .contains(Permission::Governance) + ); + } + + #[test] + fn migrate_deletes_config() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + assert!(v0_4_1::migrate(deps.as_mut().storage).is_ok()); + + assert!(v0_4_1::CONFIG.load(&deps.storage).is_err()) + } + + fn instantiate_contract(deps: DepsMut) { + instantiate( + deps, + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + governance_account: GOVERNANCE.to_string(), + }, + ) + .unwrap(); + } + #[deprecated(since = "0.4.1", note = "Only used to test the migration")] + fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, + ) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, v0_4_1::BASE_VERSION)?; + + v0_4_1::CONFIG.save( + deps.storage, + &v0_4_1::Config { + governance: deps.api.addr_validate(&msg.governance_account)?, + }, + )?; + Ok(Response::default()) + } +} diff --git a/contracts/service-registry/src/contract/query.rs b/contracts/service-registry/src/contract/query.rs index 051f6615b..241bfce10 100644 --- a/contracts/service-registry/src/contract/query.rs +++ b/contracts/service-registry/src/contract/query.rs @@ -1,10 +1,9 @@ use router_api::ChainName; -use crate::state::{WeightedVerifier, VERIFIERS, VERIFIERS_PER_CHAIN, VERIFIER_WEIGHT}; - use super::*; +use crate::state::{WeightedVerifier, VERIFIERS, VERIFIERS_PER_CHAIN, VERIFIER_WEIGHT}; -pub fn get_active_verifiers( +pub fn active_verifiers( deps: Deps, service_name: String, chain_name: ChainName, @@ -37,7 +36,7 @@ pub fn get_active_verifiers( } } -pub fn get_verifier( +pub fn verifier( deps: Deps, service_name: String, verifier: String, @@ -50,7 +49,7 @@ pub fn get_verifier( .ok_or(ContractError::VerifierNotFound) } -pub fn get_service(deps: Deps, service_name: String) -> Result { +pub fn service(deps: Deps, service_name: String) -> Result { SERVICES .may_load(deps.storage, &service_name)? .ok_or(ContractError::ServiceNotFound) diff --git a/contracts/service-registry/src/error.rs b/contracts/service-registry/src/error.rs index ad86e6b44..0c45576d9 100644 --- a/contracts/service-registry/src/error.rs +++ b/contracts/service-registry/src/error.rs @@ -1,5 +1,4 @@ -use axelar_wasm_std::nonempty; -use axelar_wasm_std_derive::IntoContractError; +use axelar_wasm_std::{nonempty, IntoContractError}; use cosmwasm_std::{OverflowError, StdError}; use thiserror::Error; diff --git a/contracts/service-registry/src/helpers.rs b/contracts/service-registry/src/helpers.rs index 7247c3fdf..8ba4bfa8f 100644 --- a/contracts/service-registry/src/helpers.rs +++ b/contracts/service-registry/src/helpers.rs @@ -1,8 +1,7 @@ +use cosmwasm_std::{to_json_binary, Addr, CosmosMsg, StdResult, WasmMsg}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{to_json_binary, Addr, CosmosMsg, StdResult, WasmMsg}; - use crate::msg::ExecuteMsg; /// ServiceRegistry is a wrapper around Addr that provides a lot of helpers diff --git a/contracts/service-registry/src/lib.rs b/contracts/service-registry/src/lib.rs index fad5e4ff9..98208b782 100644 --- a/contracts/service-registry/src/lib.rs +++ b/contracts/service-registry/src/lib.rs @@ -4,6 +4,4 @@ pub mod helpers; pub mod msg; pub mod state; -mod migrations; - pub use crate::error::ContractError; diff --git a/contracts/service-registry/src/migrations/mod.rs b/contracts/service-registry/src/migrations/mod.rs deleted file mode 100644 index 683e59145..000000000 --- a/contracts/service-registry/src/migrations/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod v_0_4; diff --git a/contracts/service-registry/src/migrations/v_0_4.rs b/contracts/service-registry/src/migrations/v_0_4.rs deleted file mode 100644 index 6d86c8966..000000000 --- a/contracts/service-registry/src/migrations/v_0_4.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Migrate the `Service` struct and rename `service_contract` field to `coordinator_contract`. - -use cosmwasm_std::{Order, Response, Storage}; - -use crate::state::{Service, SERVICES}; - -mod v0_3_state { - use cosmwasm_schema::cw_serde; - use cosmwasm_std::{Addr, Uint128}; - use cw_storage_plus::Map; - - #[cw_serde] - pub struct Service { - pub name: String, - pub service_contract: Addr, - pub min_num_verifiers: u16, - pub max_num_verifiers: Option, - pub min_verifier_bond: Uint128, - pub bond_denom: String, - pub unbonding_period_days: u16, - pub description: String, - } - - type ServiceName = str; - pub const SERVICES: Map<&ServiceName, Service> = Map::new("services"); -} - -pub fn migrate_services( - store: &mut dyn Storage, -) -> Result { - v0_3_state::SERVICES - .range(store, None, None, Order::Ascending) - .collect::, _>>()? - .into_iter() - .map(|(service_name, service)| { - let service = Service { - name: service.name, - coordinator_contract: service.service_contract, - min_num_verifiers: service.min_num_verifiers, - max_num_verifiers: service.max_num_verifiers, - min_verifier_bond: service.min_verifier_bond, - bond_denom: service.bond_denom, - unbonding_period_days: service.unbonding_period_days, - description: service.description, - }; - SERVICES.save(store, &service_name, &service) - }) - .collect::, _>>()?; - - Ok(Response::default()) -} - -#[cfg(test)] -mod test { - use super::*; - use cosmwasm_std::{testing::mock_dependencies, Addr, Uint128}; - - #[test] - fn successfully_migrate_services() { - let mut deps = mock_dependencies(); - - let initial_services = vec![ - v0_3_state::Service { - name: "service1".to_string(), - service_contract: Addr::unchecked("service_contract1"), - min_num_verifiers: 5, - max_num_verifiers: Some(10), - min_verifier_bond: Uint128::from(1000u128), - bond_denom: "denom1".to_string(), - unbonding_period_days: 7, - description: "description1".to_string(), - }, - v0_3_state::Service { - name: "service2".to_string(), - service_contract: Addr::unchecked("service_contract2"), - min_num_verifiers: 3, - max_num_verifiers: None, - min_verifier_bond: Uint128::from(2000u128), - bond_denom: "denom2".to_string(), - unbonding_period_days: 14, - description: "description2".to_string(), - }, - ]; - - for service in &initial_services { - v0_3_state::SERVICES - .save(&mut deps.storage, service.name.as_str(), service) - .unwrap(); - } - - migrate_services(&mut deps.storage).unwrap(); - - for service in &initial_services { - let migrated_service: Service = - SERVICES.load(&deps.storage, service.name.as_str()).unwrap(); - - let expected_service = Service { - name: service.name.clone(), - coordinator_contract: service.service_contract.clone(), - min_num_verifiers: service.min_num_verifiers, - max_num_verifiers: service.max_num_verifiers, - min_verifier_bond: service.min_verifier_bond, - bond_denom: service.bond_denom.clone(), - unbonding_period_days: service.unbonding_period_days, - description: service.description.clone(), - }; - - assert_eq!(migrated_service, expected_service); - } - } -} diff --git a/contracts/service-registry/src/msg.rs b/contracts/service-registry/src/msg.rs index 777ee0353..934e07bab 100644 --- a/contracts/service-registry/src/msg.rs +++ b/contracts/service-registry/src/msg.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Uint128}; +use msgs_derive::EnsurePermissions; use router_api::ChainName; #[cw_serde] @@ -8,8 +9,10 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(EnsurePermissions)] pub enum ExecuteMsg { - // Can only be called by governance account + /// Can only be called by governance account + #[permission(Governance)] RegisterService { service_name: String, coordinator_contract: Addr, @@ -20,62 +23,69 @@ pub enum ExecuteMsg { unbonding_period_days: u16, // number of days to wait after starting unbonding before allowed to claim stake description: String, }, - // Authorizes verifiers to join a service. Can only be called by governance account. Verifiers must still bond sufficient stake to participate. + /// Authorizes verifiers to join a service. Can only be called by governance account. Verifiers must still bond sufficient stake to participate. + #[permission(Governance)] AuthorizeVerifiers { verifiers: Vec, service_name: String, }, - // Revoke authorization for specified verifiers. Can only be called by governance account. Verifiers bond remains unchanged + /// Revoke authorization for specified verifiers. Can only be called by governance account. Verifiers bond remains unchanged + #[permission(Governance)] UnauthorizeVerifiers { verifiers: Vec, service_name: String, }, - // Jail verifiers. Can only be called by governance account. Jailed verifiers are not allowed to unbond or claim stake. + /// Jail verifiers. Can only be called by governance account. Jailed verifiers are not allowed to unbond or claim stake. + #[permission(Governance)] JailVerifiers { verifiers: Vec, service_name: String, }, - // Register support for the specified chains. Called by the verifier. + /// Register support for the specified chains. Called by the verifier. + #[permission(Specific(verifier))] RegisterChainSupport { service_name: String, chains: Vec, }, - // Deregister support for the specified chains. Called by the verifier. + /// Deregister support for the specified chains. Called by the verifier. + #[permission(Specific(verifier))] DeregisterChainSupport { service_name: String, chains: Vec, }, - // Locks up any funds sent with the message as stake. Called by the verifier. - BondVerifier { - service_name: String, - }, - // Initiates unbonding of staked funds. Called by the verifier. - UnbondVerifier { - service_name: String, - }, - // Claim previously staked funds that have finished unbonding. Called by the verifier. - ClaimStake { - service_name: String, - }, + /// Locks up any funds sent with the message as stake. Marks the sender as a potential verifier that can be authorized. + #[permission(Any)] + BondVerifier { service_name: String }, + /// Initiates unbonding of staked funds for the sender. + #[permission(Any)] + UnbondVerifier { service_name: String }, + /// Claim previously staked funds that have finished unbonding for the sender. + #[permission(Any)] + ClaimStake { service_name: String }, } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { #[returns(Vec)] - GetActiveVerifiers { + ActiveVerifiers { service_name: String, chain_name: ChainName, }, #[returns(crate::state::Service)] - GetService { service_name: String }, + Service { service_name: String }, #[returns(crate::state::Verifier)] - GetVerifier { + Verifier { service_name: String, verifier: String, }, } + +#[cw_serde] +pub struct MigrateMsg { + pub coordinator_contract: Addr, +} diff --git a/contracts/service-registry/src/state.rs b/contracts/service-registry/src/state.rs index 5dafe177b..974241b53 100644 --- a/contracts/service-registry/src/state.rs +++ b/contracts/service-registry/src/state.rs @@ -1,20 +1,12 @@ +use axelar_wasm_std::nonempty; +use axelar_wasm_std::snapshot::Participant; use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Storage, Timestamp, Uint128}; +use cw_storage_plus::Map; use router_api::ChainName; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Addr, Storage, Timestamp, Uint128}; -use cw_storage_plus::{Item, Map}; - -#[cw_serde] -pub struct Config { - pub governance: Addr, -} - -pub const CONFIG: Item = Item::new("config"); - -use axelar_wasm_std::{nonempty, snapshot::Participant}; - use crate::ContractError; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] @@ -189,9 +181,12 @@ pub fn deregister_chains_support( #[cfg(test)] mod tests { - use super::*; + use std::str::FromStr; + use std::vec; + use cosmwasm_std::testing::mock_dependencies; - use std::{str::FromStr, vec}; + + use super::*; #[test] fn register_single_verifier_chain_single_call_success() { diff --git a/contracts/voting-verifier/.cargo/config b/contracts/voting-verifier/.cargo/config.toml similarity index 100% rename from contracts/voting-verifier/.cargo/config rename to contracts/voting-verifier/.cargo/config.toml diff --git a/contracts/voting-verifier/Cargo.toml b/contracts/voting-verifier/Cargo.toml index 35f139271..c43b428a7 100644 --- a/contracts/voting-verifier/Cargo.toml +++ b/contracts/voting-verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "voting-verifier" -version = "0.5.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" description = "Voting verifier contract" @@ -33,14 +33,15 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] -axelar-wasm-std = { workspace = true } -axelar-wasm-std-derive = { workspace = true } +axelar-wasm-std = { workspace = true, features = ["derive"] } client = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } error-stack = { workspace = true } +itertools = { workspace = true } +msgs-derive = { workspace = true } multisig = { workspace = true, features = ["library"] } report = { workspace = true } rewards = { workspace = true, features = ["library"] } @@ -50,6 +51,7 @@ service-registry = { workspace = true, features = ["library"] } thiserror = { workspace = true } [dev-dependencies] +alloy-primitives = { version = "0.7.7", features = ["getrandom"] } cw-multi-test = "0.15.1" integration-tests = { workspace = true } multisig = { workspace = true, features = ["test", "library"] } diff --git a/contracts/voting-verifier/src/bin/schema.rs b/contracts/voting-verifier/src/bin/schema.rs index 6f6a8eb3f..d6bf17803 100644 --- a/contracts/voting-verifier/src/bin/schema.rs +++ b/contracts/voting-verifier/src/bin/schema.rs @@ -1,5 +1,4 @@ use cosmwasm_schema::write_api; - use voting_verifier::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { diff --git a/contracts/voting-verifier/src/client.rs b/contracts/voting-verifier/src/client.rs index d0ac78f41..f0c107a01 100644 --- a/contracts/voting-verifier/src/client.rs +++ b/contracts/voting-verifier/src/client.rs @@ -1,15 +1,11 @@ +use axelar_wasm_std::voting::{PollId, Vote}; +use axelar_wasm_std::{nonempty, MajorityThreshold, VerificationStatus}; use cosmwasm_std::{Addr, WasmMsg}; use error_stack::ResultExt; - -use axelar_wasm_std::{ - nonempty, - voting::{PollId, Vote}, - MajorityThreshold, VerificationStatus, -}; use multisig::verifier_set::VerifierSet; use router_api::Message; -use crate::msg::{ExecuteMsg, MessageStatus, Poll, QueryMsg}; +use crate::msg::{ExecuteMsg, MessageStatus, PollResponse, QueryMsg}; type Result = error_stack::Result; @@ -31,10 +27,8 @@ pub struct Client<'a> { impl<'a> Client<'a> { pub fn verify_messages(&self, messages: Vec) -> Option { - ignore_empty(messages).map(|messages| { - self.client - .execute(&ExecuteMsg::VerifyMessages { messages }) - }) + ignore_empty(messages) + .map(|messages| self.client.execute(&ExecuteMsg::VerifyMessages(messages))) } pub fn vote(&self, poll_id: PollId, votes: Vec) -> WasmMsg { @@ -62,9 +56,9 @@ impl<'a> Client<'a> { }) } - pub fn poll(&self, poll_id: PollId) -> Result { + pub fn poll(&self, poll_id: PollId) -> Result { self.client - .query(&QueryMsg::GetPoll { poll_id }) + .query(&QueryMsg::Poll { poll_id }) .change_context_lazy(|| Error::QueryVotingVerifier(self.client.address.clone())) } @@ -73,20 +67,20 @@ impl<'a> Client<'a> { [] => Ok(vec![]), _ => self .client - .query(&QueryMsg::GetMessagesStatus { messages }) + .query(&QueryMsg::MessagesStatus(messages)) .change_context_lazy(|| Error::QueryVotingVerifier(self.client.address.clone())), } } pub fn verifier_set_status(&self, new_verifier_set: VerifierSet) -> Result { self.client - .query(&QueryMsg::GetVerifierSetStatus { new_verifier_set }) + .query(&QueryMsg::VerifierSetStatus(new_verifier_set)) .change_context_lazy(|| Error::QueryVotingVerifier(self.client.address.clone())) } pub fn current_threshold(&self) -> Result { self.client - .query(&QueryMsg::GetCurrentThreshold) + .query(&QueryMsg::CurrentThreshold) .change_context_lazy(|| Error::QueryVotingVerifier(self.client.address.clone())) } } @@ -104,22 +98,16 @@ fn ignore_empty(msgs: Vec) -> Option> { mod test { use std::collections::BTreeMap; - use axelar_wasm_std::{ - msg_id::tx_hash_event_index::HexTxHashAndEventIndex, Threshold, VerificationStatus, - }; - use cosmwasm_std::{ - from_json, - testing::{mock_dependencies, mock_env, mock_info, MockQuerier}, - Addr, DepsMut, QuerierWrapper, Uint128, Uint64, WasmQuery, - }; + use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; + use axelar_wasm_std::{Threshold, VerificationStatus}; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockQuerier}; + use cosmwasm_std::{from_json, Addr, DepsMut, QuerierWrapper, Uint128, Uint64, WasmQuery}; use multisig::verifier_set::VerifierSet; use router_api::{CrossChainId, Message}; - use crate::{ - contract::{instantiate, query}, - msg::{InstantiateMsg, MessageStatus, QueryMsg}, - Client, - }; + use crate::contract::{instantiate, query}; + use crate::msg::{InstantiateMsg, MessageStatus, QueryMsg}; + use crate::Client; #[test] fn query_messages_status() { @@ -127,32 +115,32 @@ mod test { let client: Client = client::Client::new(QuerierWrapper::new(&querier), addr).into(); let msg_1 = Message { - cc_id: CrossChainId { - chain: "eth".parse().unwrap(), - id: HexTxHashAndEventIndex { + cc_id: CrossChainId::new( + "eth", + HexTxHashAndEventIndex { tx_hash: [0; 32], event_index: 0, } .to_string() - .parse() - .unwrap(), - }, + .as_str(), + ) + .unwrap(), source_address: "0x1234".parse().unwrap(), destination_address: "0x5678".parse().unwrap(), destination_chain: "eth".parse().unwrap(), payload_hash: [0; 32], }; let msg_2 = Message { - cc_id: CrossChainId { - chain: "eth".parse().unwrap(), - id: HexTxHashAndEventIndex { + cc_id: CrossChainId::new( + "eth", + HexTxHashAndEventIndex { tx_hash: [1; 32], event_index: 0, } .to_string() - .parse() - .unwrap(), - }, + .as_str(), + ) + .unwrap(), source_address: "0x4321".parse().unwrap(), destination_address: "0x8765".parse().unwrap(), destination_chain: "eth".parse().unwrap(), @@ -229,11 +217,12 @@ mod test { .unwrap() .try_into() .unwrap(), - block_expiry: 100, + block_expiry: 100.try_into().unwrap(), confirmation_height: 10, source_chain: "source-chain".parse().unwrap(), - rewards_address: "rewards".to_string(), + rewards_address: "rewards".try_into().unwrap(), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, + address_format: axelar_wasm_std::address_format::AddressFormat::Eip55, }; instantiate(deps, env, info.clone(), msg.clone()).unwrap(); diff --git a/contracts/voting-verifier/src/contract.rs b/contracts/voting-verifier/src/contract.rs index bf2a97d65..b9acc44fd 100644 --- a/contracts/voting-verifier/src/contract.rs +++ b/contracts/voting-verifier/src/contract.rs @@ -1,3 +1,4 @@ +use axelar_wasm_std::permission_control; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -5,9 +6,13 @@ use cosmwasm_std::{ StdResult, }; +use crate::contract::migrations::v0_5_0; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{Config, CONFIG}; -use crate::{execute, query}; + +mod execute; +mod migrations; +mod query; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -18,11 +23,13 @@ pub fn instantiate( _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> Result { +) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + let governance = deps.api.addr_validate(&msg.governance_address)?; + permission_control::set_governance(deps.storage, &governance)?; + let config = Config { - governance: deps.api.addr_validate(&msg.governance_address)?, service_name: msg.service_name, service_registry_contract: deps.api.addr_validate(&msg.service_registry_address)?, source_gateway_address: msg.source_gateway_address, @@ -32,6 +39,7 @@ pub fn instantiate( source_chain: msg.source_chain, rewards_contract: deps.api.addr_validate(&msg.rewards_address)?, msg_id_format: msg.msg_id_format, + address_format: msg.address_format, }; CONFIG.save(deps.storage, &config)?; @@ -45,39 +53,43 @@ pub fn execute( env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::VerifyMessages { messages } => execute::verify_messages(deps, env, messages), - ExecuteMsg::Vote { poll_id, votes } => execute::vote(deps, env, info, poll_id, votes), - ExecuteMsg::EndPoll { poll_id } => execute::end_poll(deps, env, poll_id), +) -> Result { + match msg.ensure_permissions(deps.storage, &info.sender)? { + ExecuteMsg::VerifyMessages(messages) => Ok(execute::verify_messages(deps, env, messages)?), + ExecuteMsg::Vote { poll_id, votes } => Ok(execute::vote(deps, env, info, poll_id, votes)?), + ExecuteMsg::EndPoll { poll_id } => Ok(execute::end_poll(deps, env, poll_id)?), ExecuteMsg::VerifyVerifierSet { message_id, new_verifier_set, - } => execute::verify_verifier_set(deps, env, message_id, new_verifier_set), + } => Ok(execute::verify_verifier_set( + deps, + env, + &message_id, + new_verifier_set, + )?), ExecuteMsg::UpdateVotingThreshold { new_voting_threshold, - } => { - execute::require_governance(&deps, info.sender)?; - execute::update_voting_threshold(deps, new_voting_threshold) - } + } => Ok(execute::update_voting_threshold( + deps, + new_voting_threshold, + )?), } - .map_err(axelar_wasm_std::ContractError::from) } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::GetPoll { poll_id: _ } => { - todo!() + QueryMsg::Poll { poll_id } => { + to_json_binary(&query::poll_response(deps, env.block.height, poll_id)?) } - QueryMsg::GetMessagesStatus { messages } => { - to_json_binary(&query::messages_status(deps, &messages)?) - } - QueryMsg::GetVerifierSetStatus { new_verifier_set } => { - to_json_binary(&query::verifier_set_status(deps, &new_verifier_set)?) + QueryMsg::MessagesStatus(messages) => { + to_json_binary(&query::messages_status(deps, &messages, env.block.height)?) } - QueryMsg::GetCurrentThreshold => to_json_binary(&query::voting_threshold(deps)?), + QueryMsg::VerifierSetStatus(new_verifier_set) => to_json_binary( + &query::verifier_set_status(deps, &new_verifier_set, env.block.height)?, + ), + QueryMsg::CurrentThreshold => to_json_binary(&query::voting_threshold(deps)?), } } @@ -86,44 +98,39 @@ pub fn migrate( deps: DepsMut, _env: Env, _msg: Empty, -) -> Result { - // any version checks should be done before here - - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; +) -> Result { + v0_5_0::migrate(deps.storage)?; Ok(Response::default()) } #[cfg(test)] mod test { - use cosmwasm_std::{ - from_json, - testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, - Addr, Empty, Fraction, OwnedDeps, Uint128, Uint64, WasmQuery, + use axelar_wasm_std::address_format::AddressFormat; + use axelar_wasm_std::msg_id::{ + Base58SolanaTxSignatureAndEventIndex, Base58TxDigestAndEventIndex, HexTxHashAndEventIndex, + MessageIdFormat, }; - use sha3::{Digest, Keccak256}; - + use axelar_wasm_std::voting::Vote; use axelar_wasm_std::{ - msg_id::{ - base_58_event_index::Base58TxDigestAndEventIndex, - tx_hash_event_index::HexTxHashAndEventIndex, MessageIdFormat, - }, - nonempty, - voting::Vote, - MajorityThreshold, Threshold, VerificationStatus, + err_contains, nonempty, MajorityThreshold, Threshold, VerificationStatus, }; - use multisig::{ - key::KeyType, - test::common::{build_verifier_set, ecdsa_test_data}, + use cosmwasm_std::testing::{ + mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, }; + use cosmwasm_std::{from_json, Addr, Empty, Fraction, OwnedDeps, Uint128, Uint64, WasmQuery}; + use multisig::key::KeyType; + use multisig::test::common::{build_verifier_set, ecdsa_test_data}; use router_api::{ChainName, CrossChainId, Message}; use service_registry::state::{ AuthorizationState, BondingState, Verifier, WeightedVerifier, VERIFIER_WEIGHT, }; - - use crate::{error::ContractError, events::TxEventConfirmation, msg::MessageStatus}; + use sha3::{Digest, Keccak256, Keccak512}; use super::*; + use crate::error::ContractError; + use crate::events::TxEventConfirmation; + use crate::msg::MessageStatus; const SENDER: &str = "sender"; const SERVICE_REGISTRY_ADDRESS: &str = "service_registry_address"; @@ -136,17 +143,13 @@ mod test { "source-chain".parse().unwrap() } - fn governance() -> Addr { - Addr::unchecked(GOVERNANCE) - } - fn initial_voting_threshold() -> MajorityThreshold { Threshold::try_from((2, 3)).unwrap().try_into().unwrap() } fn assert_contract_err_strings_equal( - actual: impl Into, - expected: impl Into, + actual: impl Into, + expected: impl Into, ) { assert_eq!(actual.into().to_string(), expected.into().to_string()); } @@ -172,19 +175,25 @@ mod test { ) -> OwnedDeps { let mut deps = mock_dependencies(); - let config = Config { - governance: governance(), - service_name: SERVICE_NAME.parse().unwrap(), - service_registry_contract: Addr::unchecked(SERVICE_REGISTRY_ADDRESS), - source_gateway_address: "source_gateway_address".parse().unwrap(), - voting_threshold: initial_voting_threshold(), - block_expiry: POLL_BLOCK_EXPIRY, - confirmation_height: 100, - source_chain: source_chain(), - rewards_contract: Addr::unchecked(REWARDS_ADDRESS), - msg_id_format: msg_id_format.clone(), - }; - CONFIG.save(deps.as_mut().storage, &config).unwrap(); + instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + governance_address: GOVERNANCE.parse().unwrap(), + service_registry_address: SERVICE_REGISTRY_ADDRESS.parse().unwrap(), + service_name: SERVICE_NAME.parse().unwrap(), + source_gateway_address: "source_gateway_address".parse().unwrap(), + voting_threshold: initial_voting_threshold(), + block_expiry: POLL_BLOCK_EXPIRY.try_into().unwrap(), + confirmation_height: 100, + source_chain: source_chain(), + rewards_address: REWARDS_ADDRESS.parse().unwrap(), + msg_id_format: msg_id_format.clone(), + address_format: AddressFormat::Eip55, + }, + ) + .unwrap(); deps.querier.update_wasm(move |wq| match wq { WasmQuery::Smart { contract_addr, .. } if contract_addr == SERVICE_REGISTRY_ADDRESS => { @@ -208,35 +217,44 @@ mod test { } fn message_id(id: &str, index: u32, msg_id_format: &MessageIdFormat) -> nonempty::String { - let tx_hash = Keccak256::digest(id.as_bytes()).into(); match msg_id_format { MessageIdFormat::HexTxHashAndEventIndex => HexTxHashAndEventIndex { - tx_hash, + tx_hash: Keccak256::digest(id.as_bytes()).into(), event_index: index, } .to_string() .parse() .unwrap(), MessageIdFormat::Base58TxDigestAndEventIndex => Base58TxDigestAndEventIndex { - tx_digest: tx_hash, + tx_digest: Keccak256::digest(id.as_bytes()).into(), event_index: index, } .to_string() .parse() .unwrap(), + MessageIdFormat::Base58SolanaTxSignatureAndEventIndex => { + Base58SolanaTxSignatureAndEventIndex { + raw_signature: Keccak512::digest(id.as_bytes()).into(), + event_index: index, + } + .to_string() + .parse() + .unwrap() + } } } fn messages(len: u32, msg_id_format: &MessageIdFormat) -> Vec { (0..len) .map(|i| Message { - cc_id: CrossChainId { - chain: source_chain(), - id: message_id("id", i, msg_id_format), - }, - source_address: format!("source_address{i}").parse().unwrap(), + cc_id: CrossChainId::new(source_chain(), message_id("id", i, msg_id_format)) + .unwrap(), + source_address: alloy_primitives::Address::random() + .to_string() + .try_into() + .unwrap(), destination_chain: format!("destination-chain{i}").parse().unwrap(), - destination_address: format!("destination_address{i}").parse().unwrap(), + destination_address: format!("destination-address{i}").parse().unwrap(), payload_hash: [0; 32], }) .collect() @@ -256,49 +274,33 @@ mod test { .collect() } - #[test] - fn migrate_sets_contract_version() { - let msg_id_format = MessageIdFormat::HexTxHashAndEventIndex; - let verifiers = verifiers(2); - let mut deps = setup(verifiers.clone(), &msg_id_format); - - migrate(deps.as_mut(), mock_env(), Empty {}).unwrap(); - - let contract_version = cw2::get_contract_version(deps.as_mut().storage).unwrap(); - assert_eq!(contract_version.contract, "voting-verifier"); - assert_eq!(contract_version.version, CONTRACT_VERSION); - } - #[test] fn should_fail_if_messages_are_not_from_same_source() { let msg_id_format = MessageIdFormat::HexTxHashAndEventIndex; let verifiers = verifiers(2); let mut deps = setup(verifiers.clone(), &msg_id_format); - let msg = ExecuteMsg::VerifyMessages { - messages: vec![ - Message { - cc_id: CrossChainId { - chain: source_chain(), - id: message_id("id", 1, &msg_id_format), - }, - source_address: "source_address1".parse().unwrap(), - destination_chain: "destination-chain1".parse().unwrap(), - destination_address: "destination_address1".parse().unwrap(), - payload_hash: [0; 32], - }, - Message { - cc_id: CrossChainId { - chain: "other-chain".parse().unwrap(), - id: message_id("id", 2, &msg_id_format), - }, - source_address: "source_address2".parse().unwrap(), - destination_chain: "destination-chain2".parse().unwrap(), - destination_address: "destination_address2".parse().unwrap(), - payload_hash: [0; 32], - }, - ], - }; + let msg = ExecuteMsg::VerifyMessages(vec![ + Message { + cc_id: CrossChainId::new(source_chain(), message_id("id", 1, &msg_id_format)) + .unwrap(), + source_address: alloy_primitives::Address::random() + .to_string() + .parse() + .unwrap(), + destination_chain: "destination-chain1".parse().unwrap(), + destination_address: "destination-address1".parse().unwrap(), + payload_hash: [0; 32], + }, + Message { + cc_id: CrossChainId::new("other-chain", message_id("id", 2, &msg_id_format)) + .unwrap(), + source_address: "source-address2".parse().unwrap(), + destination_chain: "destination-chain2".parse().unwrap(), + destination_address: "destination-address2".parse().unwrap(), + payload_hash: [0; 32], + }, + ]); let err = execute(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), msg).unwrap_err(); assert_contract_err_strings_equal(err, ContractError::SourceChainMismatch(source_chain())); } @@ -310,13 +312,15 @@ mod test { let mut deps = setup(verifiers.clone(), &msg_id_format); let mut messages = messages(1, &MessageIdFormat::HexTxHashAndEventIndex); - let msg_id = "foobar"; - messages[0].cc_id.id = msg_id.parse().unwrap(); + messages[0].cc_id = CrossChainId::new(source_chain(), "foobar").unwrap(); - let msg = ExecuteMsg::VerifyMessages { messages }; + let msg = ExecuteMsg::VerifyMessages(messages); let err = execute(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), msg).unwrap_err(); - assert_contract_err_strings_equal(err, ContractError::InvalidMessageID(msg_id.to_string())); + assert_contract_err_strings_equal( + err, + ContractError::InvalidMessageID("foobar".to_string()), + ); } #[test] @@ -326,14 +330,12 @@ mod test { let mut deps = setup(verifiers.clone(), &msg_id_format); let messages = messages(1, &MessageIdFormat::Base58TxDigestAndEventIndex); - let msg = ExecuteMsg::VerifyMessages { - messages: messages.clone(), - }; + let msg = ExecuteMsg::VerifyMessages(messages.clone()); let err = execute(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), msg).unwrap_err(); assert_contract_err_strings_equal( err, - ContractError::InvalidMessageID(messages[0].cc_id.id.to_string()), + ContractError::InvalidMessageID(messages[0].cc_id.message_id.to_string()), ); } @@ -344,14 +346,12 @@ mod test { let mut deps = setup(verifiers.clone(), &msg_id_format); let messages = messages(1, &MessageIdFormat::HexTxHashAndEventIndex); - let msg = ExecuteMsg::VerifyMessages { - messages: messages.clone(), - }; + let msg = ExecuteMsg::VerifyMessages(messages.clone()); let err = execute(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), msg).unwrap_err(); assert_contract_err_strings_equal( err, - ContractError::InvalidMessageID(messages[0].cc_id.id.to_string()), + ContractError::InvalidMessageID(messages[0].cc_id.message_id.to_string()), ); } @@ -368,9 +368,9 @@ mod test { deps.as_mut(), mock_env(), mock_info(SENDER, &[]), - ExecuteMsg::VerifyMessages { - messages: messages[0..messages_in_progress].to_vec(), // verify a subset of the messages - }, + ExecuteMsg::VerifyMessages( + messages[0..messages_in_progress].to_vec(), // verify a subset of the messages + ), ) .unwrap(); @@ -378,9 +378,9 @@ mod test { deps.as_mut(), mock_env(), mock_info(SENDER, &[]), - ExecuteMsg::VerifyMessages { - messages: messages.clone(), // verify all messages including the ones from previous execution - }, + ExecuteMsg::VerifyMessages( + messages.clone(), // verify all messages including the ones from previous execution + ), ) .unwrap(); @@ -407,10 +407,7 @@ mod test { .iter() .cloned() .map(|e| { - ( - e, - &axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, - ) + (e, &MessageIdFormat::HexTxHashAndEventIndex) .try_into() .unwrap() }) @@ -426,9 +423,7 @@ mod test { let mut deps = setup(verifiers.clone(), &msg_id_format); let messages = messages(5, &msg_id_format); - let msg = ExecuteMsg::VerifyMessages { - messages: messages.clone(), - }; + let msg = ExecuteMsg::VerifyMessages(messages.clone()); execute( deps.as_mut(), mock_env(), @@ -437,24 +432,12 @@ mod test { ) .unwrap(); - execute( - deps.as_mut(), - mock_env_expired(), - mock_info(SENDER, &[]), - ExecuteMsg::EndPoll { - poll_id: Uint64::one().into(), - }, - ) - .unwrap(); - // confirm it was not verified let status: Vec = from_json( query( deps.as_ref(), - mock_env(), - QueryMsg::GetMessagesStatus { - messages: messages.clone(), - }, + mock_env_expired(), + QueryMsg::MessagesStatus(messages.clone()), ) .unwrap(), ) @@ -465,7 +448,13 @@ mod test { ); // retries same message - let res = execute(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), msg).unwrap(); + let res = execute( + deps.as_mut(), + mock_env_expired(), + mock_info(SENDER, &[]), + msg, + ) + .unwrap(); let actual: Vec = serde_json::from_str( &res.events @@ -488,10 +477,7 @@ mod test { let expected = messages .into_iter() .map(|e| { - ( - e, - &axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, - ) + (e, &MessageIdFormat::HexTxHashAndEventIndex) .try_into() .unwrap() }) @@ -510,9 +496,7 @@ mod test { // 1. First verification - let msg_verify = ExecuteMsg::VerifyMessages { - messages: messages.clone(), - }; + let msg_verify = ExecuteMsg::VerifyMessages(messages.clone()); let res = execute( deps.as_mut(), @@ -566,10 +550,8 @@ mod test { let res: Vec = from_json( query( deps.as_ref(), - mock_env(), - QueryMsg::GetMessagesStatus { - messages: messages.clone(), - }, + mock_env_expired(), + QueryMsg::MessagesStatus(messages.clone()), ) .unwrap(), ) @@ -595,7 +577,7 @@ mod test { let res = execute( deps.as_mut(), - mock_env(), + mock_env_expired(), mock_info(SENDER, &[]), msg_verify, ); @@ -604,10 +586,8 @@ mod test { let res: Vec = from_json( query( deps.as_ref(), - mock_env(), - QueryMsg::GetMessagesStatus { - messages: messages.clone(), - }, + mock_env_expired(), + QueryMsg::MessagesStatus(messages.clone()), ) .unwrap(), ) @@ -638,9 +618,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetMessagesStatus { - messages: messages.clone(), - }, + QueryMsg::MessagesStatus(messages.clone()), ) .unwrap(), ) @@ -664,9 +642,7 @@ mod test { deps.as_mut(), mock_env(), mock_info(SENDER, &[]), - ExecuteMsg::VerifyMessages { - messages: messages.clone(), - }, + ExecuteMsg::VerifyMessages(messages.clone()), ) .unwrap(); @@ -674,9 +650,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetMessagesStatus { - messages: messages.clone(), - }, + QueryMsg::MessagesStatus(messages.clone()), ) .unwrap(), ) @@ -688,7 +662,7 @@ mod test { } #[test] - fn should_query_status_failed_to_verify_when_no_consensus_and_poll_ended() { + fn should_query_status_failed_to_verify_when_no_consensus_and_poll_expired() { let msg_id_format = MessageIdFormat::HexTxHashAndEventIndex; let verifiers = verifiers(2); let mut deps = setup(verifiers.clone(), &msg_id_format); @@ -700,30 +674,15 @@ mod test { deps.as_mut(), mock_env(), mock_info(SENDER, &[]), - ExecuteMsg::VerifyMessages { - messages: messages.clone(), - }, - ) - .unwrap(); - - // end poll - execute( - deps.as_mut(), - mock_env_expired(), - mock_info(SENDER, &[]), - ExecuteMsg::EndPoll { - poll_id: Uint64::one().into(), - }, + ExecuteMsg::VerifyMessages(messages.clone()), ) .unwrap(); let statuses: Vec = from_json( query( deps.as_ref(), - mock_env(), - QueryMsg::GetMessagesStatus { - messages: messages.clone(), - }, + mock_env_expired(), + QueryMsg::MessagesStatus(messages.clone()), ) .unwrap(), ) @@ -764,9 +723,7 @@ mod test { deps.as_mut(), mock_env(), mock_info(SENDER, &[]), - ExecuteMsg::VerifyMessages { - messages: messages.clone(), - }, + ExecuteMsg::VerifyMessages(messages.clone()), ) .unwrap(); @@ -801,9 +758,7 @@ mod test { &query( deps.as_ref(), mock_env(), - QueryMsg::GetMessagesStatus { - messages: messages.clone(), - }, + QueryMsg::MessagesStatus(messages.clone()), ) .unwrap(), ) @@ -830,9 +785,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetVerifierSetStatus { - new_verifier_set: verifier_set.clone(), - }, + QueryMsg::VerifierSetStatus(verifier_set.clone()), ) .unwrap(), ) @@ -882,9 +835,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetVerifierSetStatus { - new_verifier_set: verifier_set.clone(), - }, + QueryMsg::VerifierSetStatus(verifier_set.clone()), ) .unwrap(), ) @@ -937,9 +888,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetVerifierSetStatus { - new_verifier_set: verifier_set.clone(), - }, + QueryMsg::VerifierSetStatus(verifier_set.clone()), ) .unwrap(), ) @@ -992,9 +941,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetVerifierSetStatus { - new_verifier_set: verifier_set.clone(), - }, + QueryMsg::VerifierSetStatus(verifier_set.clone()), ) .unwrap(), ) @@ -1039,9 +986,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetVerifierSetStatus { - new_verifier_set: verifier_set.clone(), - }, + QueryMsg::VerifierSetStatus(verifier_set.clone()), ) .unwrap(), ) @@ -1127,7 +1072,7 @@ mod test { ) .unwrap(); - let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCurrentThreshold).unwrap(); + let res = query(deps.as_ref(), mock_env(), QueryMsg::CurrentThreshold).unwrap(); let threshold: MajorityThreshold = from_json(res).unwrap(); assert_eq!(threshold, new_voting_threshold); @@ -1149,9 +1094,7 @@ mod test { deps.as_mut(), mock_env(), mock_info(SENDER, &[]), - ExecuteMsg::VerifyMessages { - messages: messages.clone(), - }, + ExecuteMsg::VerifyMessages(messages.clone()), ) .unwrap(); @@ -1206,9 +1149,7 @@ mod test { query( deps.as_ref(), mock_env(), - QueryMsg::GetMessagesStatus { - messages: messages.clone(), - }, + QueryMsg::MessagesStatus(messages.clone()), ) .unwrap(), ) @@ -1256,9 +1197,7 @@ mod test { deps.as_mut(), mock_env(), mock_info(SENDER, &[]), - ExecuteMsg::VerifyMessages { - messages: messages.clone(), - }, + ExecuteMsg::VerifyMessages(messages.clone()), ) .unwrap(); @@ -1296,10 +1235,8 @@ mod test { let res: Vec = from_json( query( deps.as_ref(), - mock_env(), - QueryMsg::GetMessagesStatus { - messages: messages.clone(), - }, + mock_env_expired(), + QueryMsg::MessagesStatus(messages.clone()), ) .unwrap(), ) @@ -1324,15 +1261,12 @@ mod test { threshold, Threshold::try_from((2, 3)).unwrap().try_into().unwrap() ); - let votes_to_reach_quorum = 2; - let messages = messages(2, &msg_id_format); + let messages = messages(3, &msg_id_format); // 1. First verification - let msg_verify = ExecuteMsg::VerifyMessages { - messages: messages.clone(), - }; + let msg_verify = ExecuteMsg::VerifyMessages(messages.clone()); let res = execute( deps.as_mut(), @@ -1342,11 +1276,28 @@ mod test { ); assert!(res.is_ok()); - // 2. Verifiers cast votes, but only reach consensus on the first three messages + // 2. Verifiers cast votes + // The first message reaches quorum after 2 votes, + // The second message reaches quorum after 3 votes, + // The third message never reaches quorum verifiers.iter().enumerate().for_each(|(i, verifier)| { let msg = ExecuteMsg::Vote { poll_id: 1u64.into(), - votes: vec![Vote::SucceededOnChain, Vote::NotFound], + votes: vec![ + Vote::SucceededOnChain, + if i % 2 == 0 { + Vote::NotFound + } else { + Vote::SucceededOnChain + }, + if i % 3 == 0 { + Vote::NotFound + } else if i % 3 == 1 { + Vote::SucceededOnChain + } else { + Vote::FailedOnChain + }, + ], }; let res = execute( @@ -1357,63 +1308,103 @@ mod test { ) .unwrap(); - // should emit event when vote causes quorum to be reached - assert_eq!( - res.events.iter().any(|event| event.ty == "quorum_reached"), - i == votes_to_reach_quorum - 1 - ); + let verify_event = + |res: &Response, expected_message: Message, expected_status: VerificationStatus| { + let mut iter = res.events.iter(); + + let event = iter.find(|event| event.ty == "quorum_reached").unwrap(); + + let msg: Message = serde_json::from_str( + &event + .attributes + .iter() + .find(|attr| attr.key == "content") + .unwrap() + .value, + ) + .unwrap(); + assert_eq!(msg, expected_message); + + let status: VerificationStatus = serde_json::from_str( + &event + .attributes + .iter() + .find(|attr| attr.key == "status") + .unwrap() + .value, + ) + .unwrap(); + assert_eq!(status, expected_status); + + let additional_event = iter.find(|event| event.ty == "quorum_reached"); + assert_eq!(additional_event, None); + }; + + if i == 0 { + let event = res.events.iter().find(|event| event.ty == "quorum_reached"); + assert_eq!(event, None); + } - if i == votes_to_reach_quorum - 1 { - let mut iter = res.events.iter(); + if i == 1 { + verify_event( + &res, + messages[0].clone(), + VerificationStatus::SucceededOnSourceChain, + ); + } - let first_event = iter.find(|event| event.ty == "quorum_reached").unwrap(); + if i == 2 { + verify_event( + &res, + messages[1].clone(), + VerificationStatus::NotFoundOnSourceChain, + ); + } + }); + } - let msg: Message = serde_json::from_str( - &first_event - .attributes - .iter() - .find(|attr| attr.key == "content") - .unwrap() - .value, - ) - .unwrap(); - assert_eq!(msg, messages[0]); - - let status: VerificationStatus = serde_json::from_str( - &first_event - .attributes - .iter() - .find(|attr| attr.key == "status") - .unwrap() - .value, - ) - .unwrap(); - assert_eq!(status, VerificationStatus::SucceededOnSourceChain); + #[test] + fn should_fail_if_messages_have_invalid_source_address() { + let msg_id_format = MessageIdFormat::HexTxHashAndEventIndex; + let verifiers = verifiers(2); + let mut deps = setup(verifiers.clone(), &msg_id_format); - let second_event = iter.find(|event| event.ty == "quorum_reached").unwrap(); + let eip55_address = alloy_primitives::Address::random().to_string(); - let msg: Message = serde_json::from_str( - &second_event - .attributes - .iter() - .find(|attr| attr.key == "content") - .unwrap() - .value, - ) - .unwrap(); - assert_eq!(msg, messages[1]); - - let status: VerificationStatus = serde_json::from_str( - &second_event - .attributes - .iter() - .find(|attr| attr.key == "status") - .unwrap() - .value, - ) - .unwrap(); - assert_eq!(status, VerificationStatus::NotFoundOnSourceChain); - } - }); + let cases = vec![ + eip55_address.to_lowercase(), + eip55_address.to_uppercase(), + // mix + eip55_address + .chars() + .enumerate() + .map(|(i, c)| { + if i % 2 == 0 { + c.to_uppercase().next().unwrap() + } else { + c.to_lowercase().next().unwrap() + } + }) + .collect::(), + ]; + + for case in cases { + let mut messages = messages(1, &MessageIdFormat::HexTxHashAndEventIndex); + messages[0].source_address = case.parse().unwrap(); + let msg = ExecuteMsg::VerifyMessages(messages); + let res = execute(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), msg); + assert!(res.is_err_and(|err| err_contains!( + err.report, + ContractError, + ContractError::InvalidSourceAddress { .. } + ))); + } + + // should not fail if address is valid + let mut messages = messages(1, &MessageIdFormat::HexTxHashAndEventIndex); + messages[0].source_address = eip55_address.parse().unwrap(); + let msg = ExecuteMsg::VerifyMessages(messages); + let res = execute(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), msg); + assert!(res.is_ok()); } } diff --git a/contracts/voting-verifier/src/execute.rs b/contracts/voting-verifier/src/contract/execute.rs similarity index 64% rename from contracts/voting-verifier/src/execute.rs rename to contracts/voting-verifier/src/contract/execute.rs index 991a45837..83152fd69 100644 --- a/contracts/voting-verifier/src/execute.rs +++ b/contracts/voting-verifier/src/contract/execute.rs @@ -1,38 +1,29 @@ +use std::collections::HashMap; + +use axelar_wasm_std::address_format::{validate_address, AddressFormat}; +use axelar_wasm_std::utils::TryMapExt; +use axelar_wasm_std::voting::{PollId, PollResults, Vote, WeightedPoll}; +use axelar_wasm_std::{snapshot, MajorityThreshold, VerificationStatus}; use cosmwasm_std::{ - to_json_binary, Addr, Deps, DepsMut, Env, Event, MessageInfo, OverflowError, OverflowOperation, + to_json_binary, Deps, DepsMut, Env, Event, MessageInfo, OverflowError, OverflowOperation, QueryRequest, Response, Storage, WasmMsg, WasmQuery, }; - -use axelar_wasm_std::{ - nonempty, snapshot, - voting::{PollId, PollResults, Vote, WeightedPoll}, - MajorityThreshold, VerificationStatus, -}; - +use error_stack::{report, Report, ResultExt}; +use itertools::Itertools; use multisig::verifier_set::VerifierSet; use router_api::{ChainName, Message}; -use service_registry::{msg::QueryMsg, state::WeightedVerifier}; - -use crate::state::{self, Poll, PollContent}; -use crate::state::{CONFIG, POLLS, POLL_ID}; -use crate::{error::ContractError, query::message_status}; -use crate::{events::QuorumReached, query::verifier_set_status, state::poll_verifier_sets}; -use crate::{ - events::{ - PollEnded, PollMetadata, PollStarted, TxEventConfirmation, VerifierSetConfirmation, Voted, - }, - state::poll_messages, +use service_registry::msg::QueryMsg; +use service_registry::state::WeightedVerifier; + +use crate::contract::query::{message_status, verifier_set_status}; +use crate::error::ContractError; +use crate::events::{ + PollEnded, PollMetadata, PollStarted, QuorumReached, TxEventConfirmation, + VerifierSetConfirmation, Voted, +}; +use crate::state::{ + self, poll_messages, poll_verifier_sets, Poll, PollContent, CONFIG, POLLS, POLL_ID, VOTES, }; - -// TODO: this type of function exists in many contracts. Would be better to implement this -// in one place, and then just include it -pub fn require_governance(deps: &DepsMut, sender: Addr) -> Result<(), ContractError> { - let config = CONFIG.load(deps.storage)?; - if config.governance != sender { - return Err(ContractError::Unauthorized); - } - Ok(()) -} pub fn update_voting_threshold( deps: DepsMut, @@ -48,18 +39,18 @@ pub fn update_voting_threshold( pub fn verify_verifier_set( deps: DepsMut, env: Env, - message_id: nonempty::String, + message_id: &str, new_verifier_set: VerifierSet, ) -> Result { - let status = verifier_set_status(deps.as_ref(), &new_verifier_set)?; + let status = verifier_set_status(deps.as_ref(), &new_verifier_set, env.block.height)?; if status.is_confirmed() { return Ok(Response::new()); } let config = CONFIG.load(deps.storage)?; let snapshot = take_snapshot(deps.as_ref(), &config.source_chain)?; - let participants = snapshot.get_participants(); - let expires_at = calculate_expiration(env.block.height, config.block_expiry)?; + let participants = snapshot.participants(); + let expires_at = calculate_expiration(env.block.height, config.block_expiry.into())?; let poll_id = create_verifier_set_poll(deps.storage, expires_at, snapshot)?; @@ -93,26 +84,22 @@ pub fn verify_messages( deps: DepsMut, env: Env, messages: Vec, -) -> Result { +) -> Result> { if messages.is_empty() { Err(ContractError::EmptyMessages)?; } - let source_chain = CONFIG.load(deps.storage)?.source_chain; + let config = CONFIG.load(deps.storage).map_err(ContractError::from)?; - if messages - .iter() - .any(|message| message.cc_id.chain.ne(&source_chain)) - { - Err(ContractError::SourceChainMismatch(source_chain))?; - } - - let config = CONFIG.load(deps.storage)?; - - let messages = messages - .into_iter() - .map(|message| message_status(deps.as_ref(), &message).map(|status| (status, message))) - .collect::, _>>()?; + let messages = messages.try_map(|message| { + validate_source_chain(message, &config.source_chain) + .and_then(|message| validate_source_address(message, &config.address_format)) + .and_then(|message| { + message_status(deps.as_ref(), &message, env.block.height) + .map(|status| (status, message)) + .map_err(Report::from) + }) + })?; let msgs_to_verify: Vec = messages .into_iter() @@ -130,18 +117,20 @@ pub fn verify_messages( return Ok(Response::new()); } - let snapshot = take_snapshot(deps.as_ref(), &msgs_to_verify[0].cc_id.chain)?; - let participants = snapshot.get_participants(); - let expires_at = calculate_expiration(env.block.height, config.block_expiry)?; + let snapshot = take_snapshot(deps.as_ref(), &config.source_chain)?; + let participants = snapshot.participants(); + let expires_at = calculate_expiration(env.block.height, config.block_expiry.into())?; let id = create_messages_poll(deps.storage, expires_at, snapshot, msgs_to_verify.len())?; for (idx, message) in msgs_to_verify.iter().enumerate() { - poll_messages().save( - deps.storage, - &message.hash(), - &state::PollContent::::new(message.clone(), id, idx), - )?; + poll_messages() + .save( + deps.storage, + &message.hash(), + &state::PollContent::::new(message.clone(), id, idx), + ) + .map_err(ContractError::from)?; } let messages = msgs_to_verify @@ -165,25 +154,25 @@ pub fn verify_messages( )) } -fn get_poll_results(poll: &Poll) -> PollResults { +fn poll_results(poll: &Poll) -> PollResults { match poll { - Poll::Messages(weighted_poll) => weighted_poll.state().results, - Poll::ConfirmVerifierSet(weighted_poll) => weighted_poll.state().results, + Poll::Messages(weighted_poll) => weighted_poll.results(), + Poll::ConfirmVerifierSet(weighted_poll) => weighted_poll.results(), } } fn make_quorum_event( - vote: &Vote, + vote: Option, index_in_poll: u32, poll_id: &PollId, poll: &Poll, deps: &DepsMut, -) -> Result { - let status = match vote { +) -> Result, ContractError> { + let status = vote.map(|vote| match vote { Vote::SucceededOnChain => VerificationStatus::SucceededOnSourceChain, Vote::FailedOnChain => VerificationStatus::FailedOnSourceChain, Vote::NotFound => VerificationStatus::NotFoundOnSourceChain, - }; + }); match poll { Poll::Messages(_) => { @@ -191,25 +180,34 @@ fn make_quorum_event( .idx .load_message(deps.storage, *poll_id, index_in_poll)? .expect("message not found in poll"); - Ok(QuorumReached { - content: msg, - status, - } - .into()) + + Ok(status.map(|status| { + QuorumReached { + content: msg, + status, + poll_id: *poll_id, + } + .into() + })) } Poll::ConfirmVerifierSet(_) => { let verifier_set = poll_verifier_sets() .idx .load_verifier_set(deps.storage, *poll_id)? .expect("verifier set not found in poll"); - Ok(QuorumReached { - content: verifier_set, - status, - } - .into()) + + Ok(status.map(|status| { + QuorumReached { + content: verifier_set, + status, + poll_id: *poll_id, + } + .into() + })) } } } + pub fn vote( deps: DepsMut, env: Env, @@ -221,15 +219,15 @@ pub fn vote( .may_load(deps.storage, poll_id)? .ok_or(ContractError::PollNotFound)?; - let results_before_voting = get_poll_results(&poll); + let results_before_voting = poll_results(&poll); let poll = poll.try_map(|poll| { - poll.cast_vote(env.block.height, &info.sender, votes) + poll.cast_vote(env.block.height, &info.sender, votes.clone()) .map_err(ContractError::from) })?; POLLS.save(deps.storage, poll_id, &poll)?; - let results_after_voting = get_poll_results(&poll); + let results_after_voting = poll_results(&poll); let quorum_events = results_after_voting .difference(results_before_voting) @@ -237,13 +235,14 @@ pub fn vote( .0 .into_iter() .enumerate() - .filter_map(|(idx, vote)| vote.map(|vote| (idx, vote))) .map(|(index_in_poll, vote)| { let idx = u32::try_from(index_in_poll) .expect("the amount of votes should never overflow u32"); - make_quorum_event(&vote, idx, &poll_id, &poll, &deps) + make_quorum_event(vote, idx, &poll_id, &poll, &deps) }) - .collect::, _>>()?; + .collect::>, _>>()?; + + VOTES.save(deps.storage, (poll_id, info.sender.to_string()), &votes)?; Ok(Response::new() .add_event( @@ -253,7 +252,7 @@ pub fn vote( } .into(), ) - .add_events(quorum_events)) + .add_events(quorum_events.into_iter().flatten())) } pub fn end_poll(deps: DepsMut, env: Env, poll_id: PollId) -> Result { @@ -266,8 +265,15 @@ pub fn end_poll(deps: DepsMut, env: Env, poll_id: PollId) -> Result)> = VOTES + .prefix(poll_id) + .range(deps.storage, None, None, cosmwasm_std::Order::Ascending) + .try_collect()?; + let poll_result = match &poll { - Poll::Messages(poll) | Poll::ConfirmVerifierSet(poll) => poll.state(), + Poll::Messages(poll) | Poll::ConfirmVerifierSet(poll) => { + poll.state(HashMap::from_iter(votes)) + } }; // TODO: change rewards contract interface to accept a list of addresses to avoid creating multiple wasm messages @@ -292,6 +298,7 @@ pub fn end_poll(deps: DepsMut, env: Env, poll_id: PollId) -> Result Result Result Config { - Config { - governance, - service_registry_contract: Addr::unchecked("doesn't matter"), - service_name: "validators".to_string().try_into().unwrap(), - source_gateway_address: "0x89e51fA8CA5D66cd220bAed62ED01e8951aa7c40" - .to_string() - .try_into() - .unwrap(), - voting_threshold, - source_chain: "ethereum".to_string().try_into().unwrap(), - block_expiry: 10, - confirmation_height: 2, - rewards_contract: Addr::unchecked("rewards"), - msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, - } +fn validate_source_chain( + message: Message, + source_chain: &ChainName, +) -> Result> { + if message.cc_id.source_chain != *source_chain { + Err(report!(ContractError::SourceChainMismatch( + source_chain.clone() + ))) + } else { + Ok(message) } +} - #[test] - fn require_governance_should_reject_non_governance() { - let mut deps = mock_dependencies(); - let governance = Addr::unchecked("governance"); - CONFIG - .save( - deps.as_mut().storage, - &mock_config( - governance.clone(), - Threshold::try_from((2, 3)).unwrap().try_into().unwrap(), - ), - ) - .unwrap(); - - let res = require_governance(&deps.as_mut(), Addr::unchecked("random")); - assert!(res.is_err()); +fn validate_source_address( + message: Message, + address_format: &AddressFormat, +) -> Result> { + validate_address(&message.source_address, address_format) + .change_context(ContractError::InvalidSourceAddress)?; - let res = require_governance(&deps.as_mut(), governance); - assert!(res.is_ok()); - } + Ok(message) } diff --git a/contracts/voting-verifier/src/contract/migrations/mod.rs b/contracts/voting-verifier/src/contract/migrations/mod.rs new file mode 100644 index 000000000..504cbdb5c --- /dev/null +++ b/contracts/voting-verifier/src/contract/migrations/mod.rs @@ -0,0 +1 @@ +pub mod v0_5_0; diff --git a/contracts/voting-verifier/src/contract/migrations/v0_5_0.rs b/contracts/voting-verifier/src/contract/migrations/v0_5_0.rs new file mode 100644 index 000000000..06085bdcc --- /dev/null +++ b/contracts/voting-verifier/src/contract/migrations/v0_5_0.rs @@ -0,0 +1,266 @@ +#![allow(deprecated)] + +use axelar_wasm_std::address_format::AddressFormat; +use axelar_wasm_std::error::ContractError; +use axelar_wasm_std::msg_id::MessageIdFormat; +use axelar_wasm_std::{nonempty, permission_control, MajorityThreshold}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Attribute, StdResult, Storage}; +use cw_storage_plus::Item; +use router_api::ChainName; + +use crate::contract::{CONTRACT_NAME, CONTRACT_VERSION}; +use crate::state; + +const BASE_VERSION: &str = "0.5.0"; + +pub fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { + cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; + + let config = CONFIG.load(storage)?; + migrate_permission_control(storage, &config.governance)?; + migrate_config(storage, config)?; + + delete_polls(storage); + + cw2::set_contract_version(storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + Ok(()) +} + +fn migrate_config(storage: &mut dyn Storage, config: Config) -> StdResult<()> { + CONFIG.remove(storage); + + let config = state::Config { + service_registry_contract: config.service_registry_contract, + service_name: config.service_name, + source_chain: config.source_chain, + rewards_contract: config.rewards_contract, + block_expiry: config + .block_expiry + .try_into() + .unwrap_or(1.try_into().expect("1 is not zero")), + confirmation_height: config.confirmation_height, + msg_id_format: config.msg_id_format, + source_gateway_address: config.source_gateway_address, + voting_threshold: config.voting_threshold, + address_format: AddressFormat::Eip55, + }; + + state::CONFIG.save(storage, &config) +} + +fn migrate_permission_control(storage: &mut dyn Storage, governance: &Addr) -> StdResult<()> { + permission_control::set_governance(storage, governance) +} + +fn delete_polls(storage: &mut dyn Storage) { + state::POLLS.clear(storage); + state::VOTES.clear(storage); + state::poll_messages().clear(storage); + state::poll_messages().clear(storage); +} + +#[cw_serde] +#[deprecated(since = "0.5.0", note = "only used during migration")] +pub struct Config { + pub governance: Addr, + pub service_registry_contract: Addr, + pub service_name: nonempty::String, + pub source_gateway_address: nonempty::String, + pub voting_threshold: MajorityThreshold, + pub block_expiry: u64, // number of blocks after which a poll expires + pub confirmation_height: u64, + pub source_chain: ChainName, + pub rewards_contract: Addr, + pub msg_id_format: MessageIdFormat, +} +impl From for Vec { + fn from(other: Config) -> Self { + vec![ + ("service_name", other.service_name.to_string()), + ( + "service_registry_contract", + other.service_registry_contract.to_string(), + ), + ( + "source_gateway_address", + other.source_gateway_address.to_string(), + ), + ( + "voting_threshold", + serde_json::to_string(&other.voting_threshold) + .expect("failed to serialize voting_threshold"), + ), + ("block_expiry", other.block_expiry.to_string()), + ("confirmation_height", other.confirmation_height.to_string()), + ] + .into_iter() + .map(Attribute::from) + .collect() + } +} +#[deprecated(since = "0.5.0", note = "only used during migration")] +pub const CONFIG: Item = Item::new("config"); + +#[cfg(test)] +mod tests { + use axelar_wasm_std::msg_id::MessageIdFormat; + use axelar_wasm_std::permission_control::Permission; + use axelar_wasm_std::{nonempty, permission_control, MajorityThreshold, Threshold}; + use cosmwasm_schema::cw_serde; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{Addr, Attribute, DepsMut, Empty, Env, Event, MessageInfo, Response}; + use router_api::ChainName; + + use crate::contract::migrations::v0_5_0; + use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; + use crate::state; + + const GOVERNANCE: &str = "governance"; + + #[test] + fn migrate_checks_contract_version() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap(); + + assert!(v0_5_0::migrate(deps.as_mut().storage).is_err()); + + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, v0_5_0::BASE_VERSION) + .unwrap(); + + assert!(v0_5_0::migrate(deps.as_mut().storage).is_ok()); + } + + #[test] + fn migrate_sets_contract_version() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + migrate(deps.as_mut(), mock_env(), Empty {}).unwrap(); + + let contract_version = cw2::get_contract_version(deps.as_mut().storage).unwrap(); + assert_eq!(contract_version.contract, CONTRACT_NAME); + assert_eq!(contract_version.version, CONTRACT_VERSION); + } + + #[test] + fn config_gets_migrated() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + assert!(v0_5_0::CONFIG.load(deps.as_mut().storage).is_ok()); + assert!(state::CONFIG.load(deps.as_mut().storage).is_err()); + + assert!(v0_5_0::migrate(deps.as_mut().storage).is_ok()); + + assert!(v0_5_0::CONFIG.load(deps.as_mut().storage).is_err()); + assert!(state::CONFIG.load(deps.as_mut().storage).is_ok()); + } + + #[test] + fn permission_control_gets_migrated() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + assert!(v0_5_0::migrate(deps.as_mut().storage).is_ok()); + + assert!(permission_control::sender_role( + deps.as_mut().storage, + &Addr::unchecked(GOVERNANCE) + ) + .unwrap() + .contains(Permission::Governance)); + } + + #[test] + fn state_is_cleared_after_migration() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut()); + + assert!(v0_5_0::migrate(deps.as_mut().storage).is_ok()); + + assert!(state::VOTES.is_empty(deps.as_ref().storage)); + assert!(state::POLLS.is_empty(deps.as_ref().storage)); + assert!(state::poll_messages().is_empty(deps.as_ref().storage)); + assert!(state::poll_verifier_sets().is_empty(deps.as_ref().storage)); + } + + fn instantiate_contract(deps: DepsMut) { + instantiate( + deps, + mock_env(), + mock_info("admin", &[]), + InstantiateMsg { + governance_address: GOVERNANCE.parse().unwrap(), + service_registry_address: "service_registry".parse().unwrap(), + service_name: "service".parse().unwrap(), + source_gateway_address: "source_gateway".parse().unwrap(), + voting_threshold: Threshold::try_from((2u64, 3u64)) + .and_then(MajorityThreshold::try_from) + .unwrap(), + block_expiry: 1, + confirmation_height: 1, + source_chain: "source-chain".parse().unwrap(), + rewards_address: "rewards".to_string(), + msg_id_format: MessageIdFormat::HexTxHashAndEventIndex, + }, + ) + .unwrap(); + } + + #[deprecated(since = "0.5.0", note = "only used to test the migration")] + fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, + ) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, v0_5_0::BASE_VERSION)?; + + let config = v0_5_0::Config { + governance: deps.api.addr_validate(&msg.governance_address)?, + service_name: msg.service_name, + service_registry_contract: deps.api.addr_validate(&msg.service_registry_address)?, + source_gateway_address: msg.source_gateway_address, + voting_threshold: msg.voting_threshold, + block_expiry: msg.block_expiry, + confirmation_height: msg.confirmation_height, + source_chain: msg.source_chain, + rewards_contract: deps.api.addr_validate(&msg.rewards_address)?, + msg_id_format: msg.msg_id_format, + }; + v0_5_0::CONFIG.save(deps.storage, &config)?; + + Ok(Response::new() + .add_event(Event::new("instantiated").add_attributes(>::from(config)))) + } + + #[cw_serde] + #[deprecated(since = "0.5.0", note = "only used to test the migration")] + pub struct InstantiateMsg { + /// Address that can call all messages of unrestricted governance permission level, like UpdateVotingThreshold. + /// It can execute messages that bypasses verification checks to rescue the contract if it got into an otherwise unrecoverable state due to external forces. + /// On mainnet it should match the address of the Cosmos governance module. + pub governance_address: nonempty::String, + /// Service registry contract address on axelar. + pub service_registry_address: nonempty::String, + /// Name of service in the service registry for which verifiers are registered. + pub service_name: nonempty::String, + /// Axelar's gateway contract address on the source chain + pub source_gateway_address: nonempty::String, + /// Threshold of weighted votes required for voting to be considered complete for a particular message + pub voting_threshold: MajorityThreshold, + /// The number of blocks after which a poll expires + pub block_expiry: u64, + /// The number of blocks to wait for on the source chain before considering a transaction final + pub confirmation_height: u64, + /// Name of the source chain + pub source_chain: ChainName, + /// Rewards contract address on axelar. + pub rewards_address: String, + /// Format that incoming messages should use for the id field of CrossChainId + pub msg_id_format: MessageIdFormat, + } +} diff --git a/contracts/voting-verifier/src/query.rs b/contracts/voting-verifier/src/contract/query.rs similarity index 54% rename from contracts/voting-verifier/src/query.rs rename to contracts/voting-verifier/src/contract/query.rs index e387a22d6..4e477503b 100644 --- a/contracts/voting-verifier/src/query.rs +++ b/contracts/voting-verifier/src/contract/query.rs @@ -1,19 +1,12 @@ -use axelar_wasm_std::{ - voting::{PollStatus, Vote}, - MajorityThreshold, VerificationStatus, -}; +use axelar_wasm_std::voting::{PollId, PollStatus, Vote}; +use axelar_wasm_std::{MajorityThreshold, VerificationStatus}; use cosmwasm_std::Deps; use multisig::verifier_set::VerifierSet; use router_api::Message; -use crate::{ - error::ContractError, - state::{poll_messages, poll_verifier_sets, CONFIG}, -}; -use crate::{ - msg::MessageStatus, - state::{self, Poll, PollContent, POLLS}, -}; +use crate::error::ContractError; +use crate::msg::{MessageStatus, PollData, PollResponse}; +use crate::state::{poll_messages, poll_verifier_sets, Poll, PollContent, CONFIG, POLLS}; pub fn voting_threshold(deps: Deps) -> Result { Ok(CONFIG.load(deps.storage)?.voting_threshold) @@ -22,38 +15,91 @@ pub fn voting_threshold(deps: Deps) -> Result pub fn messages_status( deps: Deps, messages: &[Message], + cur_block_height: u64, ) -> Result, ContractError> { messages .iter() .map(|message| { - message_status(deps, message) + message_status(deps, message, cur_block_height) .map(|status| MessageStatus::new(message.to_owned(), status)) }) .collect() } -pub fn message_status(deps: Deps, message: &Message) -> Result { +pub fn message_status( + deps: Deps, + message: &Message, + cur_block_height: u64, +) -> Result { let loaded_poll_content = poll_messages().may_load(deps.storage, &message.hash())?; - Ok(verification_status(deps, loaded_poll_content, message)) + Ok(verification_status( + deps, + loaded_poll_content, + message, + cur_block_height, + )) +} + +pub fn poll_response( + deps: Deps, + current_block_height: u64, + poll_id: PollId, +) -> Result { + let poll = POLLS.load(deps.storage, poll_id)?; + let (data, status) = match &poll { + Poll::Messages(poll) => { + let msgs = poll_messages().idx.load_messages(deps.storage, poll_id)?; + assert_eq!( + poll.tallies.len(), + msgs.len(), + "data inconsistency for number of messages in poll {}", + poll.poll_id + ); + + (PollData::Messages(msgs), poll.status(current_block_height)) + } + Poll::ConfirmVerifierSet(poll) => ( + PollData::VerifierSet( + poll_verifier_sets() + .idx + .load_verifier_set(deps.storage, poll_id)? + .expect("verifier set not found in poll"), + ), + poll.status(current_block_height), + ), + }; + + Ok(PollResponse { + poll: poll.weighted_poll(), + data, + status, + }) } pub fn verifier_set_status( deps: Deps, verifier_set: &VerifierSet, + cur_block_height: u64, ) -> Result { let loaded_poll_content = poll_verifier_sets().may_load( deps.storage, &verifier_set.hash().as_slice().try_into().unwrap(), )?; - Ok(verification_status(deps, loaded_poll_content, verifier_set)) + Ok(verification_status( + deps, + loaded_poll_content, + verifier_set, + cur_block_height, + )) } fn verification_status( deps: Deps, stored_poll_content: Option>, content: &T, + cur_block_height: u64, ) -> VerificationStatus { match stored_poll_content { Some(stored) => { @@ -76,7 +122,9 @@ fn verification_status( Some(Vote::SucceededOnChain) => VerificationStatus::SucceededOnSourceChain, Some(Vote::FailedOnChain) => VerificationStatus::FailedOnSourceChain, Some(Vote::NotFound) => VerificationStatus::NotFoundOnSourceChain, - None if is_finished(&poll) => VerificationStatus::FailedToVerify, + None if voting_completed(&poll, cur_block_height) => { + VerificationStatus::FailedToVerify + } None => VerificationStatus::InProgress, } } @@ -84,40 +132,42 @@ fn verification_status( } } -fn is_finished(poll: &state::Poll) -> bool { +fn voting_completed(poll: &Poll, cur_block_height: u64) -> bool { match poll { - state::Poll::Messages(poll) | state::Poll::ConfirmVerifierSet(poll) => { - poll.status == PollStatus::Finished + Poll::Messages(poll) | Poll::ConfirmVerifierSet(poll) => { + matches!( + poll.status(cur_block_height), + PollStatus::Expired | PollStatus::Finished + ) } } } #[cfg(test)] mod tests { - use axelar_wasm_std::{ - msg_id::tx_hash_event_index::HexTxHashAndEventIndex, - nonempty, - voting::{PollId, Tallies, Vote, WeightedPoll}, - Participant, Snapshot, Threshold, - }; - use cosmwasm_std::{testing::mock_dependencies, Addr, Uint128, Uint64}; + use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; + use axelar_wasm_std::voting::{PollId, Tallies, Vote, WeightedPoll}; + use axelar_wasm_std::{nonempty, Participant, Snapshot, Threshold}; + use cosmwasm_std::testing::{mock_dependencies, mock_env}; + use cosmwasm_std::{Addr, Uint128, Uint64}; + use itertools::Itertools; use router_api::CrossChainId; - use crate::state::PollContent; - use super::*; + use crate::state::PollContent; #[test] fn verification_status_in_progress() { let mut deps = mock_dependencies(); let idx = 0; + let cur_block_height = 100; - let poll = poll(); + let poll = poll(cur_block_height + 10); POLLS .save( deps.as_mut().storage, poll.poll_id, - &state::Poll::Messages(poll.clone()), + &Poll::Messages(poll.clone()), ) .unwrap(); @@ -135,7 +185,7 @@ mod tests { msg.clone(), VerificationStatus::InProgress )], - messages_status(deps.as_ref(), &[msg]).unwrap() + messages_status(deps.as_ref(), &[msg], cur_block_height).unwrap() ); } @@ -143,8 +193,9 @@ mod tests { fn verification_status_verified() { let mut deps = mock_dependencies(); let idx = 0; + let cur_block_height = 100; - let mut poll = poll(); + let mut poll = poll(cur_block_height + 10); poll.tallies[idx] = Tallies::default(); poll.tallies[idx].tally(&Vote::SucceededOnChain, &Uint128::from(5u64)); @@ -152,7 +203,7 @@ mod tests { .save( deps.as_mut().storage, poll.poll_id, - &state::Poll::Messages(poll.clone()), + &Poll::Messages(poll.clone()), ) .unwrap(); @@ -170,7 +221,7 @@ mod tests { msg.clone(), VerificationStatus::SucceededOnSourceChain )], - messages_status(deps.as_ref(), &[msg]).unwrap() + messages_status(deps.as_ref(), &[msg], cur_block_height).unwrap() ); } @@ -178,15 +229,17 @@ mod tests { fn verification_status_failed_to_verify() { let mut deps = mock_dependencies(); let idx = 0; + let cur_block_height = 100; + let poll_duration = 10; + let expires_at = cur_block_height + poll_duration; - let mut poll = poll(); - poll.status = PollStatus::Finished; + let poll = poll(expires_at); POLLS .save( deps.as_mut().storage, poll.poll_id, - &state::Poll::Messages(poll.clone()), + &Poll::Messages(poll.clone()), ) .unwrap(); @@ -204,7 +257,7 @@ mod tests { msg.clone(), VerificationStatus::FailedToVerify )], - messages_status(deps.as_ref(), &[msg]).unwrap() + messages_status(deps.as_ref(), &[msg], expires_at).unwrap() ); } @@ -215,30 +268,65 @@ mod tests { assert_eq!( vec![MessageStatus::new(msg.clone(), VerificationStatus::Unknown)], - messages_status(deps.as_ref(), &[msg]).unwrap() + messages_status(deps.as_ref(), &[msg], 0).unwrap() + ); + } + + #[test] + #[allow(clippy::cast_possible_truncation)] + fn poll_response() { + let mut deps = mock_dependencies(); + + let poll = poll(1); + POLLS + .save( + deps.as_mut().storage, + poll.poll_id, + &Poll::Messages(poll.clone()), + ) + .unwrap(); + + let messages = (0..poll.poll_size as u32).map(message); + messages.clone().enumerate().for_each(|(idx, msg)| { + poll_messages() + .save( + deps.as_mut().storage, + &msg.hash(), + &PollContent::::new(msg, poll.poll_id, idx), + ) + .unwrap() + }); + + assert_eq!( + PollResponse { + poll: poll.clone(), + data: PollData::Messages(messages.collect_vec()), + status: PollStatus::Expired + }, + super::poll_response(deps.as_ref(), mock_env().block.height, poll.poll_id).unwrap() ); } fn message(id: u32) -> Message { Message { - cc_id: CrossChainId { - chain: "source-chain".parse().unwrap(), - id: HexTxHashAndEventIndex { + cc_id: CrossChainId::new( + "source-chain", + HexTxHashAndEventIndex { tx_hash: [0; 32], event_index: id, } .to_string() - .try_into() - .unwrap(), - }, - source_address: format!("source_address{id}").parse().unwrap(), + .as_str(), + ) + .unwrap(), + source_address: format!("source-address{id}").parse().unwrap(), destination_chain: format!("destination-chain{id}").parse().unwrap(), - destination_address: format!("destination_address{id}").parse().unwrap(), + destination_address: format!("destination-address{id}").parse().unwrap(), payload_hash: [0; 32], } } - pub fn poll() -> WeightedPoll { + pub fn poll(expires_at: u64) -> WeightedPoll { let participants: nonempty::Vec = vec!["addr1", "addr2", "addr3"] .into_iter() .map(|participant| Participant { @@ -255,6 +343,6 @@ mod tests { let snapshot = Snapshot::new(threshold.try_into().unwrap(), participants); - WeightedPoll::new(PollId::from(Uint64::one()), snapshot, 0, 5) + WeightedPoll::new(PollId::from(Uint64::one()), snapshot, expires_at, 5) } } diff --git a/contracts/voting-verifier/src/error.rs b/contracts/voting-verifier/src/error.rs index 1aec7e088..0945232f6 100644 --- a/contracts/voting-verifier/src/error.rs +++ b/contracts/voting-verifier/src/error.rs @@ -1,5 +1,4 @@ -use axelar_wasm_std::{nonempty, voting}; -use axelar_wasm_std_derive::IntoContractError; +use axelar_wasm_std::{nonempty, voting, IntoContractError}; use cosmwasm_std::{OverflowError, StdError}; use router_api::ChainName; use service_registry; @@ -42,6 +41,9 @@ pub enum ContractError { #[error("poll results have different length")] PollResultsLengthUnequal, + + #[error("invalid source address")] + InvalidSourceAddress, } impl From for StdError { diff --git a/contracts/voting-verifier/src/events.rs b/contracts/voting-verifier/src/events.rs index c2d0a9436..484171597 100644 --- a/contracts/voting-verifier/src/events.rs +++ b/contracts/voting-verifier/src/events.rs @@ -1,14 +1,14 @@ use std::str::FromStr; use std::vec::Vec; -use axelar_wasm_std::msg_id::base_58_event_index::Base58TxDigestAndEventIndex; -use axelar_wasm_std::msg_id::tx_hash_event_index::HexTxHashAndEventIndex; -use axelar_wasm_std::msg_id::MessageIdFormat; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Attribute, Event}; - +use axelar_wasm_std::msg_id::{ + Base58SolanaTxSignatureAndEventIndex, Base58TxDigestAndEventIndex, HexTxHashAndEventIndex, + MessageIdFormat, +}; use axelar_wasm_std::voting::{PollId, Vote}; use axelar_wasm_std::{nonempty, VerificationStatus}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Attribute, Event}; use multisig::verifier_set::VerifierSet; use router_api::{Address, ChainName, Message}; @@ -17,23 +17,44 @@ use crate::state::Config; impl From for Vec { fn from(other: Config) -> Self { + // destructuring the Config struct so changes to the fields don't go unnoticed + let Config { + service_name, + service_registry_contract, + source_gateway_address, + voting_threshold, + block_expiry, + confirmation_height, + source_chain, + rewards_contract, + msg_id_format, + address_format, + } = other; + vec![ - ("service_name", other.service_name.to_string()), + ("service_name", service_name.to_string()), ( "service_registry_contract", - other.service_registry_contract.to_string(), - ), - ( - "source_gateway_address", - other.source_gateway_address.to_string(), + service_registry_contract.to_string(), ), + ("source_gateway_address", source_gateway_address.to_string()), ( "voting_threshold", - serde_json::to_string(&other.voting_threshold) + serde_json::to_string(&voting_threshold) .expect("failed to serialize voting_threshold"), ), - ("block_expiry", other.block_expiry.to_string()), - ("confirmation_height", other.confirmation_height.to_string()), + ("block_expiry", block_expiry.to_string()), + ("confirmation_height", confirmation_height.to_string()), + ("source_chain", source_chain.to_string()), + ("rewards_contract", rewards_contract.to_string()), + ( + "msg_id_format", + serde_json::to_string(&msg_id_format).expect("failed to serialize msg_id_format"), + ), + ( + "address_format", + serde_json::to_string(&address_format).expect("failed to serialize address_format"), + ), ] .into_iter() .map(Attribute::from) @@ -122,27 +143,33 @@ pub struct VerifierSetConfirmation { /// If parsing is successful, returns (tx_id, event_index). Otherwise returns ContractError::InvalidMessageID fn parse_message_id( - message_id: nonempty::String, + message_id: &str, msg_id_format: &MessageIdFormat, ) -> Result<(nonempty::String, u32), ContractError> { match msg_id_format { MessageIdFormat::Base58TxDigestAndEventIndex => { - let id = Base58TxDigestAndEventIndex::from_str(&message_id) - .map_err(|_| ContractError::InvalidMessageID(message_id.into()))?; + let id = Base58TxDigestAndEventIndex::from_str(message_id) + .map_err(|_| ContractError::InvalidMessageID(message_id.to_string()))?; Ok((id.tx_digest_as_base58(), id.event_index)) } MessageIdFormat::HexTxHashAndEventIndex => { - let id = HexTxHashAndEventIndex::from_str(&message_id) - .map_err(|_| ContractError::InvalidMessageID(message_id.into()))?; + let id = HexTxHashAndEventIndex::from_str(message_id) + .map_err(|_| ContractError::InvalidMessageID(message_id.to_string()))?; Ok((id.tx_hash_as_hex(), id.event_index)) } + MessageIdFormat::Base58SolanaTxSignatureAndEventIndex => { + let id = Base58SolanaTxSignatureAndEventIndex::from_str(message_id) + .map_err(|_| ContractError::InvalidMessageID(message_id.to_string()))?; + + Ok((id.signature_as_base58(), id.event_index)) + } } } impl VerifierSetConfirmation { pub fn new( - message_id: nonempty::String, + message_id: &str, msg_id_format: MessageIdFormat, verifier_set: VerifierSet, ) -> Result { @@ -173,7 +200,7 @@ pub struct TxEventConfirmation { impl TryFrom<(Message, &MessageIdFormat)> for TxEventConfirmation { type Error = ContractError; fn try_from((msg, msg_id_format): (Message, &MessageIdFormat)) -> Result { - let (tx_id, event_index) = parse_message_id(msg.cc_id.id, msg_id_format)?; + let (tx_id, event_index) = parse_message_id(&msg.cc_id.message_id, msg_id_format)?; Ok(TxEventConfirmation { tx_id, @@ -204,6 +231,7 @@ impl From for Event { pub struct PollEnded { pub poll_id: PollId, + pub source_chain: ChainName, pub results: Vec>, } @@ -214,6 +242,11 @@ impl From for Event { "poll_id", serde_json::to_string(&other.poll_id).expect("failed to serialize poll_id"), ) + .add_attribute( + "source_chain", + serde_json::to_string(&other.source_chain) + .expect("failed to serialize source_chain"), + ) .add_attribute( "results", serde_json::to_string(&other.results).expect("failed to serialize results"), @@ -224,6 +257,7 @@ impl From for Event { pub struct QuorumReached { pub content: T, pub status: VerificationStatus, + pub poll_id: PollId, } impl From> for Event @@ -240,6 +274,10 @@ where "status", serde_json::to_string(&value.status).expect("failed to serialize status"), ) + .add_attribute( + "poll_id", + serde_json::to_string(&value.poll_id).expect("failed to serialize poll_id"), + ) } } @@ -247,13 +285,10 @@ where mod test { use std::collections::BTreeMap; - use axelar_wasm_std::{ - msg_id::{ - base_58_event_index::Base58TxDigestAndEventIndex, - tx_hash_event_index::HexTxHashAndEventIndex, MessageIdFormat, - }, - nonempty, + use axelar_wasm_std::msg_id::{ + Base58TxDigestAndEventIndex, HexTxHashAndEventIndex, MessageIdFormat, }; + use axelar_wasm_std::nonempty; use cosmwasm_std::Uint128; use multisig::verifier_set::VerifierSet; use router_api::{CrossChainId, Message}; @@ -270,11 +305,8 @@ mod test { fn generate_msg(msg_id: nonempty::String) -> Message { Message { - cc_id: CrossChainId { - chain: "source-chain".parse().unwrap(), - id: msg_id, - }, - source_address: "source_address".parse().unwrap(), + cc_id: CrossChainId::new("source-chain", msg_id).unwrap(), + source_address: "source-address".parse().unwrap(), destination_chain: "destination-chain".parse().unwrap(), destination_address: "destination-address".parse().unwrap(), payload_hash: [0; 32], @@ -359,7 +391,7 @@ mod test { created_at: 1, }; let event = VerifierSetConfirmation::new( - msg_id.to_string().parse().unwrap(), + &msg_id.to_string(), MessageIdFormat::HexTxHashAndEventIndex, verifier_set.clone(), ) @@ -382,7 +414,7 @@ mod test { created_at: 1, }; let event = VerifierSetConfirmation::new( - msg_id.to_string().parse().unwrap(), + &msg_id.to_string(), MessageIdFormat::Base58TxDigestAndEventIndex, verifier_set.clone(), ) @@ -403,7 +435,7 @@ mod test { }; let event = VerifierSetConfirmation::new( - msg_id.to_string().parse().unwrap(), + msg_id, MessageIdFormat::Base58TxDigestAndEventIndex, verifier_set, ); @@ -423,7 +455,7 @@ mod test { }; let event = VerifierSetConfirmation::new( - msg_id.to_string().parse().unwrap(), + &msg_id.to_string(), MessageIdFormat::Base58TxDigestAndEventIndex, verifier_set, ); diff --git a/contracts/voting-verifier/src/lib.rs b/contracts/voting-verifier/src/lib.rs index 20ce222dd..f2ab17a66 100644 --- a/contracts/voting-verifier/src/lib.rs +++ b/contracts/voting-verifier/src/lib.rs @@ -4,8 +4,5 @@ pub use client::Client; pub mod contract; pub mod error; pub mod events; -pub mod execute; -mod migrations; pub mod msg; -pub mod query; pub mod state; diff --git a/contracts/voting-verifier/src/migrations/mod.rs b/contracts/voting-verifier/src/migrations/mod.rs deleted file mode 100644 index 8b1378917..000000000 --- a/contracts/voting-verifier/src/migrations/mod.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/contracts/voting-verifier/src/msg.rs b/contracts/voting-verifier/src/msg.rs index 3e25cd51d..dc6d79455 100644 --- a/contracts/voting-verifier/src/msg.rs +++ b/contracts/voting-verifier/src/msg.rs @@ -1,11 +1,9 @@ +use axelar_wasm_std::address_format::AddressFormat; +use axelar_wasm_std::msg_id::MessageIdFormat; +use axelar_wasm_std::voting::{PollId, PollStatus, Vote, WeightedPoll}; +use axelar_wasm_std::{nonempty, MajorityThreshold, VerificationStatus}; use cosmwasm_schema::{cw_serde, QueryResponses}; - -use axelar_wasm_std::{ - msg_id::MessageIdFormat, - nonempty, - voting::{PollId, Vote}, - MajorityThreshold, VerificationStatus, -}; +use msgs_derive::EnsurePermissions; use multisig::verifier_set::VerifierSet; use router_api::{ChainName, Message}; @@ -24,69 +22,75 @@ pub struct InstantiateMsg { /// Threshold of weighted votes required for voting to be considered complete for a particular message pub voting_threshold: MajorityThreshold, /// The number of blocks after which a poll expires - pub block_expiry: u64, + pub block_expiry: nonempty::Uint64, /// The number of blocks to wait for on the source chain before considering a transaction final pub confirmation_height: u64, /// Name of the source chain pub source_chain: ChainName, /// Rewards contract address on axelar. - pub rewards_address: String, + pub rewards_address: nonempty::String, /// Format that incoming messages should use for the id field of CrossChainId pub msg_id_format: MessageIdFormat, + pub address_format: AddressFormat, } #[cw_serde] +#[derive(EnsurePermissions)] pub enum ExecuteMsg { // Computes the results of a poll // For all verified messages, calls MessagesVerified on the verifier - EndPoll { - poll_id: PollId, - }, + #[permission(Any)] + EndPoll { poll_id: PollId }, // Casts votes for specified poll - Vote { - poll_id: PollId, - votes: Vec, - }, + #[permission(Any)] + Vote { poll_id: PollId, votes: Vec }, // returns a vector of true/false values, indicating current verification status for each message // starts a poll for any not yet verified messages - VerifyMessages { - messages: Vec, - }, + #[permission(Any)] + VerifyMessages(Vec), // Starts a poll to confirm a verifier set update on the external gateway + #[permission(Any)] VerifyVerifierSet { message_id: nonempty::String, new_verifier_set: VerifierSet, }, // Update the threshold used for new polls. Callable only by governance + #[permission(Governance)] UpdateVotingThreshold { new_voting_threshold: MajorityThreshold, }, } #[cw_serde] -pub struct Poll { - poll_id: PollId, - messages: Vec, +pub enum PollData { + Messages(Vec), + VerifierSet(VerifierSet), +} +#[cw_serde] +pub struct PollResponse { + pub poll: WeightedPoll, + pub data: PollData, + pub status: PollStatus, } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - #[returns(Poll)] - GetPoll { poll_id: PollId }, + #[returns(PollResponse)] + Poll { poll_id: PollId }, #[returns(Vec)] - GetMessagesStatus { messages: Vec }, + MessagesStatus(Vec), #[returns(VerificationStatus)] - GetVerifierSetStatus { new_verifier_set: VerifierSet }, + VerifierSetStatus(VerifierSet), #[returns(MajorityThreshold)] - GetCurrentThreshold, + CurrentThreshold, } #[cw_serde] diff --git a/contracts/voting-verifier/src/state.rs b/contracts/voting-verifier/src/state.rs index d6785ea2f..471dc0ab0 100644 --- a/contracts/voting-verifier/src/state.rs +++ b/contracts/voting-verifier/src/state.rs @@ -1,15 +1,11 @@ +use axelar_wasm_std::address_format::AddressFormat; +use axelar_wasm_std::hash::Hash; +use axelar_wasm_std::msg_id::MessageIdFormat; +use axelar_wasm_std::voting::{PollId, Vote, WeightedPoll}; +use axelar_wasm_std::{counter, nonempty, MajorityThreshold}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Order, StdResult, Storage}; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; - -use axelar_wasm_std::{ - counter, - hash::Hash, - msg_id::MessageIdFormat, - nonempty, - voting::{PollId, WeightedPoll}, - MajorityThreshold, -}; use multisig::verifier_set::VerifierSet; use router_api::{ChainName, Message}; @@ -17,16 +13,16 @@ use crate::error::ContractError; #[cw_serde] pub struct Config { - pub governance: Addr, pub service_registry_contract: Addr, pub service_name: nonempty::String, pub source_gateway_address: nonempty::String, pub voting_threshold: MajorityThreshold, - pub block_expiry: u64, // number of blocks after which a poll expires + pub block_expiry: nonempty::Uint64, // number of blocks after which a poll expires pub confirmation_height: u64, pub source_chain: ChainName, pub rewards_contract: Addr, pub msg_id_format: MessageIdFormat, + pub address_format: AddressFormat, } #[cw_serde] @@ -46,6 +42,13 @@ impl Poll { Poll::ConfirmVerifierSet(poll) => Ok(Poll::ConfirmVerifierSet(func(poll)?)), } } + + pub fn weighted_poll(self) -> WeightedPoll { + match self { + Poll::Messages(poll) => poll, + Poll::ConfirmVerifierSet(poll) => poll, + } + } } #[cw_serde] @@ -79,6 +82,9 @@ pub const POLL_ID: counter::Counter = counter::Counter::new("poll_id"); pub const POLLS: Map = Map::new("polls"); +type VerifierAddr = String; +pub const VOTES: Map<(PollId, VerifierAddr), Vec> = Map::new("votes"); + pub const CONFIG: Item = Item::new("config"); /// A multi-index that indexes a message by (PollID, index in poll) pair. The primary key of the underlying @@ -113,6 +119,16 @@ impl<'a> PollMessagesIndex<'a> { _ => panic!("More than one message for poll_id and index_in_poll"), } } + + pub fn load_messages(&self, storage: &dyn Storage, poll_id: PollId) -> StdResult> { + poll_messages() + .idx + .0 + .sub_prefix(poll_id.to_string()) + .range(storage, None, None, Order::Ascending) + .map(|item| item.map(|(_, poll_content)| poll_content.content)) + .collect::>>() + } } const POLL_MESSAGES_PKEY_NAMESPACE: &str = "poll_messages"; diff --git a/doc/README.md b/doc/README.md index 88a313bee..4e03fe37c 100644 --- a/doc/README.md +++ b/doc/README.md @@ -33,4 +33,4 @@ mdbook serve doc --open ## Contributing Information about how to contribute to the documentation can be found in the documentation -chapter [here](http://localhost:3000/contributing/documentation.html) +chapter [here](src/contributing/documentation.md) diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 1f3cfc509..e9229e3e2 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -15,8 +15,8 @@ ## Message Access Requirements -[Amplifier Access Control](message_access.md) +- [Amplifier Access Control](message_access.md) # Contributing -[Documentation](contributing/documentation.md) \ No newline at end of file +- [Documentation](contributing/documentation.md) \ No newline at end of file diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 4ab645022..09ca8a18a 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "integration-tests" -version = "0.1.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" description = "Amplifier Integration Tests" @@ -48,7 +48,7 @@ serde = { workspace = true } serde_json = { workspace = true } service-registry = { workspace = true } sha3 = { workspace = true } -tofn = { git = "https://github.com/axelarnetwork/tofn.git", branch = "update-deps" } +tofn = { workspace = true } voting-verifier = { workspace = true } [lints] diff --git a/integration-tests/src/contract.rs b/integration-tests/src/contract.rs index 4d2cea980..487ca9ca2 100644 --- a/integration-tests/src/contract.rs +++ b/integration-tests/src/contract.rs @@ -22,7 +22,7 @@ pub trait Contract { app: &mut App, caller: Addr, execute_message: &Self::ExMsg, - ) -> Result + ) -> Result where Self::ExMsg: Serialize, Self::ExMsg: std::fmt::Debug, @@ -36,7 +36,7 @@ pub trait Contract { caller: Addr, execute_message: &Self::ExMsg, funds: &[Coin], - ) -> Result + ) -> Result where Self::ExMsg: Serialize, Self::ExMsg: std::fmt::Debug, @@ -49,7 +49,7 @@ pub trait Contract { ) .map_err(|err| { report!(err - .downcast::() + .downcast::() .unwrap_or_else(|err| err.downcast::().unwrap().into())) }) } diff --git a/integration-tests/src/coordinator_contract.rs b/integration-tests/src/coordinator_contract.rs index 7b178253f..c742492c1 100644 --- a/integration-tests/src/coordinator_contract.rs +++ b/integration-tests/src/coordinator_contract.rs @@ -1,8 +1,9 @@ -use crate::contract::Contract; use coordinator::contract::{execute, instantiate, query}; use cosmwasm_std::Addr; use cw_multi_test::{App, ContractWrapper, Executor}; +use crate::contract::Contract; + #[derive(Clone)] pub struct CoordinatorContract { pub contract_addr: Addr, diff --git a/integration-tests/src/gateway_contract.rs b/integration-tests/src/gateway_contract.rs index 7ed4b8585..73ec11b32 100644 --- a/integration-tests/src/gateway_contract.rs +++ b/integration-tests/src/gateway_contract.rs @@ -1,7 +1,8 @@ -use crate::contract::Contract; use cosmwasm_std::Addr; use cw_multi_test::{App, ContractWrapper, Executor}; +use crate::contract::Contract; + #[derive(Clone)] pub struct GatewayContract { pub contract_addr: Addr, diff --git a/integration-tests/src/multisig_contract.rs b/integration-tests/src/multisig_contract.rs index 63d35b0d0..ef7bbefc4 100644 --- a/integration-tests/src/multisig_contract.rs +++ b/integration-tests/src/multisig_contract.rs @@ -1,7 +1,9 @@ -use crate::contract::Contract; +use axelar_wasm_std::nonempty; use cosmwasm_std::Addr; use cw_multi_test::{App, ContractWrapper, Executor}; +use crate::contract::Contract; + #[derive(Clone)] pub struct MultisigContract { pub contract_addr: Addr, @@ -11,8 +13,9 @@ impl MultisigContract { pub fn instantiate_contract( app: &mut App, governance: Addr, + admin: Addr, rewards_address: Addr, - block_expiry: u64, + block_expiry: nonempty::Uint64, ) -> Self { let code = ContractWrapper::new( multisig::contract::execute, @@ -28,6 +31,7 @@ impl MultisigContract { &multisig::msg::InstantiateMsg { rewards_address: rewards_address.to_string(), governance_address: governance.to_string(), + admin_address: admin.to_string(), block_expiry, }, &[], diff --git a/integration-tests/src/multisig_prover_contract.rs b/integration-tests/src/multisig_prover_contract.rs index a86ba7f20..75256fb28 100644 --- a/integration-tests/src/multisig_prover_contract.rs +++ b/integration-tests/src/multisig_prover_contract.rs @@ -1,10 +1,12 @@ -use crate::{contract::Contract, protocol::Protocol}; use axelar_wasm_std::Threshold; use cosmwasm_std::Addr; use cw_multi_test::{ContractWrapper, Executor}; use multisig::key::KeyType; use multisig_prover::encoding::Encoder; +use crate::contract::Contract; +use crate::protocol::Protocol; + #[derive(Clone)] pub struct MultisigProverContract { pub contract_addr: Addr, diff --git a/integration-tests/src/protocol.rs b/integration-tests/src/protocol.rs index dd7ae0506..962ad2dff 100644 --- a/integration-tests/src/protocol.rs +++ b/integration-tests/src/protocol.rs @@ -2,11 +2,11 @@ use axelar_wasm_std::nonempty; use cosmwasm_std::Addr; use cw_multi_test::App; -use crate::{ - coordinator_contract::CoordinatorContract, multisig_contract::MultisigContract, - rewards_contract::RewardsContract, router_contract::RouterContract, - service_registry_contract::ServiceRegistryContract, -}; +use crate::coordinator_contract::CoordinatorContract; +use crate::multisig_contract::MultisigContract; +use crate::rewards_contract::RewardsContract; +use crate::router_contract::RouterContract; +use crate::service_registry_contract::ServiceRegistryContract; pub struct Protocol { pub genesis_address: Addr, // holds u128::max coins, can use to send coins to other addresses diff --git a/integration-tests/src/rewards_contract.rs b/integration-tests/src/rewards_contract.rs index 48f1f02dc..0e0118739 100644 --- a/integration-tests/src/rewards_contract.rs +++ b/integration-tests/src/rewards_contract.rs @@ -1,7 +1,8 @@ -use crate::contract::Contract; -use cosmwasm_std::{Addr, Binary, Deps, Env, StdResult}; +use cosmwasm_std::Addr; use cw_multi_test::{App, ContractWrapper, Executor}; +use crate::contract::Contract; + #[derive(Clone)] pub struct RewardsContract { pub contract_addr: Addr, @@ -17,7 +18,7 @@ impl RewardsContract { let code = ContractWrapper::new( rewards::contract::execute, rewards::contract::instantiate, - |_: Deps, _: Env, _: rewards::msg::QueryMsg| -> StdResult { todo!() }, + rewards::contract::query, ); let code_id = app.store_code(Box::new(code)); diff --git a/integration-tests/src/router_contract.rs b/integration-tests/src/router_contract.rs index 1ade90804..45ca7e33f 100644 --- a/integration-tests/src/router_contract.rs +++ b/integration-tests/src/router_contract.rs @@ -1,7 +1,8 @@ -use crate::contract::Contract; use cosmwasm_std::Addr; use cw_multi_test::{App, ContractWrapper, Executor}; +use crate::contract::Contract; + #[derive(Clone)] pub struct RouterContract { pub contract_addr: Addr, diff --git a/integration-tests/src/service_registry_contract.rs b/integration-tests/src/service_registry_contract.rs index 34d79c477..59afd08da 100644 --- a/integration-tests/src/service_registry_contract.rs +++ b/integration-tests/src/service_registry_contract.rs @@ -1,8 +1,9 @@ -use crate::contract::Contract; use cosmwasm_std::Addr; use cw_multi_test::{App, ContractWrapper, Executor}; use service_registry::contract::{execute, instantiate, query}; +use crate::contract::Contract; + #[derive(Clone)] pub struct ServiceRegistryContract { pub contract_addr: Addr, diff --git a/integration-tests/src/voting_verifier_contract.rs b/integration-tests/src/voting_verifier_contract.rs index 5f992770b..43d4a2b60 100644 --- a/integration-tests/src/voting_verifier_contract.rs +++ b/integration-tests/src/voting_verifier_contract.rs @@ -1,11 +1,11 @@ -use crate::contract::Contract; -use crate::protocol::Protocol; -use axelar_wasm_std::nonempty; -use axelar_wasm_std::MajorityThreshold; +use axelar_wasm_std::{nonempty, MajorityThreshold}; use cosmwasm_std::Addr; use cw_multi_test::{ContractWrapper, Executor}; use router_api::ChainName; +use crate::contract::Contract; +use crate::protocol::Protocol; + #[derive(Clone)] pub struct VotingVerifierContract { pub contract_addr: Addr, @@ -41,11 +41,17 @@ impl VotingVerifierContract { service_name: protocol.service_name.clone(), source_gateway_address, voting_threshold, - block_expiry: 10, + block_expiry: 10.try_into().unwrap(), confirmation_height: 5, source_chain, - rewards_address: protocol.rewards.contract_addr.to_string(), + rewards_address: protocol + .rewards + .contract_addr + .to_string() + .try_into() + .unwrap(), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, + address_format: axelar_wasm_std::address_format::AddressFormat::Eip55, }, &[], "voting_verifier", diff --git a/integration-tests/tests/bond_unbond.rs b/integration-tests/tests/bond_unbond.rs index 4ee8da440..b3f27638d 100644 --- a/integration-tests/tests/bond_unbond.rs +++ b/integration-tests/tests/bond_unbond.rs @@ -7,8 +7,8 @@ pub mod test_utils; #[test] fn claim_stake_after_rotation_success() { let chains: Vec = vec![ - "Ethereum".to_string().try_into().unwrap(), - "Polygon".to_string().try_into().unwrap(), + "Ethereum".try_into().unwrap(), + "Polygon".try_into().unwrap(), ]; let test_utils::TestCase { @@ -78,8 +78,8 @@ fn claim_stake_after_rotation_success() { #[test] fn claim_stake_when_in_all_active_verifier_sets_fails() { let chains: Vec = vec![ - "Ethereum".to_string().try_into().unwrap(), - "Polygon".to_string().try_into().unwrap(), + "Ethereum".try_into().unwrap(), + "Polygon".try_into().unwrap(), ]; let test_utils::TestCase { @@ -118,8 +118,8 @@ fn claim_stake_when_in_all_active_verifier_sets_fails() { #[test] fn claim_stake_when_in_some_active_verifier_sets_fails() { let chains: Vec = vec![ - "Ethereum".to_string().try_into().unwrap(), - "Polygon".to_string().try_into().unwrap(), + "Ethereum".try_into().unwrap(), + "Polygon".try_into().unwrap(), ]; let test_utils::TestCase { @@ -162,8 +162,8 @@ fn claim_stake_when_in_some_active_verifier_sets_fails() { #[test] fn claim_stake_after_deregistering_before_rotation_fails() { let chains: Vec = vec![ - "Ethereum".to_string().try_into().unwrap(), - "Polygon".to_string().try_into().unwrap(), + "Ethereum".try_into().unwrap(), + "Polygon".try_into().unwrap(), ]; let test_utils::TestCase { @@ -212,8 +212,8 @@ fn claim_stake_after_deregistering_before_rotation_fails() { #[test] fn claim_stake_when_jailed_fails() { let chains: Vec = vec![ - "Ethereum".to_string().try_into().unwrap(), - "Polygon".to_string().try_into().unwrap(), + "Ethereum".try_into().unwrap(), + "Polygon".try_into().unwrap(), ]; let test_utils::TestCase { @@ -267,8 +267,8 @@ fn claim_stake_when_jailed_fails() { #[test] fn claim_stake_when_in_next_verifier_sets_fails() { let chains: Vec = vec![ - "Ethereum".to_string().try_into().unwrap(), - "Polygon".to_string().try_into().unwrap(), + "Ethereum".try_into().unwrap(), + "Polygon".try_into().unwrap(), ]; let test_utils::TestCase { diff --git a/integration-tests/tests/chain_freeze_unfreeze.rs b/integration-tests/tests/chain_freeze_unfreeze.rs index 8cc2984a5..f1f46d0a8 100644 --- a/integration-tests/tests/chain_freeze_unfreeze.rs +++ b/integration-tests/tests/chain_freeze_unfreeze.rs @@ -1,5 +1,4 @@ use cosmwasm_std::{Addr, HexBinary}; - use integration_tests::contract::Contract; use router_api::{CrossChainId, Message}; @@ -17,14 +16,12 @@ fn chain_can_be_freezed_unfreezed() { } = test_utils::setup_test_case(); let msgs = vec![Message { - cc_id: CrossChainId { - chain: chain1.chain_name.clone(), - id: "0x88d7956fd7b6fcec846548d83bd25727f2585b4be3add21438ae9fbb34625924-3" - .to_string() - .try_into() - .unwrap(), - }, - source_address: "0xBf12773B49()0e1Deb57039061AAcFA2A87DEaC9b9" + cc_id: CrossChainId::new( + chain1.chain_name.clone(), + "0x88d7956fd7b6fcec846548d83bd25727f2585b4be3add21438ae9fbb34625924-3", + ) + .unwrap(), + source_address: "0xBf12773B490e1Deb57039061AAcFA2A87DEaC9b9" .to_string() .try_into() .unwrap(), @@ -64,7 +61,7 @@ fn chain_can_be_freezed_unfreezed() { test_utils::freeze_chain( &mut protocol.app, &protocol.router, - &chain1.chain_name, + chain1.chain_name.clone(), router_api::GatewayDirection::Bidirectional, &protocol.router_admin_address, ); @@ -91,7 +88,7 @@ fn chain_can_be_freezed_unfreezed() { // routed message should have been preserved let found_msgs = - test_utils::get_messages_from_gateway(&mut protocol.app, &chain2.gateway, &msg_ids); + test_utils::messages_from_gateway(&mut protocol.app, &chain2.gateway, &msg_ids); assert_eq!(found_msgs, msgs); // can route again diff --git a/integration-tests/tests/message_routing.rs b/integration-tests/tests/message_routing.rs index 3faf8da08..6ed73be9b 100644 --- a/integration-tests/tests/message_routing.rs +++ b/integration-tests/tests/message_routing.rs @@ -1,5 +1,4 @@ use cosmwasm_std::{Addr, HexBinary, Uint128}; - use integration_tests::contract::Contract; use router_api::{CrossChainId, Message}; @@ -20,14 +19,12 @@ fn single_message_can_be_verified_and_routed_and_proven_and_rewards_are_distribu } = test_utils::setup_test_case(); let msgs = vec![Message { - cc_id: CrossChainId { - chain: chain1.chain_name.clone(), - id: "0x88d7956fd7b6fcec846548d83bd25727f2585b4be3add21438ae9fbb34625924-3" - .to_string() - .try_into() - .unwrap(), - }, - source_address: "0xBf12773B49()0e1Deb57039061AAcFA2A87DEaC9b9" + cc_id: CrossChainId::new( + chain1.chain_name.clone(), + "0x88d7956fd7b6fcec846548d83bd25727f2585b4be3add21438ae9fbb34625924-3", + ) + .unwrap(), + source_address: "0xBf12773B490e1Deb57039061AAcFA2A87DEaC9b9" .to_string() .try_into() .unwrap(), @@ -67,7 +64,7 @@ fn single_message_can_be_verified_and_routed_and_proven_and_rewards_are_distribu // check that the message can be found at the outgoing gateway let found_msgs = - test_utils::get_messages_from_gateway(&mut protocol.app, &chain2.gateway, &msg_ids); + test_utils::messages_from_gateway(&mut protocol.app, &chain2.gateway, &msg_ids); assert_eq!(found_msgs, msgs); // trigger signing and submit all necessary signatures @@ -78,7 +75,7 @@ fn single_message_can_be_verified_and_routed_and_proven_and_rewards_are_distribu &verifiers, ); - let proof = test_utils::get_proof(&mut protocol.app, &chain2.multisig_prover, &session_id); + let proof = test_utils::proof(&mut protocol.app, &chain2.multisig_prover, &session_id); // proof should be complete by now assert!(matches!( @@ -127,13 +124,11 @@ fn routing_to_incorrect_gateway_interface() { } = test_utils::setup_test_case(); let msgs = [Message { - cc_id: CrossChainId { - chain: chain1.chain_name.clone(), - id: "0x88d7956fd7b6fcec846548d83bd25727f2585b4be3add21438ae9fbb34625924-3" - .to_string() - .try_into() - .unwrap(), - }, + cc_id: CrossChainId::new( + chain1.chain_name.clone(), + "0x88d7956fd7b6fcec846548d83bd25727f2585b4be3add21438ae9fbb34625924-3", + ) + .unwrap(), source_address: "0xBf12773B49()0e1Deb57039061AAcFA2A87DEaC9b9" .to_string() .try_into() diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 6ddc30523..0a94de2a4 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -1,44 +1,38 @@ -use axelar_wasm_std::{ - msg_id::tx_hash_event_index::HexTxHashAndEventIndex, - nonempty, - voting::{PollId, Vote}, - Participant, Threshold, -}; +use std::collections::{HashMap, HashSet}; + +use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; +use axelar_wasm_std::voting::{PollId, Vote}; +use axelar_wasm_std::{nonempty, Participant, Threshold}; +use coordinator::msg::ExecuteMsg as CoordinatorExecuteMsg; use cosmwasm_std::{ coins, Addr, Attribute, BlockInfo, Event, HexBinary, StdError, Uint128, Uint64, }; use cw_multi_test::{App, AppResponse, Executor}; -use multisig_prover::msg::VerifierSetResponse; -use router_api::{Address, ChainName, CrossChainId, GatewayDirection, Message}; -use std::collections::HashSet; - use integration_tests::contract::Contract; use integration_tests::coordinator_contract::CoordinatorContract; use integration_tests::gateway_contract::GatewayContract; use integration_tests::multisig_contract::MultisigContract; use integration_tests::multisig_prover_contract::MultisigProverContract; +use integration_tests::protocol::Protocol; use integration_tests::rewards_contract::RewardsContract; +use integration_tests::router_contract::RouterContract; use integration_tests::service_registry_contract::ServiceRegistryContract; use integration_tests::voting_verifier_contract::VotingVerifierContract; -use integration_tests::{protocol::Protocol, router_contract::RouterContract}; - use k256::ecdsa; -use sha3::{Digest, Keccak256}; - -use coordinator::msg::ExecuteMsg as CoordinatorExecuteMsg; -use multisig::{ - key::{KeyType, PublicKey}, - verifier_set::VerifierSet, -}; +use multisig::key::{KeyType, PublicKey}; +use multisig::verifier_set::VerifierSet; +use multisig_prover::msg::VerifierSetResponse; use rewards::state::PoolId; +use router_api::{Address, ChainName, CrossChainId, GatewayDirection, Message}; use service_registry::msg::ExecuteMsg; +use sha3::{Digest, Keccak256}; use tofn::ecdsa::KeyPair; pub const AXL_DENOMINATION: &str = "uaxl"; pub const SIGNATURE_BLOCK_EXPIRY: u64 = 100; -fn get_event_attribute<'a>( +fn find_event_attribute<'a>( events: &'a [Event], event_type: &str, attribute_name: &str, @@ -67,10 +61,10 @@ pub fn verify_messages( let response = response.unwrap(); - let poll_id = get_event_attribute(&response.events, "wasm-messages_poll_started", "poll_id") + let poll_id = find_event_attribute(&response.events, "wasm-messages_poll_started", "poll_id") .map(|attr| serde_json::from_str(&attr.value).unwrap()) .expect("couldn't get poll_id"); - let expiry = get_event_attribute(&response.events, "wasm-messages_poll_started", "expires_at") + let expiry = find_event_attribute(&response.events, "wasm-messages_poll_started", "expires_at") .map(|attr| attr.value.as_str().parse().unwrap()) .expect("couldn't get poll expiry"); (poll_id, expiry) @@ -88,16 +82,15 @@ pub fn route_messages(app: &mut App, gateway: &GatewayContract, msgs: &[Message] pub fn freeze_chain( app: &mut App, router: &RouterContract, - chain_name: &ChainName, + chain_name: ChainName, direction: GatewayDirection, admin: &Addr, ) { let response = router.execute( app, admin.clone(), - &router_api::msg::ExecuteMsg::FreezeChain { - chain: chain_name.clone(), - direction, + &router_api::msg::ExecuteMsg::FreezeChains { + chains: HashMap::from([(chain_name, direction)]), }, ); assert!(response.is_ok(), "{:?}", response); @@ -113,9 +106,8 @@ pub fn unfreeze_chain( let response = router.execute( app, admin.clone(), - &router_api::msg::ExecuteMsg::UnfreezeChain { - chain: chain_name.clone(), - direction, + &router_api::msg::ExecuteMsg::UnfreezeChains { + chains: HashMap::from([(chain_name.clone(), direction)]), }, ); assert!(response.is_ok(), "{:?}", response); @@ -205,17 +197,17 @@ pub fn construct_proof_and_sign( let response = multisig_prover.execute( &mut protocol.app, Addr::unchecked("relayer"), - &multisig_prover::msg::ExecuteMsg::ConstructProof { - message_ids: messages.iter().map(|msg| msg.cc_id.clone()).collect(), - }, + &multisig_prover::msg::ExecuteMsg::ConstructProof( + messages.iter().map(|msg| msg.cc_id.clone()).collect(), + ), ); assert!(response.is_ok()); sign_proof(protocol, verifiers, response.unwrap()) } -pub fn get_multisig_session_id(response: AppResponse) -> Uint64 { - get_event_attribute(&response.events, "wasm-signing_started", "session_id") +pub fn multisig_session_id(response: AppResponse) -> Uint64 { + find_event_attribute(&response.events, "wasm-signing_started", "session_id") .map(|attr| attr.value.as_str().try_into().unwrap()) .expect("couldn't get session_id") } @@ -225,10 +217,10 @@ pub fn sign_proof( verifiers: &Vec, response: AppResponse, ) -> Uint64 { - let msg_to_sign = get_event_attribute(&response.events, "wasm-signing_started", "msg") + let msg_to_sign = find_event_attribute(&response.events, "wasm-signing_started", "msg") .map(|attr| attr.value.clone()) .expect("couldn't find message to sign"); - let session_id = get_multisig_session_id(response); + let session_id = multisig_session_id(response); for verifier in verifiers { let signature = tofn::ecdsa::sign( @@ -279,31 +271,29 @@ pub fn register_service( assert!(response.is_ok()); } -pub fn get_messages_from_gateway( +pub fn messages_from_gateway( app: &mut App, gateway: &GatewayContract, message_ids: &[CrossChainId], ) -> Vec { let query_response: Result, StdError> = gateway.query( app, - &gateway_api::msg::QueryMsg::GetOutgoingMessages { - message_ids: message_ids.to_owned(), - }, + &gateway_api::msg::QueryMsg::OutgoingMessages(message_ids.to_owned()), ); assert!(query_response.is_ok()); query_response.unwrap() } -pub fn get_proof( +pub fn proof( app: &mut App, multisig_prover: &MultisigProverContract, multisig_session_id: &Uint64, -) -> multisig_prover::msg::GetProofResponse { - let query_response: Result = multisig_prover +) -> multisig_prover::msg::ProofResponse { + let query_response: Result = multisig_prover .query( app, - &multisig_prover::msg::QueryMsg::GetProof { + &multisig_prover::msg::QueryMsg::Proof { multisig_session_id: *multisig_session_id, }, ); @@ -312,7 +302,7 @@ pub fn get_proof( query_response.unwrap() } -pub fn get_verifier_set_from_prover( +pub fn verifier_set_from_prover( app: &mut App, multisig_prover_contract: &MultisigProverContract, ) -> VerifierSet { @@ -365,13 +355,13 @@ pub fn setup_protocol(service_name: nonempty::String) -> Protocol { .init_balance(storage, &genesis, coins(u128::MAX, AXL_DENOMINATION)) .unwrap() }); - let router_admin_address = Addr::unchecked("admin"); + let admin_address = Addr::unchecked("admin"); let governance_address = Addr::unchecked("governance"); let nexus_gateway = Addr::unchecked("nexus_gateway"); let router = RouterContract::instantiate_contract( &mut app, - router_admin_address.clone(), + admin_address.clone(), governance_address.clone(), nexus_gateway.clone(), ); @@ -391,8 +381,9 @@ pub fn setup_protocol(service_name: nonempty::String) -> Protocol { let multisig = MultisigContract::instantiate_contract( &mut app, governance_address.clone(), + admin_address.clone(), rewards.contract_addr.clone(), - SIGNATURE_BLOCK_EXPIRY, + SIGNATURE_BLOCK_EXPIRY.try_into().unwrap(), ); let coordinator = @@ -405,7 +396,7 @@ pub fn setup_protocol(service_name: nonempty::String) -> Protocol { genesis_address: genesis, governance_address, router, - router_admin_address, + router_admin_address: admin_address, multisig, coordinator, service_registry, @@ -500,15 +491,15 @@ pub fn confirm_verifier_set( assert!(response.is_ok()); } -fn get_verifier_set_poll_id_and_expiry(response: AppResponse) -> (PollId, PollExpiryBlock) { - let poll_id = get_event_attribute( +fn verifier_set_poll_id_and_expiry(response: AppResponse) -> (PollId, PollExpiryBlock) { + let poll_id = find_event_attribute( &response.events, "wasm-verifier_set_poll_started", "poll_id", ) .map(|attr| serde_json::from_str(&attr.value).unwrap()) .expect("couldn't get poll_id"); - let expiry = get_event_attribute( + let expiry = find_event_attribute( &response.events, "wasm-verifier_set_poll_started", "expires_at", @@ -540,7 +531,7 @@ pub fn create_verifier_set_poll( ); assert!(response.is_ok()); - get_verifier_set_poll_id_and_expiry(response.unwrap()) + verifier_set_poll_id_and_expiry(response.unwrap()) } pub fn verifiers_to_verifier_set( @@ -655,7 +646,7 @@ pub fn setup_chain( ) -> Chain { let voting_verifier = VotingVerifierContract::instantiate_contract( protocol, - "doesn't matter".to_string().try_into().unwrap(), + "doesn't matter".try_into().unwrap(), Threshold::try_from((3, 4)).unwrap().try_into().unwrap(), chain_name.clone(), ); @@ -685,8 +676,11 @@ pub fn setup_chain( let response = protocol.multisig.execute( &mut protocol.app, protocol.governance_address.clone(), - &multisig::msg::ExecuteMsg::AuthorizeCaller { - contract_address: multisig_prover.contract_addr.clone(), + &multisig::msg::ExecuteMsg::AuthorizeCallers { + contracts: HashMap::from([( + multisig_prover.contract_addr.to_string(), + chain_name.clone(), + )]), }, ); assert!(response.is_ok()); @@ -788,7 +782,7 @@ pub fn rotate_active_verifier_set( let session_id = sign_proof(protocol, previous_verifiers, response.unwrap()); - let proof = get_proof(&mut protocol.app, &chain.multisig_prover, &session_id); + let proof = proof(&mut protocol.app, &chain.multisig_prover, &session_id); assert!(matches!( proof.status, multisig_prover::msg::ProofStatus::Completed { .. } @@ -831,10 +825,10 @@ pub struct TestCase { // Creates an instance of Axelar Amplifier with an initial verifier set registered, and returns a TestCase instance. pub fn setup_test_case() -> TestCase { - let mut protocol = setup_protocol("validators".to_string().try_into().unwrap()); + let mut protocol = setup_protocol("validators".try_into().unwrap()); let chains = vec![ - "Ethereum".to_string().try_into().unwrap(), - "Polygon".to_string().try_into().unwrap(), + "Ethereum".try_into().unwrap(), + "Polygon".try_into().unwrap(), ]; let verifiers = create_new_verifiers_vec( chains.clone(), @@ -859,8 +853,8 @@ pub fn setup_test_case() -> TestCase { } pub fn assert_contract_err_strings_equal( - actual: impl Into, - expected: impl Into, + actual: impl Into, + expected: impl Into, ) { assert_eq!(actual.into().to_string(), expected.into().to_string()); } diff --git a/integration-tests/tests/update_worker_set.rs b/integration-tests/tests/update_worker_set.rs index 33495385f..4bbdcfe8e 100644 --- a/integration-tests/tests/update_worker_set.rs +++ b/integration-tests/tests/update_worker_set.rs @@ -1,21 +1,17 @@ use cosmwasm_std::Addr; use cw_multi_test::Executor; - use integration_tests::contract::Contract; use multisig_prover::msg::ExecuteMsg; -use service_registry::{msg::QueryMsg as ServiceRegistryQueryMsg, state::WeightedVerifier}; -use test_utils::{ - create_new_verifiers_vec, get_multisig_session_id, register_in_service_registry, - register_verifiers, rotate_active_verifier_set, Verifier, -}; +use service_registry::msg::QueryMsg as ServiceRegistryQueryMsg; +use service_registry::state::WeightedVerifier; pub mod test_utils; #[test] fn verifier_set_can_be_initialized_and_then_manually_updated() { let chains: Vec = vec![ - "Ethereum".to_string().try_into().unwrap(), - "Polygon".to_string().try_into().unwrap(), + "Ethereum".try_into().unwrap(), + "Polygon".try_into().unwrap(), ]; let test_utils::TestCase { @@ -30,19 +26,19 @@ fn verifier_set_can_be_initialized_and_then_manually_updated() { test_utils::verifiers_to_verifier_set(&mut protocol, &initial_verifiers); let verifier_set = - test_utils::get_verifier_set_from_prover(&mut protocol.app, ðereum.multisig_prover); + test_utils::verifier_set_from_prover(&mut protocol.app, ðereum.multisig_prover); assert_eq!(verifier_set, simulated_verifier_set); // add third and fourth verifier let mut new_verifiers = Vec::new(); - let new_verifier = Verifier { + let new_verifier = test_utils::Verifier { addr: Addr::unchecked("verifier3"), supported_chains: chains.clone(), key_pair: test_utils::generate_key(2), }; new_verifiers.push(new_verifier); - let new_verifier = Verifier { + let new_verifier = test_utils::Verifier { addr: Addr::unchecked("verifier4"), supported_chains: chains.clone(), key_pair: test_utils::generate_key(3), @@ -70,7 +66,7 @@ fn verifier_set_can_be_initialized_and_then_manually_updated() { // sign with old verifiers let session_id = test_utils::sign_proof(&mut protocol, &initial_verifiers, response); - let proof = test_utils::get_proof(&mut protocol.app, ðereum.multisig_prover, &session_id); + let proof = test_utils::proof(&mut protocol.app, ðereum.multisig_prover, &session_id); assert!(matches!( proof.status, multisig_prover::msg::ProofStatus::Completed { .. } @@ -104,15 +100,15 @@ fn verifier_set_can_be_initialized_and_then_manually_updated() { ); let new_verifier_set = - test_utils::get_verifier_set_from_prover(&mut protocol.app, ðereum.multisig_prover); + test_utils::verifier_set_from_prover(&mut protocol.app, ðereum.multisig_prover); assert_eq!(new_verifier_set, expected_new_verifier_set); } #[test] fn verifier_set_cannot_be_updated_again_while_pending_verifier_is_not_yet_confirmed() { let chains = vec![ - "Ethereum".to_string().try_into().unwrap(), - "Polygon".to_string().try_into().unwrap(), + "Ethereum".try_into().unwrap(), + "Polygon".try_into().unwrap(), ]; let test_utils::TestCase { mut protocol, @@ -126,7 +122,7 @@ fn verifier_set_cannot_be_updated_again_while_pending_verifier_is_not_yet_confir test_utils::verifiers_to_verifier_set(&mut protocol, &initial_verifiers); let verifier_set = - test_utils::get_verifier_set_from_prover(&mut protocol.app, ðereum.multisig_prover); + test_utils::verifier_set_from_prover(&mut protocol.app, ðereum.multisig_prover); assert_eq!(verifier_set, simulated_verifier_set); @@ -160,7 +156,7 @@ fn verifier_set_cannot_be_updated_again_while_pending_verifier_is_not_yet_confir let session_id = test_utils::sign_proof(&mut protocol, &initial_verifiers, response); - let proof = test_utils::get_proof(&mut protocol.app, ðereum.multisig_prover, &session_id); + let proof = test_utils::proof(&mut protocol.app, ðereum.multisig_prover, &session_id); // proof must be completed assert!(matches!( @@ -206,7 +202,7 @@ fn verifier_set_cannot_be_updated_again_while_pending_verifier_is_not_yet_confir ); let new_verifier_set = - test_utils::get_verifier_set_from_prover(&mut protocol.app, ðereum.multisig_prover); + test_utils::verifier_set_from_prover(&mut protocol.app, ðereum.multisig_prover); assert_eq!(new_verifier_set, expected_new_verifier_set); @@ -231,8 +227,8 @@ fn verifier_set_cannot_be_updated_again_while_pending_verifier_is_not_yet_confir #[test] fn verifier_set_update_can_be_resigned() { let chains = vec![ - "Ethereum".to_string().try_into().unwrap(), - "Polygon".to_string().try_into().unwrap(), + "Ethereum".try_into().unwrap(), + "Polygon".try_into().unwrap(), ]; let test_utils::TestCase { mut protocol, @@ -246,7 +242,7 @@ fn verifier_set_update_can_be_resigned() { test_utils::verifiers_to_verifier_set(&mut protocol, &initial_verifiers); let verifier_set = - test_utils::get_verifier_set_from_prover(&mut protocol.app, ðereum.multisig_prover); + test_utils::verifier_set_from_prover(&mut protocol.app, ðereum.multisig_prover); assert_eq!(verifier_set, simulated_verifier_set); @@ -275,7 +271,7 @@ fn verifier_set_update_can_be_resigned() { ) .unwrap(); - let first_session_id = get_multisig_session_id(response.clone()); + let first_session_id = test_utils::multisig_session_id(response.clone()); // signing didn't occur, trigger signing again let response = protocol @@ -288,7 +284,7 @@ fn verifier_set_update_can_be_resigned() { ) .unwrap(); - let second_session_id = get_multisig_session_id(response.clone()); + let second_session_id = test_utils::multisig_session_id(response.clone()); assert_ne!(first_session_id, second_session_id); test_utils::sign_proof(&mut protocol, &initial_verifiers, response); @@ -304,13 +300,13 @@ fn verifier_set_update_can_be_resigned() { ) .unwrap(); - let third_session_id = get_multisig_session_id(response.clone()); + let third_session_id = test_utils::multisig_session_id(response.clone()); assert_ne!(first_session_id, second_session_id); assert_ne!(second_session_id, third_session_id); test_utils::sign_proof(&mut protocol, &initial_verifiers, response); - let proof = test_utils::get_proof( + let proof = test_utils::proof( &mut protocol.app, ðereum.multisig_prover, &second_session_id, @@ -325,7 +321,7 @@ fn verifier_set_update_can_be_resigned() { #[test] fn governance_should_confirm_new_verifier_set_without_verification() { - let chains: Vec = vec!["Ethereum".to_string().try_into().unwrap()]; + let chains: Vec = vec!["Ethereum".try_into().unwrap()]; let test_utils::TestCase { mut protocol, chain1: ethereum, @@ -336,7 +332,7 @@ fn governance_should_confirm_new_verifier_set_without_verification() { // add third verifier let mut new_verifiers = Vec::new(); - let new_verifier = Verifier { + let new_verifier = test_utils::Verifier { addr: Addr::unchecked("verifier3"), supported_chains: chains.clone(), key_pair: test_utils::generate_key(2), @@ -367,7 +363,7 @@ fn governance_should_confirm_new_verifier_set_without_verification() { ); let new_verifier_set = - test_utils::get_verifier_set_from_prover(&mut protocol.app, ðereum.multisig_prover); + test_utils::verifier_set_from_prover(&mut protocol.app, ðereum.multisig_prover); assert_eq!(new_verifier_set, expected_new_verifier_set); } @@ -385,21 +381,21 @@ fn rotate_signers_should_filter_out_signers_without_pubkey() { let chains: Vec = vec![chain1.chain_name.clone()]; // add a third verifier to satisfy min verifier change threshold - register_verifiers( + test_utils::register_verifiers( &mut protocol, - &create_new_verifiers_vec(chains.clone(), vec![("verifier3".to_string(), 2)]), + &test_utils::create_new_verifiers_vec(chains.clone(), vec![("verifier3".to_string(), 2)]), min_verifier_bond, ); // add a fourth verifier in service registry but does not submit a pubkey to multisig - register_in_service_registry( + test_utils::register_in_service_registry( &mut protocol, - &create_new_verifiers_vec(chains.clone(), vec![("verifier4".to_string(), 3)]), + &test_utils::create_new_verifiers_vec(chains.clone(), vec![("verifier4".to_string(), 3)]), min_verifier_bond, ); // the fourth verifier should be filtered out in prover because it does not have a pubkey - let expect_new_verifiers = create_new_verifiers_vec( + let expect_new_verifiers = test_utils::create_new_verifiers_vec( chains.clone(), vec![ ("verifier1".to_string(), 0), @@ -415,7 +411,7 @@ fn rotate_signers_should_filter_out_signers_without_pubkey() { .service_registry .query( &protocol.app, - &ServiceRegistryQueryMsg::GetActiveVerifiers { + &ServiceRegistryQueryMsg::ActiveVerifiers { service_name: protocol.service_name.to_string(), chain_name: chains[0].clone(), }, @@ -428,7 +424,7 @@ fn rotate_signers_should_filter_out_signers_without_pubkey() { ); // rotate signers - rotate_active_verifier_set( + test_utils::rotate_active_verifier_set( &mut protocol, chain1.clone(), &initial_verifiers, @@ -436,7 +432,7 @@ fn rotate_signers_should_filter_out_signers_without_pubkey() { ); let verifier_set = - test_utils::get_verifier_set_from_prover(&mut protocol.app, &chain1.multisig_prover); + test_utils::verifier_set_from_prover(&mut protocol.app, &chain1.multisig_prover); assert_eq!(verifier_set, expected_verifier_set); } diff --git a/interchain-token-service/Cargo.toml b/interchain-token-service/Cargo.toml new file mode 100644 index 000000000..af554bc14 --- /dev/null +++ b/interchain-token-service/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "interchain-token-service" +version = "0.1.0" +rust-version = { workspace = true } +edition = "2021" + +[dependencies] +alloy-primitives = { workspace = true } +alloy-sol-types = { workspace = true } +axelar-wasm-std = { workspace = true, features = ["derive"] } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +error-stack = { workspace = true } +report = { workspace = true } +router-api = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +strum = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +goldie = { workspace = true } + +[lints] +workspace = true diff --git a/interchain-token-service/release.toml b/interchain-token-service/release.toml new file mode 100644 index 000000000..954564097 --- /dev/null +++ b/interchain-token-service/release.toml @@ -0,0 +1 @@ +release = false diff --git a/interchain-token-service/src/abi.rs b/interchain-token-service/src/abi.rs new file mode 100644 index 000000000..0c9f6f6dc --- /dev/null +++ b/interchain-token-service/src/abi.rs @@ -0,0 +1,579 @@ +use alloy_primitives::{FixedBytes, U256}; +use alloy_sol_types::{sol, SolValue}; +use axelar_wasm_std::FnExt; +use cosmwasm_std::{HexBinary, Uint256}; +use error_stack::{Report, ResultExt}; +use router_api::ChainName; + +use crate::error::Error; +use crate::primitives::{ItsHubMessage, ItsMessage}; +use crate::{TokenId, TokenManagerType}; + +// ITS Message payload types +// Reference: https://github.com/axelarnetwork/interchain-token-service/blob/v1.2.4/DESIGN.md#interchain-communication-spec +// `abi_encode_params` is used to encode the struct fields as ABI params as required by the spec. +// E.g. `DeployTokenManager::abi_encode_params` encodes as `abi.encode([uint256, bytes32, uint256, bytes], [...])`. +sol! { + enum MessageType { + InterchainTransfer, + DeployInterchainToken, + DeployTokenManager, + SendToHub, + ReceiveFromHub, + } + + struct InterchainTransfer { + uint256 messageType; + bytes32 tokenId; + bytes sourceAddress; + bytes destinationAddress; + uint256 amount; + bytes data; + } + + struct DeployInterchainToken { + uint256 messageType; + bytes32 tokenId; + string name; + string symbol; + uint8 decimals; + bytes minter; + } + + struct DeployTokenManager { + uint256 messageType; + bytes32 tokenId; + uint256 tokenManagerType; + bytes params; + } + + struct SendToHub { + uint256 messageType; + /// True destination chain name when sending a message from ITS edge source contract -> ITS Hub + string destination_chain; + bytes message; + } + + struct ReceiveFromHub { + uint256 messageType; + /// True source chain name when receiving a message from ITS Hub -> ITS edge destination contract + string source_chain; + bytes message; + } +} + +impl ItsMessage { + pub fn abi_encode(self) -> HexBinary { + match self { + ItsMessage::InterchainTransfer { + token_id, + source_address, + destination_address, + amount, + data, + } => InterchainTransfer { + messageType: MessageType::InterchainTransfer.into(), + tokenId: FixedBytes::<32>::new(token_id.into()), + sourceAddress: Vec::::from(source_address).into(), + destinationAddress: Vec::::from(destination_address).into(), + amount: U256::from_le_bytes(amount.to_le_bytes()), + data: Vec::::from(data).into(), + } + .abi_encode_params(), + ItsMessage::DeployInterchainToken { + token_id, + name, + symbol, + decimals, + minter, + } => DeployInterchainToken { + messageType: MessageType::DeployInterchainToken.into(), + tokenId: FixedBytes::<32>::new(token_id.into()), + name, + symbol, + decimals, + minter: Vec::::from(minter).into(), + } + .abi_encode_params(), + ItsMessage::DeployTokenManager { + token_id, + token_manager_type, + params, + } => DeployTokenManager { + messageType: MessageType::DeployTokenManager.into(), + tokenId: FixedBytes::<32>::new(token_id.into()), + tokenManagerType: token_manager_type.into(), + params: Vec::::from(params).into(), + } + .abi_encode_params(), + } + .into() + } + + pub fn abi_decode(payload: &[u8]) -> Result> { + if payload.len() < 32 { + return Err(Report::new(Error::InvalidMessage)); + } + + let message_type = MessageType::abi_decode(&payload[0..32], true) + .change_context(Error::InvalidMessageType)?; + + let message = match message_type { + MessageType::InterchainTransfer => { + let decoded = InterchainTransfer::abi_decode_params(payload, true) + .change_context(Error::InvalidMessage)?; + + Ok(ItsMessage::InterchainTransfer { + token_id: TokenId::new(decoded.tokenId.into()), + source_address: HexBinary::from(decoded.sourceAddress.to_vec()), + destination_address: HexBinary::from(decoded.destinationAddress.as_ref()), + amount: Uint256::from_le_bytes(decoded.amount.to_le_bytes()), + data: HexBinary::from(decoded.data.as_ref()), + }) + } + MessageType::DeployInterchainToken => { + let decoded = DeployInterchainToken::abi_decode_params(payload, true) + .change_context(Error::InvalidMessage)?; + + Ok(ItsMessage::DeployInterchainToken { + token_id: TokenId::new(decoded.tokenId.into()), + name: decoded.name, + symbol: decoded.symbol, + decimals: decoded.decimals, + minter: HexBinary::from(decoded.minter.as_ref()), + }) + } + MessageType::DeployTokenManager => { + let decoded = DeployTokenManager::abi_decode_params(payload, true) + .change_context(Error::InvalidMessage)?; + + let token_manager_type = u8::try_from(decoded.tokenManagerType) + .change_context(Error::InvalidTokenManagerType)? + .then(TokenManagerType::from_repr) + .ok_or_else(|| Report::new(Error::InvalidTokenManagerType))?; + + Ok(ItsMessage::DeployTokenManager { + token_id: TokenId::new(decoded.tokenId.into()), + token_manager_type, + params: HexBinary::from(decoded.params.as_ref()), + }) + } + _ => Err(Report::new(Error::InvalidMessageType)), + }?; + + Ok(message) + } +} + +impl ItsHubMessage { + pub fn abi_encode(self) -> HexBinary { + match self { + ItsHubMessage::SendToHub { + destination_chain, + message, + } => SendToHub { + messageType: MessageType::SendToHub.into(), + destination_chain: destination_chain.into(), + message: Vec::::from(message.abi_encode()).into(), + } + .abi_encode_params() + .into(), + ItsHubMessage::ReceiveFromHub { + source_chain, + message, + } => ReceiveFromHub { + messageType: MessageType::ReceiveFromHub.into(), + source_chain: source_chain.into(), + message: Vec::::from(message.abi_encode()).into(), + } + .abi_encode_params() + .into(), + } + } + + pub fn abi_decode(payload: &[u8]) -> Result> { + if payload.len() < 32 { + return Err(Report::new(Error::InvalidMessage)); + } + + let message_type = MessageType::abi_decode(&payload[0..32], true) + .change_context(Error::InvalidMessageType)?; + + let hub_message = match message_type { + MessageType::SendToHub => { + let decoded = SendToHub::abi_decode_params(payload, true) + .change_context(Error::InvalidMessage)?; + + ItsHubMessage::SendToHub { + destination_chain: ChainName::try_from(decoded.destination_chain) + .change_context(Error::InvalidChainName)?, + message: ItsMessage::abi_decode(&decoded.message)?, + } + } + MessageType::ReceiveFromHub => { + let decoded = ReceiveFromHub::abi_decode_params(payload, true) + .change_context(Error::InvalidMessage)?; + + ItsHubMessage::ReceiveFromHub { + source_chain: ChainName::try_from(decoded.source_chain) + .change_context(Error::InvalidChainName)?, + message: ItsMessage::abi_decode(&decoded.message)?, + } + } + _ => return Err(Report::new(Error::InvalidMessageType)), + }; + + Ok(hub_message) + } +} + +impl From for U256 { + fn from(value: MessageType) -> Self { + U256::from(value as u8) + } +} + +impl From for U256 { + fn from(value: TokenManagerType) -> Self { + U256::from(value as u8) + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use alloy_primitives::{FixedBytes, U256}; + use alloy_sol_types::SolValue; + use cosmwasm_std::{HexBinary, Uint256}; + use router_api::ChainName; + + use crate::abi::{DeployTokenManager, MessageType, SendToHub}; + use crate::error::Error; + use crate::{ItsHubMessage, ItsMessage, TokenManagerType}; + + #[test] + fn interchain_transfer_encode_decode() { + let remote_chain = ChainName::from_str("chain").unwrap(); + + let cases = vec![ + ItsHubMessage::SendToHub { + destination_chain: remote_chain.clone(), + message: ItsMessage::InterchainTransfer { + token_id: [0u8; 32].into(), + source_address: HexBinary::from_hex("").unwrap(), + destination_address: HexBinary::from_hex("").unwrap(), + amount: Uint256::zero(), + data: HexBinary::from_hex("").unwrap(), + }, + }, + ItsHubMessage::SendToHub { + destination_chain: remote_chain.clone(), + message: ItsMessage::InterchainTransfer { + token_id: [255u8; 32].into(), + source_address: HexBinary::from_hex("4F4495243837681061C4743b74B3eEdf548D56A5") + .unwrap(), + destination_address: HexBinary::from_hex( + "4F4495243837681061C4743b74B3eEdf548D56A5", + ) + .unwrap(), + amount: Uint256::MAX, + data: HexBinary::from_hex("abcd").unwrap(), + }, + }, + ItsHubMessage::ReceiveFromHub { + source_chain: remote_chain.clone(), + message: ItsMessage::InterchainTransfer { + token_id: [0u8; 32].into(), + source_address: HexBinary::from_hex("").unwrap(), + destination_address: HexBinary::from_hex("").unwrap(), + amount: Uint256::zero(), + data: HexBinary::from_hex("").unwrap(), + }, + }, + ItsHubMessage::ReceiveFromHub { + source_chain: remote_chain.clone(), + message: ItsMessage::InterchainTransfer { + token_id: [255u8; 32].into(), + source_address: HexBinary::from_hex("4F4495243837681061C4743b74B3eEdf548D56A5") + .unwrap(), + destination_address: HexBinary::from_hex( + "4F4495243837681061C4743b74B3eEdf548D56A5", + ) + .unwrap(), + amount: Uint256::MAX, + data: HexBinary::from_hex("abcd").unwrap(), + }, + }, + ]; + + let encoded: Vec<_> = cases + .iter() + .map(|original| original.clone().abi_encode().to_hex()) + .collect(); + + goldie::assert_json!(encoded); + + for original in cases { + let encoded = original.clone().abi_encode(); + let decoded = ItsHubMessage::abi_decode(&encoded).unwrap(); + assert_eq!(original, decoded); + } + } + + #[test] + fn deploy_interchain_token_encode_decode() { + let remote_chain = ChainName::from_str("chain").unwrap(); + + let cases = vec![ + ItsHubMessage::SendToHub { + destination_chain: remote_chain.clone(), + message: ItsMessage::DeployInterchainToken { + token_id: [0u8; 32].into(), + name: "".into(), + symbol: "".into(), + decimals: 0, + minter: HexBinary::from_hex("").unwrap(), + }, + }, + ItsHubMessage::SendToHub { + destination_chain: remote_chain.clone(), + message: ItsMessage::DeployInterchainToken { + token_id: [1u8; 32].into(), + name: "Test Token".into(), + symbol: "TST".into(), + decimals: 18, + minter: HexBinary::from_hex("1234").unwrap(), + }, + }, + ItsHubMessage::SendToHub { + destination_chain: remote_chain.clone(), + message: ItsMessage::DeployInterchainToken { + token_id: [0u8; 32].into(), + name: "Unicode Token 🪙".into(), + symbol: "UNI🔣".into(), + decimals: 255, + minter: HexBinary::from_hex("abcd").unwrap(), + }, + }, + ItsHubMessage::ReceiveFromHub { + source_chain: remote_chain.clone(), + message: ItsMessage::DeployInterchainToken { + token_id: [0u8; 32].into(), + name: "".into(), + symbol: "".into(), + decimals: 0, + minter: HexBinary::from_hex("").unwrap(), + }, + }, + ItsHubMessage::ReceiveFromHub { + source_chain: remote_chain.clone(), + message: ItsMessage::DeployInterchainToken { + token_id: [1u8; 32].into(), + name: "Test Token".into(), + symbol: "TST".into(), + decimals: 18, + minter: HexBinary::from_hex("1234").unwrap(), + }, + }, + ItsHubMessage::ReceiveFromHub { + source_chain: remote_chain.clone(), + message: ItsMessage::DeployInterchainToken { + token_id: [0u8; 32].into(), + name: "Unicode Token 🪙".into(), + symbol: "UNI🔣".into(), + decimals: 255, + minter: HexBinary::from_hex("abcd").unwrap(), + }, + }, + ]; + + let encoded: Vec<_> = cases + .iter() + .map(|original| original.clone().abi_encode().to_hex()) + .collect(); + + goldie::assert_json!(encoded); + + for original in cases { + let encoded = original.clone().abi_encode(); + let decoded = ItsHubMessage::abi_decode(&encoded).unwrap(); + assert_eq!(original, decoded); + } + } + + #[test] + fn deploy_token_manager_encode_decode() { + let remote_chain = ChainName::from_str("chain").unwrap(); + + let cases = vec![ + ItsHubMessage::SendToHub { + destination_chain: remote_chain.clone(), + message: ItsMessage::DeployTokenManager { + token_id: [0u8; 32].into(), + token_manager_type: TokenManagerType::NativeInterchainToken, + params: HexBinary::default(), + }, + }, + ItsHubMessage::SendToHub { + destination_chain: remote_chain.clone(), + message: ItsMessage::DeployTokenManager { + token_id: [1u8; 32].into(), + token_manager_type: TokenManagerType::Gateway, + params: HexBinary::from_hex("1234").unwrap(), + }, + }, + ItsHubMessage::ReceiveFromHub { + source_chain: remote_chain.clone(), + message: ItsMessage::DeployTokenManager { + token_id: [0u8; 32].into(), + token_manager_type: TokenManagerType::NativeInterchainToken, + params: HexBinary::default(), + }, + }, + ItsHubMessage::ReceiveFromHub { + source_chain: remote_chain.clone(), + message: ItsMessage::DeployTokenManager { + token_id: [1u8; 32].into(), + token_manager_type: TokenManagerType::Gateway, + params: HexBinary::from_hex("1234").unwrap(), + }, + }, + ]; + + let encoded: Vec<_> = cases + .iter() + .map(|original| original.clone().abi_encode().to_hex()) + .collect(); + + goldie::assert_json!(encoded); + + for original in cases { + let encoded = original.clone().abi_encode(); + let decoded = ItsHubMessage::abi_decode(&encoded).unwrap(); + assert_eq!(original, decoded); + } + } + + #[test] + fn invalid_its_hub_message_type() { + let invalid_payload = SendToHub { + messageType: U256::from(MessageType::ReceiveFromHub as u8 + 1), + destination_chain: "remote-chain".into(), + message: vec![].into(), + } + .abi_encode_params(); + + let result = ItsHubMessage::abi_decode(&invalid_payload); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().current_context().to_string(), + Error::InvalidMessageType.to_string() + ); + } + + #[test] + fn invalid_its_message_type() { + let mut message = MessageType::DeployTokenManager.abi_encode(); + message[31] = 3; + + let invalid_payload = SendToHub { + messageType: MessageType::SendToHub.into(), + destination_chain: "remote-chain".into(), + message: message.into(), + } + .abi_encode_params(); + + let result = ItsHubMessage::abi_decode(&invalid_payload); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().current_context().to_string(), + Error::InvalidMessageType.to_string() + ); + } + + #[test] + fn invalid_destination_chain() { + let message = DeployTokenManager { + messageType: MessageType::DeployTokenManager.into(), + tokenId: FixedBytes::<32>::new([0u8; 32]), + tokenManagerType: TokenManagerType::NativeInterchainToken.into(), + params: vec![].into(), + }; + + let payload = SendToHub { + messageType: MessageType::SendToHub.into(), + destination_chain: "".into(), + message: message.abi_encode_params().into(), + } + .abi_encode_params(); + + let result = ItsHubMessage::abi_decode(&payload); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().current_context().to_string(), + Error::InvalidChainName.to_string() + ); + } + + #[test] + fn invalid_token_manager_type() { + let message = DeployTokenManager { + messageType: MessageType::DeployTokenManager.into(), + tokenId: FixedBytes::<32>::new([0u8; 32]), + tokenManagerType: U256::from(TokenManagerType::Gateway as u8 + 1), + params: vec![].into(), + }; + + let payload = SendToHub { + messageType: MessageType::SendToHub.into(), + destination_chain: "chain".into(), + message: message.abi_encode_params().into(), + } + .abi_encode_params(); + + let result = ItsHubMessage::abi_decode(&payload); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().current_context().to_string(), + Error::InvalidTokenManagerType.to_string() + ); + } + + #[test] + fn encode_decode_large_data() { + let large_data = vec![0u8; 1024 * 1024]; // 1MB of data + let original = ItsHubMessage::SendToHub { + destination_chain: ChainName::from_str("large-data-chain").unwrap(), + message: ItsMessage::InterchainTransfer { + token_id: [0u8; 32].into(), + source_address: HexBinary::from_hex("1234").unwrap(), + destination_address: HexBinary::from_hex("5678").unwrap(), + amount: Uint256::from(1u128), + data: HexBinary::from(large_data), + }, + }; + + let encoded = original.clone().abi_encode(); + let decoded = ItsHubMessage::abi_decode(&encoded).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn encode_decode_unicode_strings() { + let original = ItsHubMessage::SendToHub { + destination_chain: ChainName::from_str("chain").unwrap(), + message: ItsMessage::DeployInterchainToken { + token_id: [0u8; 32].into(), + name: "Unicode Token 🪙".into(), + symbol: "UNI🔣".into(), + decimals: 18, + minter: HexBinary::from_hex("abcd").unwrap(), + }, + }; + + let encoded = original.clone().abi_encode(); + let decoded = ItsHubMessage::abi_decode(&encoded).unwrap(); + assert_eq!(original, decoded); + } +} diff --git a/interchain-token-service/src/error.rs b/interchain-token-service/src/error.rs new file mode 100644 index 000000000..e7f8be5c5 --- /dev/null +++ b/interchain-token-service/src/error.rs @@ -0,0 +1,14 @@ +use axelar_wasm_std::IntoContractError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq, IntoContractError)] +pub enum Error { + #[error("failed to decode ITS message")] + InvalidMessage, + #[error("invalid message type")] + InvalidMessageType, + #[error("invalid chain name")] + InvalidChainName, + #[error("invalid token manager type")] + InvalidTokenManagerType, +} diff --git a/interchain-token-service/src/lib.rs b/interchain-token-service/src/lib.rs new file mode 100644 index 000000000..d4f5ac941 --- /dev/null +++ b/interchain-token-service/src/lib.rs @@ -0,0 +1,5 @@ +mod primitives; + +pub mod error; +pub use primitives::*; +pub mod abi; diff --git a/interchain-token-service/src/primitives.rs b/interchain-token-service/src/primitives.rs new file mode 100644 index 000000000..4970d294e --- /dev/null +++ b/interchain-token-service/src/primitives.rs @@ -0,0 +1,89 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, Uint256}; +use router_api::ChainName; +use strum::FromRepr; + +#[cw_serde] +#[derive(Eq)] +pub struct TokenId( + #[serde(with = "axelar_wasm_std::hex")] + #[schemars(with = "String")] + [u8; 32], +); + +#[cw_serde] +#[derive(Eq, Copy, FromRepr)] +#[repr(u8)] +pub enum TokenManagerType { + NativeInterchainToken, + MintBurnFrom, + LockUnlock, + LockUnlockFee, + MintBurn, + Gateway, +} + +/// ITS message type that can be sent between ITS contracts for transfers/token deployments +/// `ItsMessage` that are routed via the ITS hub get wrapped inside `ItsHubMessage` +#[cw_serde] +#[derive(Eq)] +pub enum ItsMessage { + InterchainTransfer { + token_id: TokenId, + source_address: HexBinary, + destination_address: HexBinary, + amount: Uint256, + data: HexBinary, + }, + DeployInterchainToken { + token_id: TokenId, + name: String, + symbol: String, + decimals: u8, + minter: HexBinary, + }, + DeployTokenManager { + token_id: TokenId, + token_manager_type: TokenManagerType, + params: HexBinary, + }, +} + +/// ITS message type that can be sent between ITS edge contracts and the ITS Hub +#[cw_serde] +#[derive(Eq)] +pub enum ItsHubMessage { + /// ITS edge source contract -> ITS Hub + SendToHub { + /// True destination chain of the ITS message + destination_chain: ChainName, + message: ItsMessage, + }, + /// ITS Hub -> ITS edge destination contract + ReceiveFromHub { + /// True source chain of the ITS message + source_chain: ChainName, + message: ItsMessage, + }, +} + +impl TokenId { + #[inline(always)] + pub fn new(id: [u8; 32]) -> Self { + id.into() + } +} + +impl From<[u8; 32]> for TokenId { + #[inline(always)] + fn from(id: [u8; 32]) -> Self { + Self(id) + } +} + +impl From for [u8; 32] { + #[inline(always)] + fn from(id: TokenId) -> Self { + id.0 + } +} diff --git a/interchain-token-service/src/testdata/deploy_interchain_token_encode_decode.golden b/interchain-token-service/src/testdata/deploy_interchain_token_encode_decode.golden new file mode 100644 index 000000000..e642fc6d3 --- /dev/null +++ b/interchain-token-service/src/testdata/deploy_interchain_token_encode_decode.golden @@ -0,0 +1,8 @@ +[ + "0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000001010101010101010101010101010101010101010101010101010101010101010100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000a5465737420546f6b656e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003545354000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ff00000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000012556e69636f646520546f6b656e20f09faa9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007554e49f09f94a3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002abcd000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000001010101010101010101010101010101010101010101010101010101010101010100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000a5465737420546f6b656e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003545354000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ff00000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000012556e69636f646520546f6b656e20f09faa9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007554e49f09f94a3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002abcd000000000000000000000000000000000000000000000000000000000000" +] \ No newline at end of file diff --git a/interchain-token-service/src/testdata/deploy_token_manager_encode_decode.golden b/interchain-token-service/src/testdata/deploy_token_manager_encode_decode.golden new file mode 100644 index 000000000..9bd957c4a --- /dev/null +++ b/interchain-token-service/src/testdata/deploy_token_manager_encode_decode.golden @@ -0,0 +1,6 @@ +[ + "0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000201010101010101010101010101010101010101010101010101010101010101010000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000201010101010101010101010101010101010101010101010101010101010101010000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000" +] \ No newline at end of file diff --git a/interchain-token-service/src/testdata/interchain_transfer_encode_decode.golden b/interchain-token-service/src/testdata/interchain_transfer_encode_decode.golden new file mode 100644 index 000000000..eb9766e8a --- /dev/null +++ b/interchain-token-service/src/testdata/interchain_transfer_encode_decode.golden @@ -0,0 +1,6 @@ +[ + "0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000144f4495243837681061c4743b74b3eedf548d56a500000000000000000000000000000000000000000000000000000000000000000000000000000000000000144f4495243837681061c4743b74b3eedf548d56a50000000000000000000000000000000000000000000000000000000000000000000000000000000000000002abcd000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005636861696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000144f4495243837681061c4743b74b3eedf548d56a500000000000000000000000000000000000000000000000000000000000000000000000000000000000000144f4495243837681061c4743b74b3eedf548d56a50000000000000000000000000000000000000000000000000000000000000000000000000000000000000002abcd000000000000000000000000000000000000000000000000000000000000" +] \ No newline at end of file diff --git a/packages/axelar-wasm-std-derive/Cargo.toml b/packages/axelar-wasm-std-derive/Cargo.toml index 1639c75e7..d6dcac8c2 100644 --- a/packages/axelar-wasm-std-derive/Cargo.toml +++ b/packages/axelar-wasm-std-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "axelar-wasm-std-derive" -version = "0.1.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -9,12 +9,14 @@ edition = "2021" proc-macro = true [dependencies] -axelar-wasm-std = { workspace = true } error-stack = { workspace = true } quote = "1.0.33" report = { workspace = true } syn = "2.0.29" thiserror = { workspace = true } +[dev-dependencies] +axelar-wasm-std = { workspace = true } + [lints] workspace = true diff --git a/packages/axelar-wasm-std-derive/src/lib.rs b/packages/axelar-wasm-std-derive/src/lib.rs index d1fb6f92a..4ebac5c0f 100644 --- a/packages/axelar-wasm-std-derive/src/lib.rs +++ b/packages/axelar-wasm-std-derive/src/lib.rs @@ -9,14 +9,11 @@ pub fn into_contract_error_derive(input: TokenStream) -> TokenStream { let name = &ast.ident; let gen = quote! { - use axelar_wasm_std::ContractError as _ContractError; - - impl From<#name> for _ContractError { + impl From<#name> for axelar_wasm_std::error::ContractError { fn from(error: #name) -> Self { - use report::LoggableError; use error_stack::report; - LoggableError::from(&report!(error)).into() + report!(error).into() } } }; diff --git a/packages/axelar-wasm-std-derive/tests/derive.rs b/packages/axelar-wasm-std-derive/tests/derive.rs index 5c04d3827..e5e17f8f6 100644 --- a/packages/axelar-wasm-std-derive/tests/derive.rs +++ b/packages/axelar-wasm-std-derive/tests/derive.rs @@ -1,5 +1,5 @@ -use axelar_wasm_std::ContractError; -use axelar_wasm_std_derive::IntoContractError; +use axelar_wasm_std::error::ContractError; +use axelar_wasm_std::IntoContractError; use thiserror::Error; #[derive(Error, Debug, IntoContractError)] diff --git a/packages/axelar-wasm-std/Cargo.toml b/packages/axelar-wasm-std/Cargo.toml index 0df4ed24a..7f82aa3d2 100644 --- a/packages/axelar-wasm-std/Cargo.toml +++ b/packages/axelar-wasm-std/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "axelar-wasm-std" -version = "0.1.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" description = "Axelar cosmwasm standard library crate" @@ -18,8 +18,7 @@ crate-type = ["rlib"] [features] # for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] +derive = ["dep:axelar-wasm-std-derive"] [package.metadata.scripts] optimize = """docker run --rm -v "$(pwd)":/code \ @@ -29,6 +28,8 @@ optimize = """docker run --rm -v "$(pwd)":/code \ """ [dependencies] +alloy-primitives = { workspace = true } +axelar-wasm-std-derive = { workspace = true, optional = true } bs58 = "0.5.1" cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } @@ -45,7 +46,7 @@ schemars = "0.8.10" serde = { version = "1.0.145", default-features = false, features = ["derive"] } serde_json = "1.0.89" sha3 = { workspace = true } -strum = { version = "0.25", default-features = false, features = ["derive"] } +strum = { workspace = true } thiserror = { workspace = true } valuable = { version = "0.1.0", features = ["derive"] } diff --git a/packages/axelar-wasm-std/src/address_format.rs b/packages/axelar-wasm-std/src/address_format.rs new file mode 100644 index 000000000..b0de28b68 --- /dev/null +++ b/packages/axelar-wasm-std/src/address_format.rs @@ -0,0 +1,23 @@ +use alloy_primitives::Address; +use cosmwasm_schema::cw_serde; +use error_stack::{Report, ResultExt}; + +#[derive(thiserror::Error)] +#[cw_serde] +pub enum Error { + #[error("invalid address '{0}'")] + InvalidAddress(String), +} + +#[cw_serde] +pub enum AddressFormat { + Eip55, +} + +pub fn validate_address(address: &str, format: &AddressFormat) -> Result<(), Report> { + match format { + AddressFormat::Eip55 => Address::parse_checksummed(address, None) + .change_context(Error::InvalidAddress(address.to_string())) + .map(|_| ()), + } +} diff --git a/packages/axelar-wasm-std/src/counter.rs b/packages/axelar-wasm-std/src/counter.rs index 05a709663..90ff709e8 100644 --- a/packages/axelar-wasm-std/src/counter.rs +++ b/packages/axelar-wasm-std/src/counter.rs @@ -38,7 +38,7 @@ mod tests { use super::*; #[test] - fn test_get_and_incr() { + fn test_cur_and_incr() { let mut store = MockStorage::new(); let counter: Counter = Counter::new("counter"); diff --git a/packages/axelar-wasm-std/src/error.rs b/packages/axelar-wasm-std/src/error.rs index 8e8f7410c..0e994e51a 100644 --- a/packages/axelar-wasm-std/src/error.rs +++ b/packages/axelar-wasm-std/src/error.rs @@ -1,24 +1,50 @@ -use crate::permission_control; +use std::fmt::{Display, Formatter}; + use cosmwasm_std::StdError; -use error_stack::{Context, Report}; +use error_stack::{report, Context, Report}; use report::LoggableError; use thiserror::Error; +use crate::permission_control; + /// This error is supposed to be the top-level error type our contracts return to the cosmwasm module. /// Ideally, we would like to return an error-stack [Report] directly, /// but it won't show all necessary information (namely attachments) in the error message, and many places also return an [StdError]. /// To this end, reports get converted into [LoggableError] and this [ContractError] type unifies [LoggableError] and [StdError], /// so we can return both to cosmwasm. -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error(transparent)] - Std(#[from] StdError), - #[error(transparent)] - Structured(#[from] LoggableError), - #[error(transparent)] - Unauthorized(#[from] permission_control::Error), - #[error(transparent)] - WrongVersion(#[from] cw2::VersionError), +#[derive(Error, Debug)] +pub struct ContractError { + pub report: Report, +} + +#[derive(Error, Debug)] +pub enum Error { + #[error("")] + Report, +} + +impl From for ContractError { + fn from(err: StdError) -> Self { + ContractError { + report: report!(err).change_context(Error::Report), + } + } +} + +impl From for ContractError { + fn from(err: cw2::VersionError) -> Self { + ContractError { + report: report!(err).change_context(Error::Report), + } + } +} + +impl From for ContractError { + fn from(err: permission_control::Error) -> Self { + ContractError { + report: report!(err).change_context(Error::Report), + } + } } impl From> for ContractError @@ -26,7 +52,15 @@ where T: Context, { fn from(report: Report) -> Self { - ContractError::Structured(LoggableError::from(&report)) + ContractError { + report: report.change_context(Error::Report), + } + } +} + +impl Display for ContractError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + LoggableError::from(&self.report).fmt(f) } } @@ -42,3 +76,15 @@ pub fn extend_err( Err(added_error) } } + +#[macro_export] +macro_rules! err_contains { + ($expression:expr, $error_type:ty, $pattern:pat $(if $guard:expr)? $(,)?) => { + match $expression.downcast_ref::<$error_type>() { + Some($pattern) $(if $guard)? => true, + _ => false, + } + }; +} + +pub use err_contains; diff --git a/packages/axelar-wasm-std/src/flagset.rs b/packages/axelar-wasm-std/src/flagset.rs index 8402862f0..78280ef89 100644 --- a/packages/axelar-wasm-std/src/flagset.rs +++ b/packages/axelar-wasm-std/src/flagset.rs @@ -1,6 +1,8 @@ use std::ops::{Deref, DerefMut}; -use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; +use schemars::gen::SchemaGenerator; +use schemars::schema::Schema; +use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize}; #[derive(Serialize, Clone, Debug, PartialEq, Eq)] diff --git a/packages/axelar-wasm-std/src/killswitch.rs b/packages/axelar-wasm-std/src/killswitch.rs new file mode 100644 index 000000000..b2342d5a8 --- /dev/null +++ b/packages/axelar-wasm-std/src/killswitch.rs @@ -0,0 +1,218 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Event, Response, StdError, StdResult, Storage}; +use cw_storage_plus::Item; + +/// This is a generic module to be used as a "killswitch" for any contract. +/// The killswitch can be set to "engaged" or "disengaged". The contract +/// can then call `is_contract_active`, which will return true if the killswitch +/// is disengaged. `init` should be called at contract instantiation to set +/// the initial state of the killswitch. + +#[cw_serde] +pub enum State { + Engaged, + Disengaged, +} + +/// Sets the initial state of the killswitch. Should be called during contract instantiation +pub fn init(storage: &mut dyn Storage, initial_state: State) -> StdResult<()> { + STATE.save(storage, &initial_state) +} + +/// Sets the killswitch state to `Engaged`. If the state was previously `Disengaged`, +/// adds the on_state_changed event to the response. Returns an error if the killswitch +/// was not initialized via `init` +pub fn engage(storage: &mut dyn Storage, on_state_change: impl Into) -> StdResult { + let state = STATE.update(storage, |state| match state { + State::Disengaged => Ok(State::Engaged), + State::Engaged => Err(KillSwitchUpdateError::SameState), + }); + + killswitch_update_response(state, on_state_change) +} + +/// Sets the killswitch state to `Disengaged`. If the state was previously `Engaged`, +/// adds the on_state_changed event to the response. Returns an error if the killswitch +/// was not initialized via `init` +pub fn disengage( + storage: &mut dyn Storage, + on_state_change: impl Into, +) -> StdResult { + let state = STATE.update(storage, |state| match state { + State::Engaged => Ok(State::Disengaged), + State::Disengaged => Err(KillSwitchUpdateError::SameState), + }); + + killswitch_update_response(state, on_state_change) +} + +/// Returns true if the killswitch state is `Disengaged`. Otherwise returns false. +/// Returns false if the killswitch was not initialized +pub fn is_contract_active(storage: &dyn Storage) -> bool { + STATE.load(storage).unwrap_or(State::Engaged) == State::Disengaged +} + +#[derive(thiserror::Error, Debug)] +enum KillSwitchUpdateError { + #[error("killswitch is already in the same state")] + SameState, + #[error(transparent)] + Std(#[from] StdError), +} + +fn killswitch_update_response( + state: Result, + on_state_change: impl Into, +) -> StdResult { + match state { + Ok(_) => Ok(Response::new().add_event(on_state_change.into())), + Err(KillSwitchUpdateError::SameState) => Ok(Response::new()), + Err(KillSwitchUpdateError::Std(err)) => Err(err), + } +} + +const STATE: Item = Item::new("state"); + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::mock_dependencies; + use cosmwasm_std::Event; + + use crate::killswitch::{disengage, engage, init, is_contract_active, State, STATE}; + + enum Events { + Engaged, + Disengaged, + } + + impl From for Event { + fn from(val: Events) -> Event { + match val { + Events::Engaged => Event::new("engaged"), + Events::Disengaged => Event::new("disengaged"), + } + } + } + + #[test] + fn init_should_be_able_to_set_state_to_engaged() { + let mut deps = mock_dependencies(); + assert!(STATE.may_load(&deps.storage).unwrap().is_none()); + + init(deps.as_mut().storage, State::Engaged).unwrap(); + + assert_eq!(STATE.load(&deps.storage).unwrap(), State::Engaged); + assert!(!is_contract_active(&deps.storage)); + } + + #[test] + fn init_should_be_able_to_set_state_to_disengaged() { + let mut deps = mock_dependencies(); + assert!(STATE.may_load(&deps.storage).unwrap().is_none()); + + init(deps.as_mut().storage, State::Disengaged).unwrap(); + + assert_eq!(STATE.load(&deps.storage).unwrap(), State::Disengaged); + assert!(is_contract_active(&deps.storage)); + } + + #[test] + fn is_contract_active_should_return_true_when_disengaged() { + let mut deps = mock_dependencies(); + + assert!(!is_contract_active(&deps.storage)); + + STATE.save(deps.as_mut().storage, &State::Engaged).unwrap(); + + assert!(!is_contract_active(&deps.storage)); + + STATE + .save(deps.as_mut().storage, &State::Disengaged) + .unwrap(); + + assert!(is_contract_active(&deps.storage)); + } + + #[test] + fn engage_should_error_when_unset() { + let mut deps = mock_dependencies(); + + assert!(engage(deps.as_mut().storage, Events::Engaged).is_err()); + } + + #[test] + fn engage_should_correctly_set_state_when_disengaged() { + let mut deps = mock_dependencies(); + + STATE + .save(deps.as_mut().storage, &State::Disengaged) + .unwrap(); + engage(deps.as_mut().storage, Events::Engaged).unwrap(); + assert_eq!(STATE.load(&deps.storage).unwrap(), State::Engaged); + assert!(!is_contract_active(&deps.storage)); + } + + #[test] + fn engage_should_correctly_set_state_when_engaged() { + let mut deps = mock_dependencies(); + + STATE.save(deps.as_mut().storage, &State::Engaged).unwrap(); + engage(deps.as_mut().storage, Events::Engaged).unwrap(); + assert_eq!(STATE.load(&deps.storage).unwrap(), State::Engaged); + assert!(!is_contract_active(&deps.storage)); + } + + #[test] + fn disengage_should_error_when_unset() { + let mut deps = mock_dependencies(); + + assert!(disengage(deps.as_mut().storage, Events::Disengaged).is_err()); + } + + #[test] + fn disengage_should_correctly_set_state_when_disengaged() { + let mut deps = mock_dependencies(); + + STATE + .save(deps.as_mut().storage, &State::Disengaged) + .unwrap(); + disengage(deps.as_mut().storage, Events::Disengaged).unwrap(); + assert_eq!(STATE.load(&deps.storage).unwrap(), State::Disengaged); + assert!(is_contract_active(&deps.storage)); + } + + #[test] + fn disengage_should_correctly_set_state_when_engaged() { + let mut deps = mock_dependencies(); + + STATE.save(deps.as_mut().storage, &State::Engaged).unwrap(); + disengage(deps.as_mut().storage, Events::Engaged).unwrap(); + assert_eq!(STATE.load(&deps.storage).unwrap(), State::Disengaged); + assert!(is_contract_active(&deps.storage)); + } + + #[test] + fn engage_and_disengage_should_emit_event_when_state_changes() { + let mut deps = mock_dependencies(); + + init(deps.as_mut().storage, State::Disengaged).unwrap(); + + let engaged_event: Event = Events::Engaged.into(); + let disengaged_event: Event = Events::Disengaged.into(); + + let res = engage(deps.as_mut().storage, Events::Engaged).unwrap(); + assert!(res.events.into_iter().any(|event| event == engaged_event)); + + let res = engage(deps.as_mut().storage, Events::Engaged).unwrap(); + assert_eq!(res.events.len(), 0); + + let res = disengage(deps.as_mut().storage, Events::Disengaged).unwrap(); + assert!(res + .events + .into_iter() + .any(|event| event == disengaged_event)); + + let res = disengage(deps.as_mut().storage, Events::Disengaged).unwrap(); + assert_eq!(res.events.len(), 0); + } +} diff --git a/packages/axelar-wasm-std/src/lib.rs b/packages/axelar-wasm-std/src/lib.rs index 87ef974a4..8f6299a4f 100644 --- a/packages/axelar-wasm-std/src/lib.rs +++ b/packages/axelar-wasm-std/src/lib.rs @@ -1,17 +1,16 @@ -pub use crate::{ - error::ContractError, - fn_ext::FnExt, - snapshot::{Participant, Snapshot}, - threshold::{MajorityThreshold, Threshold}, - verification::VerificationStatus, -}; +pub use crate::fn_ext::FnExt; +pub use crate::snapshot::{Participant, Snapshot}; +pub use crate::threshold::{MajorityThreshold, Threshold}; +pub use crate::verification::VerificationStatus; +pub mod address_format; pub mod counter; pub mod error; pub mod flagset; mod fn_ext; pub mod hash; pub mod hex; +pub mod killswitch; pub mod msg_id; pub mod nonempty; pub mod permission_control; @@ -20,3 +19,6 @@ pub mod threshold; pub mod utils; pub mod verification; pub mod voting; + +#[cfg(feature = "derive")] +pub use axelar_wasm_std_derive::*; diff --git a/packages/axelar-wasm-std/src/msg_id/base_58_event_index.rs b/packages/axelar-wasm-std/src/msg_id/base_58_event_index.rs index ea6debb0d..1c75542b2 100644 --- a/packages/axelar-wasm-std/src/msg_id/base_58_event_index.rs +++ b/packages/axelar-wasm-std/src/msg_id/base_58_event_index.rs @@ -1,12 +1,14 @@ use core::fmt; -use std::{fmt::Display, str::FromStr}; +use std::fmt::Display; +use std::str::FromStr; use error_stack::{Report, ResultExt}; use lazy_static::lazy_static; use regex::Regex; use super::Error; -use crate::{hash::Hash, nonempty}; +use crate::hash::Hash; +use crate::nonempty; pub struct Base58TxDigestAndEventIndex { pub tx_digest: Hash, diff --git a/packages/axelar-wasm-std/src/msg_id/base_58_solana_event_index.rs b/packages/axelar-wasm-std/src/msg_id/base_58_solana_event_index.rs new file mode 100644 index 000000000..1a625f910 --- /dev/null +++ b/packages/axelar-wasm-std/src/msg_id/base_58_solana_event_index.rs @@ -0,0 +1,362 @@ +use core::fmt; +use std::fmt::Display; +use std::str::FromStr; + +use error_stack::{Report, ResultExt}; +use lazy_static::lazy_static; +use regex::Regex; + +use super::Error; +use crate::nonempty; + +type RawSignature = [u8; 64]; + +pub struct Base58SolanaTxSignatureAndEventIndex { + // Base58 decoded bytes of the Solana signature. + pub raw_signature: RawSignature, + pub event_index: u32, +} + +impl Base58SolanaTxSignatureAndEventIndex { + pub fn signature_as_base58(&self) -> nonempty::String { + bs58::encode(self.raw_signature) + .into_string() + .try_into() + .expect("failed to convert tx hash to non-empty string") + } + + pub fn new(tx_id: impl Into, event_index: impl Into) -> Self { + Self { + raw_signature: tx_id.into(), + event_index: event_index.into(), + } + } +} + +fn decode_b58_signature(signature: &str) -> Result> { + Ok(bs58::decode(signature) + .into_vec() + .change_context(Error::InvalidTxDigest(signature.to_string()))? + .as_slice() + .try_into() + .map_err(|_| Error::InvalidTxDigest(signature.to_owned()))?) +} + +const PATTERN: &str = "^([1-9A-HJ-NP-Za-km-z]{64,88})-(0|[1-9][0-9]*)$"; +lazy_static! { + static ref REGEX: Regex = Regex::new(PATTERN).expect("invalid regex"); +} + +impl FromStr for Base58SolanaTxSignatureAndEventIndex { + type Err = Report; + + fn from_str(message_id: &str) -> Result + where + Self: Sized, + { + // the PATTERN has exactly two capture groups, so the groups can be extracted safely + let (_, [signature, event_index]) = REGEX + .captures(message_id) + .ok_or(Error::InvalidMessageID { + id: message_id.to_string(), + expected_format: PATTERN.to_string(), + })? + .extract(); + + Ok(Base58SolanaTxSignatureAndEventIndex { + raw_signature: decode_b58_signature(signature)?, + event_index: event_index + .parse() + .map_err(|_| Error::EventIndexOverflow(message_id.to_string()))?, + }) + } +} + +impl Display for Base58SolanaTxSignatureAndEventIndex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}-{}", + bs58::encode(self.raw_signature).into_string(), + self.event_index + ) + } +} + +#[cfg(test)] +mod tests { + + use hex::ToHex; + + use super::*; + + fn random_bytes() -> RawSignature { + let mut bytes = [0; 64]; + for b in &mut bytes { + *b = rand::random(); + } + bytes + } + + fn random_tx_digest() -> String { + bs58::encode(random_bytes()).into_string() + } + + fn random_event_index() -> u32 { + rand::random() + } + + #[test] + fn should_parse_msg_id() { + let res = Base58SolanaTxSignatureAndEventIndex::from_str( + "4hHzKKdpXH2QMB5Jm11YR48cLqUJb9Cwq2YL3tveVTPeFkZaLP8cdcH5UphVPJ7kYwCUCRLnywd3xkUhb4ZYWtf5-0", + ); + assert!(res.is_ok()); + + for _ in 0..1000 { + let tx_digest = random_tx_digest(); + let event_index = random_event_index(); + let msg_id = format!("{}-{}", tx_digest, event_index); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&msg_id); + let parsed = res.unwrap(); + assert_eq!(parsed.event_index, event_index); + assert_eq!(parsed.signature_as_base58(), tx_digest.try_into().unwrap()); + assert_eq!(parsed.to_string(), msg_id); + } + } + + #[test] + fn should_not_parse_msg_id_with_wrong_length_base58_tx_digest() { + let tx_digest = random_tx_digest(); + let event_index = random_event_index(); + + // too long + let msg_id = format!("{}{}-{}", tx_digest, tx_digest, event_index); + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&msg_id); + assert!(res.is_err()); + + // too short + let msg_id = format!("{}-{}", &tx_digest[0..63], event_index); + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&msg_id); + assert!(res.is_err()); + } + + #[test] + fn leading_ones_should_not_be_ignored() { + let tx_digest = random_tx_digest(); + let event_index = random_event_index(); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "1{}-{}", + tx_digest, event_index + )); + assert!(res.is_err()); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "11{}-{}", + tx_digest, event_index + )); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_correct_length_base58_but_wrong_length_hex() { + // this is 88 chars and valid base58, but will decode to 66 bytes + // the leading 1s are encoded as 00 in hex and thus result in too many bytes + let tx_digest = "1111KKdpXH2QMB5Jm11YR48cLqUJb9Cwq2YL3tveVTPeFkZaLP8cdcH5UphVPJ7kYwCUCRLnywd3xkUhb4ZYWtf5"; + let event_index = random_event_index(); + let msg_id = format!("{}-{}", tx_digest, event_index); + + assert!(REGEX.captures(&msg_id).is_some()); + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&msg_id); + assert!(res.is_err()); + + // this is 88 chars and valid base 58, but will encode to 65 bytes + // (z is the largest base58 digit, and so this will overflow 2^512) + let tx_digest = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"; + assert_eq!(tx_digest.len(), 88); + let msg_id = format!("{}-{}", tx_digest, event_index); + + assert!(REGEX.captures(&msg_id).is_some()); + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&msg_id); + assert!(res.is_err()); + } + + #[test] + fn should_parse_msg_id_less_than_88_chars_tx_digest() { + // the tx digest can be less than 88 chars in the presence of leading 1s (00 in hex) + let tx_digest = + "1111KKdpXH2QMB5Jm11YR48cLqUJb9Cwq2YL3tveVTPeFkZaLP8cdcH5UphVPJ7kYwCUCRLnywd3xkUhb4ZYW"; + assert!(tx_digest.len() < 88); + let event_index = random_event_index(); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "{}-{}", + tx_digest, event_index + )); + assert!(res.is_ok()); + } + + #[test] + fn should_not_parse_msg_id_with_invalid_base58() { + let tx_digest = random_tx_digest(); + let event_index = random_event_index(); + + // 0, O and I are invalid base58 chars + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "0{}-{}", + &tx_digest[1..], + event_index + )); + assert!(res.is_err()); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "I{}-{}", + &tx_digest[1..], + event_index + )); + assert!(res.is_err()); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "O{}-{}", + &tx_digest[1..], + event_index + )); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_hex_tx_digest() { + let tx_digest = random_tx_digest(); + let event_index = random_event_index(); + let tx_digest_hex = bs58::decode(tx_digest) + .into_vec() + .unwrap() + .encode_hex::(); + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "{}-{}", + tx_digest_hex, event_index + )); + assert!(res.is_err()); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "0x{}-{}", + tx_digest_hex, event_index + )); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_missing_event_index() { + let msg_id = random_tx_digest(); + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&msg_id); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_wrong_separator() { + let tx_digest = random_tx_digest(); + let event_index = random_event_index(); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "{}:{}", + tx_digest, event_index + )); + assert!(res.is_err()); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "{}_{}", + tx_digest, event_index + )); + assert!(res.is_err()); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "{}+{}", + tx_digest, event_index + )); + assert!(res.is_err()); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "{}{}", + tx_digest, event_index + )); + assert!(res.is_err()); + + for _ in 0..10 { + let random_sep: char = rand::random(); + if random_sep == '-' { + continue; + } + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "{}{}{}", + tx_digest, random_sep, event_index + )); + assert!(res.is_err()); + } + } + + #[test] + fn should_not_parse_msg_id_with_event_index_with_leading_zeroes() { + let tx_digest = random_tx_digest(); + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!("{}-01", tx_digest)); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_non_integer_event_index() { + let tx_digest = random_tx_digest(); + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!("{}-1.0", tx_digest)); + assert!(res.is_err()); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!("{}-0x00", tx_digest)); + assert!(res.is_err()); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!("{}-foobar", tx_digest)); + assert!(res.is_err()); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!("{}-true", tx_digest)); + assert!(res.is_err()); + + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!("{}-", tx_digest)); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_overflowing_event_index() { + let event_index: u64 = u64::MAX; + let tx_digest = random_tx_digest(); + let res = Base58SolanaTxSignatureAndEventIndex::from_str(&format!( + "{}-{}", + tx_digest, event_index + )); + assert!(res.is_err()); + } + + #[test] + fn trimming_leading_ones_should_change_bytes() { + for _ in 0..100 { + let mut bytes = random_bytes(); + + // set a random (non-zero) number of leading bytes to 0 + let leading_zeroes = rand::random::() % bytes.len() + 1; + for b in bytes.iter_mut().take(leading_zeroes) { + *b = 0; + } + + let b58 = bs58::encode(&bytes).into_string(); + + // verify the base58 has the expected number of leading 1's + for c in b58.chars().take(leading_zeroes) { + assert_eq!(c, '1'); + } + + // trim a random (non-zero) number of leading 1's + let trim = rand::random::() % leading_zeroes + 1; + + // converting back to bytes should yield a different result + let decoded = bs58::decode(&b58[trim..]).into_vec().unwrap(); + assert_ne!(bytes.to_vec(), decoded); + } + } +} diff --git a/packages/axelar-wasm-std/src/msg_id/mod.rs b/packages/axelar-wasm-std/src/msg_id/mod.rs index 475c9b3b8..26c33de5b 100644 --- a/packages/axelar-wasm-std/src/msg_id/mod.rs +++ b/packages/axelar-wasm-std/src/msg_id/mod.rs @@ -1,14 +1,16 @@ -use std::{fmt::Display, str::FromStr}; +use std::fmt::Display; +use std::str::FromStr; use cosmwasm_schema::cw_serde; use error_stack::Report; -use self::{ - base_58_event_index::Base58TxDigestAndEventIndex, tx_hash_event_index::HexTxHashAndEventIndex, -}; +pub use self::base_58_event_index::Base58TxDigestAndEventIndex; +pub use self::base_58_solana_event_index::Base58SolanaTxSignatureAndEventIndex; +pub use self::tx_hash_event_index::HexTxHashAndEventIndex; -pub mod base_58_event_index; -pub mod tx_hash_event_index; +mod base_58_event_index; +mod base_58_solana_event_index; +mod tx_hash_event_index; #[derive(thiserror::Error)] #[cw_serde] @@ -39,6 +41,7 @@ pub trait MessageId: FromStr + Display {} pub enum MessageIdFormat { HexTxHashAndEventIndex, Base58TxDigestAndEventIndex, + Base58SolanaTxSignatureAndEventIndex, } // function the router calls to verify msg ids @@ -50,16 +53,17 @@ pub fn verify_msg_id(message_id: &str, format: &MessageIdFormat) -> Result<(), R MessageIdFormat::Base58TxDigestAndEventIndex => { Base58TxDigestAndEventIndex::from_str(message_id).map(|_| ()) } + MessageIdFormat::Base58SolanaTxSignatureAndEventIndex => { + Base58SolanaTxSignatureAndEventIndex::from_str(message_id).map(|_| ()) + } } } #[cfg(test)] mod test { - use crate::msg_id::{ - base_58_event_index::Base58TxDigestAndEventIndex, verify_msg_id, MessageIdFormat, - }; - use super::tx_hash_event_index::HexTxHashAndEventIndex; + use crate::msg_id::base_58_event_index::Base58TxDigestAndEventIndex; + use crate::msg_id::{verify_msg_id, MessageIdFormat}; #[test] fn should_verify_hex_tx_hash_event_index_msg_id() { diff --git a/packages/axelar-wasm-std/src/msg_id/tx_hash_event_index.rs b/packages/axelar-wasm-std/src/msg_id/tx_hash_event_index.rs index 42c764f7d..63be431d6 100644 --- a/packages/axelar-wasm-std/src/msg_id/tx_hash_event_index.rs +++ b/packages/axelar-wasm-std/src/msg_id/tx_hash_event_index.rs @@ -1,5 +1,6 @@ use core::fmt; -use std::{fmt::Display, str::FromStr}; +use std::fmt::Display; +use std::str::FromStr; use cosmwasm_std::HexBinary; use error_stack::{Report, ResultExt}; @@ -7,7 +8,8 @@ use lazy_static::lazy_static; use regex::Regex; use super::Error; -use crate::{hash::Hash, nonempty}; +use crate::hash::Hash; +use crate::nonempty; pub struct HexTxHashAndEventIndex { pub tx_hash: Hash, diff --git a/packages/axelar-wasm-std/src/nonempty/timestamp.rs b/packages/axelar-wasm-std/src/nonempty/timestamp.rs index 2501f8c36..2b1ec1ce4 100644 --- a/packages/axelar-wasm-std/src/nonempty/timestamp.rs +++ b/packages/axelar-wasm-std/src/nonempty/timestamp.rs @@ -1,6 +1,7 @@ -use crate::nonempty::Error; use cosmwasm_schema::cw_serde; +use crate::nonempty::Error; + #[cw_serde] pub struct Timestamp(cosmwasm_std::Timestamp); diff --git a/packages/axelar-wasm-std/src/nonempty/uint.rs b/packages/axelar-wasm-std/src/nonempty/uint.rs index 2f2a98fb1..de9eba31a 100644 --- a/packages/axelar-wasm-std/src/nonempty/uint.rs +++ b/packages/axelar-wasm-std/src/nonempty/uint.rs @@ -1,8 +1,9 @@ use std::fmt; -use crate::nonempty::Error; use cosmwasm_schema::cw_serde; +use crate::nonempty::Error; + #[cw_serde] #[serde(try_from = "cosmwasm_std::Uint64")] #[serde(into = "cosmwasm_std::Uint64")] @@ -110,6 +111,13 @@ impl From for cosmwasm_std::Uint128 { } } +impl TryFrom for Uint128 { + type Error = Error; + fn try_from(value: u128) -> Result { + cosmwasm_std::Uint128::from(value).try_into() + } +} + impl Uint128 { pub const fn one() -> Self { Self(cosmwasm_std::Uint128::one()) @@ -190,4 +198,10 @@ mod tests { let converted: &cosmwasm_std::Uint256 = val.as_ref(); assert_eq!(&val.0, converted); } + + #[test] + fn convert_from_uint128_to_non_empty_uint128() { + assert!(Uint128::try_from(0u128).is_err()); + assert!(Uint128::try_from(1u128).is_ok()); + } } diff --git a/packages/axelar-wasm-std/src/nonempty/vec.rs b/packages/axelar-wasm-std/src/nonempty/vec.rs index 874033ab0..eb9c68918 100644 --- a/packages/axelar-wasm-std/src/nonempty/vec.rs +++ b/packages/axelar-wasm-std/src/nonempty/vec.rs @@ -1,7 +1,8 @@ -use crate::nonempty::Error; use cosmwasm_schema::cw_serde; use cosmwasm_std::HexBinary; +use crate::nonempty::Error; + #[cw_serde] #[serde(try_from = "std::vec::Vec")] pub struct Vec(std::vec::Vec); diff --git a/packages/axelar-wasm-std/src/permission_control.rs b/packages/axelar-wasm-std/src/permission_control.rs index e9809286c..6320149ec 100644 --- a/packages/axelar-wasm-std/src/permission_control.rs +++ b/packages/axelar-wasm-std/src/permission_control.rs @@ -1,11 +1,13 @@ -use crate::flagset::FlagSet; -use crate::FnExt; +use std::fmt::{Debug, Display, Formatter}; + use cosmwasm_std::{Addr, StdResult}; use cw_storage_plus::Item; use flagset::{flags, Flags}; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use std::fmt::{Debug, Display, Formatter}; + +use crate::flagset::FlagSet; +use crate::FnExt; flags! { #[repr(u8)] @@ -43,77 +45,14 @@ pub enum Error { expected: FlagSet, actual: FlagSet, }, -} - -/// Ensure that the sender of a message has the correct permissions to perform following actions. -/// Returns an error if not. -/// # Example -/// ``` -/// # use cosmwasm_std::testing::mock_dependencies; -/// use cosmwasm_std::Addr; -/// use axelar_wasm_std::ensure_permission; -/// use axelar_wasm_std::permission_control::Permission; -///# use axelar_wasm_std::permission_control::Error; -/// -///# fn main() -> Result<(),Box>{ -///# use axelar_wasm_std::permission_control; -///# let mut deps = mock_dependencies(); -///# let deps = deps.as_mut(); -/// let admin = Addr::unchecked("admin"); -/// let governance = Addr::unchecked("governance"); -/// -/// // set these before checking permissions -/// permission_control::set_admin(deps.storage, &admin)?; -/// permission_control::set_governance(deps.storage, &governance)?; -/// -/// ensure_permission!(Permission::Elevated, deps.storage, &admin); -/// ensure_permission!(Permission::Elevated, deps.storage, &governance); -/// -/// do_something(); -/// # Ok(())} -/// -/// # fn do_something() {} -/// ``` -#[macro_export] -macro_rules! ensure_permission { - ($permission_variant:expr, $storage:expr, $sender:expr) => { - let permission = $crate::flagset::FlagSet::from($permission_variant); - - if !permission.contains($crate::permission_control::Permission::Any) { - let role = error_stack::ResultExt::change_context( - $crate::permission_control::sender_role($storage, $sender), - $crate::permission_control::Error::PermissionDenied { - expected: permission.clone(), - actual: Permission::NoPrivilege.into(), - }, - )?; - - if (*permission & *role).is_empty() { - return Err($crate::permission_control::Error::PermissionDenied { - expected: permission, - actual: role, - } - .into()); - } - } - }; -} - -/// This macro should be used as a marker to signify that the call is deliberately without checks -/// -/// # Example -/// ``` -///# fn main() -> Result<(),Box>{ -///# use axelar_wasm_std::{ensure_any_permission}; -/// ensure_any_permission!(); -/// do_something(); -/// # Ok(())} -/// -/// # fn do_something() {} -/// ``` -#[macro_export] -macro_rules! ensure_any_permission { - () => {}; + #[error("sender '{actual}' must be one of the addresses {expected:?}")] + AddressNotWhitelisted { expected: Vec, actual: Addr }, + #[error("no whitelisting condition found for sender address '{sender}'")] + WhitelistNotFound { sender: Addr }, + #[error("specific check called on wrong enum variant")] + WrongVariant, + #[error("sender is not authorized")] + Unauthorized, // generic error to handle errors that don't fall into the above cases } const ADMIN: Item = Item::new("permission_control_contract_admin_addr"); @@ -128,8 +67,7 @@ pub fn set_governance(storage: &mut dyn cosmwasm_std::Storage, addr: &Addr) -> S GOVERNANCE.save(storage, addr) } -// this is an implementation detail of the macro and shouldn't be called on its own -#[doc(hidden)] +/// Generally it shouldn't be necessary to call this function directly, use derived permission controlled functions instead #[allow(clippy::arithmetic_side_effects)] // flagset is safe pub fn sender_role( storage: &dyn cosmwasm_std::Storage, @@ -158,139 +96,12 @@ pub fn sender_role( #[cfg(test)] mod tests { - use super::*; use cosmwasm_std::testing::MockStorage; - use cosmwasm_std::Addr; - use error_stack::Report; - use flagset::Flags; - - #[test] - fn test_ensure_permission() { - let no_privilege = Addr::unchecked("regular user"); - let admin = Addr::unchecked("admin"); - let governance = Addr::unchecked("governance"); - - let check = |permission, user, storage| { - ensure_permission!(permission, storage, user); - Ok::<(), Report>(()) - }; - - // no addresses set: all addresses should be treated as not privileged - let storage = MockStorage::new(); - for permission in Permission::LIST { - match permission { - Permission::NoPrivilege | Permission::Any => { - assert!(check(*permission, &no_privilege, &storage).is_ok()); - assert!(check(*permission, &admin, &storage).is_ok()); - assert!(check(*permission, &governance, &storage).is_ok()); - } - // none of these can be called if no addresses are set - Permission::Admin | Permission::Governance | Permission::Elevated => { - assert!(check(*permission, &no_privilege, &storage).is_err()); - assert!(check(*permission, &admin, &storage).is_err()); - assert!(check(*permission, &governance, &storage).is_err()); - } - } - } - - // admin set: only admin should be allowed to call admin and elevated commands - let mut storage = MockStorage::new(); - set_admin(&mut storage, &admin).unwrap(); - - for permission in Permission::LIST { - match permission { - Permission::NoPrivilege => { - assert!(check(*permission, &no_privilege, &storage).is_ok()); - assert!(check(*permission, &admin, &storage).is_err()); // - assert!(check(*permission, &governance, &storage).is_ok()); - } - Permission::Admin | Permission::Elevated => { - assert!(check(*permission, &no_privilege, &storage).is_err()); - assert!(check(*permission, &admin, &storage).is_ok()); - assert!(check(*permission, &governance, &storage).is_err()); - } - // gov address is not set, so these should all fail - Permission::Governance => { - assert!(check(*permission, &no_privilege, &storage).is_err()); - assert!(check(*permission, &admin, &storage).is_err()); - assert!(check(*permission, &governance, &storage).is_err()); - } - Permission::Any => { - assert!(check(*permission, &no_privilege, &storage).is_ok()); - assert!(check(*permission, &admin, &storage).is_ok()); - assert!(check(*permission, &governance, &storage).is_ok()); - } - } - } - - // governance set: only governance should be allowed to call governance and elevated commands - let mut storage = MockStorage::new(); - set_governance(&mut storage, &governance).unwrap(); - - for permission in Permission::LIST { - match permission { - Permission::NoPrivilege => { - assert!(check(*permission, &no_privilege, &storage).is_ok()); - assert!(check(*permission, &admin, &storage).is_ok()); - assert!(check(*permission, &governance, &storage).is_err()); - } - // admin address is not set, so these should all fail - Permission::Admin => { - assert!(check(*permission, &no_privilege, &storage).is_err()); - assert!(check(*permission, &admin, &storage).is_err()); - assert!(check(*permission, &governance, &storage).is_err()); - } - Permission::Governance | Permission::Elevated => { - assert!(check(*permission, &no_privilege, &storage).is_err()); - assert!(check(*permission, &admin, &storage).is_err()); - assert!(check(*permission, &governance, &storage).is_ok()); - } - Permission::Any => { - assert!(check(*permission, &no_privilege, &storage).is_ok()); - assert!(check(*permission, &admin, &storage).is_ok()); - assert!(check(*permission, &governance, &storage).is_ok()); - } - } - } - - // admin and governance set: both should be allowed to call admin, governance, and elevated commands - let mut storage = MockStorage::new(); - set_admin(&mut storage, &admin).unwrap(); - set_governance(&mut storage, &governance).unwrap(); - for permission in Permission::LIST { - match permission { - Permission::NoPrivilege => { - assert!(check(*permission, &no_privilege, &storage).is_ok()); - assert!(check(*permission, &admin, &storage).is_err()); - assert!(check(*permission, &governance, &storage).is_err()); - } - Permission::Admin => { - assert!(check(*permission, &no_privilege, &storage).is_err()); - assert!(check(*permission, &admin, &storage).is_ok()); - assert!(check(*permission, &governance, &storage).is_err()); - } - Permission::Governance => { - assert!(check(*permission, &no_privilege, &storage).is_err()); - assert!(check(*permission, &admin, &storage).is_err()); - assert!(check(*permission, &governance, &storage).is_ok()); - } - Permission::Elevated => { - assert!(check(*permission, &no_privilege, &storage).is_err()); - assert!(check(*permission, &admin, &storage).is_ok()); - assert!(check(*permission, &governance, &storage).is_ok()); - } - Permission::Any => { - assert!(check(*permission, &no_privilege, &storage).is_ok()); - assert!(check(*permission, &admin, &storage).is_ok()); - assert!(check(*permission, &governance, &storage).is_ok()); - } - } - } - } + use super::*; #[test] - fn test() { + fn display_permissions() { assert_eq!(format!("{}", FlagSet::from(Permission::Admin)), "Admin"); assert_eq!( format!( @@ -307,4 +118,54 @@ mod tests { "Any" ); } + + #[test] + fn sender_role_from_storage() { + let admin = Addr::unchecked("admin"); + let governance = Addr::unchecked("governance"); + let regular_user = Addr::unchecked("regular user"); + + let mut storage = MockStorage::new(); + set_admin(&mut storage, &admin).unwrap(); + set_governance(&mut storage, &governance).unwrap(); + + assert_eq!( + sender_role(&storage, &admin).unwrap(), + FlagSet::from(Permission::Admin) + ); + assert_eq!( + sender_role(&storage, &governance).unwrap(), + FlagSet::from(Permission::Governance) + ); + assert_eq!( + sender_role(&storage, ®ular_user).unwrap(), + FlagSet::from(Permission::NoPrivilege) + ); + + set_governance(&mut storage, &admin).unwrap(); + assert_eq!( + sender_role(&storage, &admin).unwrap(), + FlagSet::from(Permission::Elevated) + ); + } + + #[test] + fn permission_level_correctly_defined() { + assert!(!FlagSet::from(Permission::NoPrivilege).contains(Permission::Admin)); + assert!(!FlagSet::from(Permission::NoPrivilege).contains(Permission::Governance)); + + assert!(!FlagSet::from(Permission::Admin).contains(Permission::NoPrivilege)); + assert!(!FlagSet::from(Permission::Admin).contains(Permission::Governance)); + + assert!(!FlagSet::from(Permission::Governance).contains(Permission::NoPrivilege)); + assert!(!FlagSet::from(Permission::Governance).contains(Permission::Admin)); + + assert!(!FlagSet::from(Permission::Elevated).contains(Permission::NoPrivilege)); + assert!(FlagSet::from(Permission::Elevated).contains(Permission::Admin)); + assert!(FlagSet::from(Permission::Elevated).contains(Permission::Governance)); + + assert!(FlagSet::from(Permission::Any).contains(Permission::NoPrivilege)); + assert!(FlagSet::from(Permission::Any).contains(Permission::Admin)); + assert!(FlagSet::from(Permission::Any).contains(Permission::Governance)); + } } diff --git a/packages/axelar-wasm-std/src/snapshot.rs b/packages/axelar-wasm-std/src/snapshot.rs index ec7956d03..165966f6b 100644 --- a/packages/axelar-wasm-std/src/snapshot.rs +++ b/packages/axelar-wasm-std/src/snapshot.rs @@ -3,7 +3,8 @@ use std::collections::HashMap; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; -use crate::{nonempty, threshold::MajorityThreshold}; +use crate::nonempty; +use crate::threshold::MajorityThreshold; #[cw_serde] pub struct Participant { @@ -44,7 +45,7 @@ impl Snapshot { } } - pub fn get_participants(&self) -> Vec { + pub fn participants(&self) -> Vec { self.participants .keys() .cloned() @@ -52,7 +53,7 @@ impl Snapshot { .collect() } - pub fn get_participant(&self, participant: &Addr) -> Option<&Participant> { + pub fn find(&self, participant: &Addr) -> Option<&Participant> { self.participants.get(&participant.to_string()) } } @@ -61,9 +62,8 @@ impl Snapshot { mod tests { use cosmwasm_std::{from_json, to_json_binary, Uint64}; - use crate::Threshold; - use super::*; + use crate::Threshold; fn mock_participant(address: &str, weight: nonempty::Uint128) -> Participant { Participant { diff --git a/packages/axelar-wasm-std/src/voting.rs b/packages/axelar-wasm-std/src/voting.rs index 4ee555195..e3eae4f97 100644 --- a/packages/axelar-wasm-std/src/voting.rs +++ b/packages/axelar-wasm-std/src/voting.rs @@ -14,25 +14,20 @@ on whether the transaction was successfully verified. */ use std::array::TryFromSliceError; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use std::fmt; -use std::ops::Add; -use std::ops::Mul; +use std::ops::{Add, Mul}; use std::str::FromStr; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, StdError, StdResult, Uint128, Uint64}; -use cw_storage_plus::{IntKey, Key, KeyDeserialize, PrimaryKey}; -use num_traits::CheckedAdd; -use num_traits::One; -use strum::EnumIter; -use strum::EnumString; -use strum::IntoEnumIterator; +use cw_storage_plus::{IntKey, Key, KeyDeserialize, Prefixer, PrimaryKey}; +use num_traits::{CheckedAdd, One}; +use strum::{EnumIter, EnumString, IntoEnumIterator}; use thiserror::Error; use valuable::Valuable; -use crate::nonempty; -use crate::Snapshot; +use crate::{nonempty, Snapshot}; #[derive(Error, Debug, PartialEq, Eq)] pub enum Error { @@ -133,6 +128,12 @@ impl<'a> PrimaryKey<'a> for PollId { } } +impl<'a> Prefixer<'a> for PollId { + fn prefix(&self) -> Vec { + vec![Key::Val64(self.0.to_be_bytes())] + } +} + impl KeyDeserialize for PollId { type Output = Self; @@ -223,9 +224,9 @@ impl PollResults { self.0 .into_iter() .zip(rhs.0) - .filter_map(|(lhs, rhs)| { + .map(|(lhs, rhs)| { if lhs.is_some() && rhs.is_none() { - Some(lhs) + lhs } else { None } @@ -246,23 +247,24 @@ pub struct PollState { #[cw_serde] pub enum PollStatus { InProgress, + Expired, Finished, } #[cw_serde] pub struct Participation { pub weight: nonempty::Uint128, - pub vote: Option>, + pub voted: bool, } #[cw_serde] pub struct WeightedPoll { pub poll_id: PollId, pub quorum: nonempty::Uint128, - pub expires_at: u64, + expires_at: u64, pub poll_size: u64, pub tallies: Vec, // running tally of weighted votes - pub status: PollStatus, + finished: bool, pub participation: BTreeMap, } @@ -278,7 +280,7 @@ impl WeightedPoll { address, Participation { weight: participant.weight, - vote: None, + voted: false, }, ) }) @@ -290,13 +292,13 @@ impl WeightedPoll { expires_at: expiry, poll_size: poll_size as u64, tallies: vec![Tallies::default(); poll_size], - status: PollStatus::InProgress, + finished: false, participation, } } pub fn finish(mut self, block_height: u64) -> Result { - if matches!(self.status, PollStatus::Finished { .. }) { + if self.finished { return Err(Error::PollNotInProgress); } @@ -304,12 +306,22 @@ impl WeightedPoll { return Err(Error::PollNotEnded); } - self.status = PollStatus::Finished; + self.finished = true; Ok(self) } - pub fn state(&self) -> PollState { + pub fn results(&self) -> PollResults { + let quorum: Uint128 = self.quorum.into(); + PollResults( + self.tallies + .iter() + .map(|tallies| tallies.consensus(quorum)) + .collect(), + ) + } + + pub fn state(&self, voting_history: HashMap>) -> PollState { let quorum: Uint128 = self.quorum.into(); let results: Vec> = self .tallies @@ -320,10 +332,11 @@ impl WeightedPoll { let consensus_participants = self .participation .iter() - .filter_map(|(address, participation)| { - participation.vote.as_ref().and_then(|votes| { + .filter_map(|(address, _)| { + voting_history.get(address).and_then(|votes| { let voted_consensus = votes.iter().zip(results.iter()).all(|(vote, result)| { - result.is_none() || Some(vote) == result.as_ref() // if there was no consensus, we don't care about the vote + result.is_none() || Some(vote) == result.as_ref() + // if there was no consensus, we don't care about the vote }); if voted_consensus { @@ -369,7 +382,7 @@ impl WeightedPoll { return Err(Error::InvalidVoteSize); } - if participation.vote.is_some() { + if participation.voted { return Err(Error::AlreadyVoted); } @@ -380,21 +393,28 @@ impl WeightedPoll { tallies.tally(vote, &participation.weight.into()); }); - participation.vote = Some(votes); + participation.voted = true; Ok(self) } + + pub fn status(&self, current_height: u64) -> PollStatus { + match self.finished { + true => PollStatus::Finished, + false if current_height >= self.expires_at => PollStatus::Expired, + _ => PollStatus::InProgress, + } + } } #[cfg(test)] mod tests { use cosmwasm_std::{Addr, Uint64}; use rand::distributions::Alphanumeric; - use rand::{thread_rng, Rng}; - - use crate::{nonempty, Participant, Threshold}; + use rand::Rng; use super::*; + use crate::{nonempty, Participant, Threshold}; #[test] fn cast_vote() { @@ -405,7 +425,7 @@ mod tests { poll.participation.get("addr1").unwrap(), &Participation { weight: nonempty::Uint128::try_from(Uint128::from(100u64)).unwrap(), - vote: None, + voted: false } ); @@ -417,14 +437,14 @@ mod tests { poll.participation.get("addr1").unwrap(), &Participation { weight: nonempty::Uint128::try_from(Uint128::from(100u64)).unwrap(), - vote: Some(votes), + voted: true } ); } #[test] fn voter_not_a_participant() { - let mut rng = thread_rng(); + let mut rng = rand::thread_rng(); let poll = new_poll( rng.gen::(), rng.gen_range(1..50), @@ -432,7 +452,7 @@ mod tests { ); let votes = vec![Vote::SucceededOnChain, Vote::SucceededOnChain]; - let rand_addr: String = thread_rng() + let rand_addr: String = rand::thread_rng() .sample_iter(&Alphanumeric) .take(5) .map(char::from) @@ -491,25 +511,31 @@ mod tests { #[test] fn finish_after_poll_conclude() { let mut poll = new_poll(2, 2, vec!["addr1", "addr2"]); - poll.status = PollStatus::Finished; - assert_eq!(poll.finish(2), Err(Error::PollNotInProgress)); + poll = poll.finish(2).unwrap(); + assert_eq!(poll.finish(3), Err(Error::PollNotInProgress)); } #[test] fn should_conclude_poll() { let poll = new_poll(2, 2, vec!["addr1", "addr2", "addr3"]); let votes = vec![Vote::SucceededOnChain, Vote::SucceededOnChain]; + let voters = [Addr::unchecked("addr1"), Addr::unchecked("addr2")]; let poll = poll - .cast_vote(1, &Addr::unchecked("addr1"), votes.clone()) + .cast_vote(1, &voters[0], votes.clone()) .unwrap() - .cast_vote(1, &Addr::unchecked("addr2"), votes) + .cast_vote(1, &voters[1], votes.clone()) .unwrap(); let poll = poll.finish(2).unwrap(); - assert_eq!(poll.status, PollStatus::Finished); + assert_eq!(poll.status(2), PollStatus::Finished); - let result = poll.state(); + let result = poll.state( + voters + .iter() + .map(|voter| (voter.to_string(), votes.clone())) + .collect(), + ); assert_eq!( result, PollState { @@ -528,16 +554,37 @@ mod tests { let poll = new_poll(2, 2, vec!["addr1", "addr2", "addr3"]); let votes = vec![Vote::SucceededOnChain, Vote::SucceededOnChain]; let wrong_votes = vec![Vote::FailedOnChain, Vote::FailedOnChain]; + let voters = [ + Addr::unchecked("addr1"), + Addr::unchecked("addr2"), + Addr::unchecked("addr3"), + ]; + let voting_history: Vec<(&Addr, Vec)> = voters + .iter() + .enumerate() + .map(|(idx, voter)| { + if idx == 1 { + (voter, wrong_votes.clone()) + } else { + (voter, votes.clone()) + } + }) + .collect(); let poll = poll - .cast_vote(1, &Addr::unchecked("addr1"), votes.clone()) + .cast_vote(1, voting_history[0].0, voting_history[0].1.clone()) .unwrap() - .cast_vote(1, &Addr::unchecked("addr2"), wrong_votes) + .cast_vote(1, voting_history[1].0, voting_history[1].1.clone()) .unwrap() - .cast_vote(1, &Addr::unchecked("addr3"), votes) + .cast_vote(1, voting_history[2].0, voting_history[2].1.clone()) .unwrap(); - let result = poll.finish(2).unwrap().state(); + let result = poll.finish(2).unwrap().state( + voting_history + .into_iter() + .map(|(voter, votes)| (voter.to_string(), votes)) + .collect(), + ); assert_eq!( result, @@ -552,6 +599,15 @@ mod tests { ); } + #[test] + fn status_should_return_current_status() { + let mut poll = new_poll(2, 2, vec!["addr1", "addr2"]); + assert_eq!(poll.status(1), PollStatus::InProgress); + assert_eq!(poll.status(2), PollStatus::Expired); + poll = poll.finish(3).unwrap(); + assert_eq!(poll.status(3), PollStatus::Finished); + } + fn new_poll(expires_at: u64, poll_size: usize, participants: Vec<&str>) -> WeightedPoll { let participants: nonempty::Vec = participants .into_iter() diff --git a/packages/client/Cargo.toml b/packages/client/Cargo.toml index 8cd9ca5c1..e00f7f6f3 100644 --- a/packages/client/Cargo.toml +++ b/packages/client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "client" -version = "0.1.0" +version = "1.0.0" edition = "2021" rust-version = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/packages/client/src/lib.rs b/packages/client/src/lib.rs index 87e1df2d4..49864d1b8 100644 --- a/packages/client/src/lib.rs +++ b/packages/client/src/lib.rs @@ -4,7 +4,8 @@ use cosmwasm_std::{ to_json_binary, Addr, QuerierWrapper, QueryRequest, StdError, WasmMsg, WasmQuery, }; use error_stack::{Report, Result}; -use serde::{de::DeserializeOwned, Serialize}; +use serde::de::DeserializeOwned; +use serde::Serialize; #[derive(thiserror::Error, Debug)] pub enum Error { diff --git a/packages/events-derive/Cargo.toml b/packages/events-derive/Cargo.toml index 630f8b3d1..a6e91c5bf 100644 --- a/packages/events-derive/Cargo.toml +++ b/packages/events-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "events-derive" -version = "0.1.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/packages/events/Cargo.toml b/packages/events/Cargo.toml index 91ca72d8f..5b354577e 100644 --- a/packages/events/Cargo.toml +++ b/packages/events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "events" -version = "0.1.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/packages/events/src/event.rs b/packages/events/src/event.rs index d39ce5925..ab7fcd1b4 100644 --- a/packages/events/src/event.rs +++ b/packages/events/src/event.rs @@ -3,16 +3,14 @@ use std::fmt::Display; use axelar_wasm_std::FnExt; use base64::engine::general_purpose::STANDARD; use base64::Engine; +use cosmrs::AccountId; use error_stack::{Report, Result, ResultExt}; -use serde_json::Value; use tendermint::abci::EventAttribute; use tendermint::{abci, block}; use crate::errors::DecodingError; use crate::Error; -use cosmrs::AccountId; - #[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { BlockBegin(block::Height), @@ -91,7 +89,7 @@ impl TryFrom for Event { } } -fn try_into_kv_pair(attr: &EventAttribute) -> Result<(String, Value), Error> { +fn try_into_kv_pair(attr: &EventAttribute) -> Result<(String, serde_json::Value), Error> { decode_event_attribute(attr) .change_context(Error::DecodingAttributesFailed) .map(|(key, value)| { diff --git a/packages/evm-gateway/Cargo.toml b/packages/evm-gateway/Cargo.toml index 6d3916050..7a1a48721 100644 --- a/packages/evm-gateway/Cargo.toml +++ b/packages/evm-gateway/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evm-gateway" -version = "0.1.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/packages/evm-gateway/src/lib.rs b/packages/evm-gateway/src/lib.rs index 177d50eb9..5354a7c6c 100644 --- a/packages/evm-gateway/src/lib.rs +++ b/packages/evm-gateway/src/lib.rs @@ -1,30 +1,25 @@ -pub mod error; +use std::str::FromStr; +use axelar_wasm_std::hash::Hash; +use axelar_wasm_std::FnExt; use cosmwasm_std::Uint256; use error_stack::{Report, ResultExt}; use ethers_contract::abigen; -use ethers_core::{ - abi::{ - encode, - Token::{self, Tuple, Uint}, - Tokenize, - }, - types::{Address, Bytes, U256}, - utils::{parse_checksummed, public_key_to_address}, -}; +use ethers_core::abi::Token::{self, Tuple, Uint}; +use ethers_core::abi::{encode, Tokenize}; +use ethers_core::types::{Address, Bytes, U256}; +use ethers_core::utils::public_key_to_address; use k256::ecdsa::VerifyingKey; -use sha3::{Digest, Keccak256}; - -use axelar_wasm_std::hash::Hash; -use multisig::{ - key::PublicKey, - msg::{Signer, SignerWithSig}, - verifier_set::VerifierSet, -}; +use multisig::key::PublicKey; +use multisig::msg::{Signer, SignerWithSig}; +use multisig::verifier_set::VerifierSet; use router_api::Message as RouterMessage; +use sha3::{Digest, Keccak256}; use crate::error::Error; +pub mod error; + // Generates the bindings for the Axelar Amplifier Gateway contract. // This includes the defined structs: Messages, WeightedSigners, WeightedSigner, and Proofs. abigen!( @@ -79,12 +74,16 @@ impl TryFrom<&RouterMessage> for Message { type Error = Report; fn try_from(msg: &RouterMessage) -> Result { - let contract_address = parse_checksummed(msg.destination_address.as_str(), None) + let contract_address = msg + .destination_address + .as_str() + .then(|addr| addr.strip_prefix("0x").unwrap_or(addr)) + .then(Address::from_str) .change_context(Error::InvalidAddress)?; Ok(Message { - source_chain: msg.cc_id.chain.to_string(), - message_id: msg.cc_id.id.to_string(), + source_chain: msg.cc_id.source_chain.to_string(), + message_id: msg.cc_id.message_id.to_string(), source_address: msg.source_address.to_string(), contract_address, payload_hash: msg.payload_hash, @@ -144,11 +143,13 @@ pub fn evm_address(pub_key: &PublicKey) -> Result> { #[cfg(test)] mod test { - use cosmwasm_std::{Addr, HexBinary, Uint128}; - use ethers_core::utils::to_checksum; + use std::str::FromStr; - use axelar_wasm_std::{nonempty, snapshot::Participant}; - use multisig::{key::PublicKey, verifier_set::VerifierSet}; + use axelar_wasm_std::nonempty; + use axelar_wasm_std::snapshot::Participant; + use cosmwasm_std::{Addr, HexBinary, Uint128}; + use multisig::key::PublicKey; + use multisig::verifier_set::VerifierSet; use router_api::{CrossChainId, Message as RouterMessage}; use crate::{Message, WeightedSigners}; @@ -172,32 +173,40 @@ mod test { let message_id = "0xff822c88807859ff226b58e24f24974a70f04b9442501ae38fd665b3c68f3834-0"; let source_address = "0x52444f1835Adc02086c37Cb226561605e2E1699b"; let destination_chain = "chain1"; - let destination_address = "0xA4f10f76B86E01B98daF66A3d02a65e14adb0767"; let payload_hash = "8c3685dc41c2eca11426f8035742fb97ea9f14931152670a5703f18fe8b392f0"; + let destination_addresses = vec![ + "0xa4f10f76b86e01b98daf66a3d02a65e14adb0767", // all lowercase + "0xA4f10f76B86E01B98daF66A3d02a65e14adb0767", // checksummed + "a4f10f76b86e01b98daf66a3d02a65e14adb0767", // no 0x prefix + ]; - let router_messages = RouterMessage { - cc_id: CrossChainId { - chain: source_chain.parse().unwrap(), - id: message_id.parse().unwrap(), - }, - source_address: source_address.parse().unwrap(), - destination_address: destination_address.parse().unwrap(), - destination_chain: destination_chain.parse().unwrap(), - payload_hash: HexBinary::from_hex(payload_hash) + for destination_address in destination_addresses { + let router_messages = RouterMessage { + cc_id: CrossChainId::new(source_chain, message_id).unwrap(), + source_address: source_address.parse().unwrap(), + destination_address: destination_address.parse().unwrap(), + destination_chain: destination_chain.parse().unwrap(), + payload_hash: HexBinary::from_hex(payload_hash) + .unwrap() + .to_array::<32>() + .unwrap(), + }; + + let gateway_message = Message::try_from(&router_messages).unwrap(); + assert_eq!(gateway_message.source_chain, source_chain); + assert_eq!(gateway_message.message_id, message_id); + assert_eq!(gateway_message.source_address, source_address); + assert_eq!( + gateway_message.contract_address, + ethers_core::types::Address::from_str( + destination_address + .strip_prefix("0x") + .unwrap_or(destination_address) + ) .unwrap() - .to_array::<32>() - .unwrap(), - }; - - let gateway_message = Message::try_from(&router_messages).unwrap(); - assert_eq!(gateway_message.source_chain, source_chain); - assert_eq!(gateway_message.message_id, message_id); - assert_eq!(gateway_message.source_address, source_address); - assert_eq!( - to_checksum(&gateway_message.contract_address, None), - destination_address - ); - assert_eq!(gateway_message.payload_hash, router_messages.payload_hash); + ); + assert_eq!(gateway_message.payload_hash, router_messages.payload_hash); + } } // Generate a worker set matches axelar-gmp-sdk-solidity repo test data diff --git a/packages/gateway-api/Cargo.toml b/packages/gateway-api/Cargo.toml index 0ff5309ce..874db1ac5 100644 --- a/packages/gateway-api/Cargo.toml +++ b/packages/gateway-api/Cargo.toml @@ -1,12 +1,16 @@ [package] name = "gateway-api" -version = "0.1.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +axelar-wasm-std = { workspace = true } cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +error-stack = { workspace = true } +msgs-derive = { workspace = true } router-api = { workspace = true } [lints] diff --git a/packages/gateway-api/src/msg.rs b/packages/gateway-api/src/msg.rs index 601fb25a5..40c0bc590 100644 --- a/packages/gateway-api/src/msg.rs +++ b/packages/gateway-api/src/msg.rs @@ -1,16 +1,18 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; +use msgs_derive::EnsurePermissions; use router_api::{CrossChainId, Message}; #[cw_serde] +#[derive(EnsurePermissions)] pub enum ExecuteMsg { - // Permissionless /// Before messages that are unknown to the system can be routed, they need to be verified. /// Use this call to trigger verification for any of the given messages that is still unverified. + #[permission(Any)] VerifyMessages(Vec), - // Permissionless /// Forward the given messages to the next step of the routing layer. If these messages are coming in from an external chain, /// they have to be verified first. + #[permission(Any)] RouteMessages(Vec), } @@ -19,5 +21,5 @@ pub enum ExecuteMsg { pub enum QueryMsg { // messages that can be relayed to the chain corresponding to this gateway #[returns(Vec)] - GetOutgoingMessages { message_ids: Vec }, + OutgoingMessages(Vec), } diff --git a/packages/msgs-derive/Cargo.toml b/packages/msgs-derive/Cargo.toml new file mode 100644 index 000000000..495c17ef6 --- /dev/null +++ b/packages/msgs-derive/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "msgs-derive" +version = "1.0.0" +edition = "2021" +rust-version = { workspace = true } + +[lib] +proc-macro = true + +[dependencies] +axelar-wasm-std = { workspace = true } +cosmwasm-std = { workspace = true } +error-stack = { workspace = true } +itertools = { workspace = true } +proc-macro2 = "1.0.85" +quote = { workspace = true } +syn = { workspace = true } + +[dev-dependencies] + +[lints] +workspace = true diff --git a/packages/msgs-derive/release.toml b/packages/msgs-derive/release.toml new file mode 100644 index 000000000..954564097 --- /dev/null +++ b/packages/msgs-derive/release.toml @@ -0,0 +1 @@ +release = false diff --git a/packages/msgs-derive/src/lib.rs b/packages/msgs-derive/src/lib.rs new file mode 100644 index 000000000..10b488f02 --- /dev/null +++ b/packages/msgs-derive/src/lib.rs @@ -0,0 +1,359 @@ +use axelar_wasm_std::permission_control::Permission; +use itertools::Itertools; +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::punctuated::Punctuated; +use syn::token::Comma; +use syn::{Data, DataEnum, DeriveInput, Expr, ExprCall, Ident, Path, Token, Variant}; + +/// This macro derives the `ensure_permissions` method for an enum. The method checks if the sender +/// has the required permissions to execute the variant. The permissions are defined using the +/// `#[permission]` attribute. The attribute can be used in two ways: +/// - `#[permission(Permission1, Permission2, ...)]` requires the sender to have at least one of +/// the specified permissions. These permissions are defined in the [axelar_wasm_std::permission_control::Permission] enum. +/// - `#[permission(Specific(Addr1, Addr2, ...))]` requires the sender to be one of the specified +/// addresses. The macro will generate a function signature that takes closures as arguments to determine +/// the whitelisted addresses. +/// +/// Both attributes can be used together, in which case the sender must have at least one of the +/// specified permissions or be one of the specified addresses. +/// The `ensure_permissions` method will return an error if the sender does not have the required +/// permissions. +/// +/// # Example +/// ``` +/// use cosmwasm_std::{Addr, Deps, Env, MessageInfo}; +/// use axelar_wasm_std::permission_control::Permission; +/// use msgs_derive::EnsurePermissions; +/// +/// #[derive(EnsurePermissions)] +/// pub enum ExecuteMsg { +/// #[permission(NoPrivilege, Admin)] +/// AnyoneButGovernanceCanCallThis, +/// #[permission(Governance)] +/// OnlyGovernanceCanCallThis, +/// #[permission(Admin, Specific(gateway))] +/// AdminOrGatewayCanCallThis, +/// #[permission(Specific(gateway))] +/// OnlyGatewayCanCallThis +/// } +/// +/// fn execute(deps: Deps, env: Env, info: MessageInfo, msg: ExecuteMsg) -> error_stack::Result<(), axelar_wasm_std::permission_control::Error> { +/// // check permissions before handling the message +/// match msg.ensure_permissions(deps.storage, &info.sender, |storage, message | GATEWAY.load(storage))? { +/// ExecuteMsg::AnyoneButGovernanceCanCallThis => Ok(()), +/// ExecuteMsg::OnlyGovernanceCanCallThis => Ok(()), +/// ExecuteMsg::AdminOrGatewayCanCallThis => Ok(()), +/// ExecuteMsg::OnlyGatewayCanCallThis => Ok(()), +/// } +/// } +/// +/// # // mock to make the example compile +/// # struct Store; +/// # impl Store { +/// # fn load(&self, storage: &dyn cosmwasm_std::Storage) -> error_stack::Result { +/// # Ok(Addr::unchecked("gateway")) +/// # } +/// # } +/// # const GATEWAY:Store = Store; +/// # use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; +/// # fn main() { +/// # let mocks = mock_dependencies(); +/// # let deps = mocks.as_ref(); +/// # let env = mock_env(); +/// // example how to call the execute function +/// let info = MessageInfo{ +/// sender: Addr::unchecked("sender"), +/// funds: vec![], +/// }; +/// +/// # let info_root = info; +/// # let info = info_root.clone(); +/// assert!(execute(deps, env, info, ExecuteMsg::AnyoneButGovernanceCanCallThis).is_ok()); +/// # let env = mock_env(); +/// # let info = info_root.clone(); +/// assert!(execute(deps, env, info, ExecuteMsg::OnlyGatewayCanCallThis).is_err()); +/// # } +/// ``` + +#[proc_macro_derive(EnsurePermissions, attributes(permission))] +pub fn derive_ensure_permissions(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as DeriveInput); + let ident = input.ident.clone(); + + match input.data.clone() { + Data::Enum(data) => build_implementation(ident, data), + _ => panic!("Only enums are supported"), + } +} +fn build_implementation(enum_type: Ident, data: DataEnum) -> TokenStream { + let (variants, permissions): (Vec<_>, Vec<_>) = data + .variants + .into_iter() + .filter_map(find_permissions) + .unzip(); + + let specific_check = build_specific_permissions_check(&enum_type, &variants, &permissions); + let general_check = build_general_permissions_check(&enum_type, &variants, &permissions); + let check_function = build_full_check_function(&permissions, specific_check, general_check); + + TokenStream::from(quote! { + impl #enum_type{ + #check_function + } + }) +} + +#[derive(Debug)] +struct MsgPermissions { + specific: Vec, + general: Vec, +} + +fn find_permissions(variant: Variant) -> Option<(Ident, MsgPermissions)> { + let (specific, general): (Vec<_>, Vec<_>) = variant + .attrs + .iter() + .filter(|attr| attr.path().is_ident("permission")) + .flat_map(|attr| + { + match attr. + parse_args_with(Punctuated::::parse_terminated){ + Ok(expr) => expr, + _=> panic!("wrong format of 'permission' attribute for variant {}", variant.ident) + } + }) + .map(|expr| match expr { + Expr::Path(path) => (None, Some(path.path)), + Expr::Call(ExprCall { args, func, .. }) => { + let paths = parse_specific_permissions(&variant, args); + if !is_specific_attribute(&func) { + panic!( + "unrecognized permission attribute for variant {}, suggestion: 'Specific(...)'?", + variant.ident + ); + } + (Some(paths), None) + } + expr => + panic!( + "unrecognized permission attribute '{}' for variant {}", + quote! {#expr}, variant.ident + ) + }) + .unzip(); + + let specific: Vec = specific.into_iter().flatten().flatten().collect(); + let general: Vec = general.into_iter().flatten().collect(); + + if !general.iter().all_unique() { + panic!("permissions for variant {} must be unique", variant.ident); + } + + if !specific.iter().all_unique() { + panic!( + "whitelisted addresses for variant {} must be unique", + variant.ident + ); + } + + if general.is_empty() && specific.is_empty() { + panic!( + "permissions for variant {} must not be empty", + variant.ident + ); + } + + if general.iter().any(is_permission_any) && !specific.is_empty() { + panic!( + "whitelisting addresses for variant {} is useless because permission '{:?}' is set", + variant.ident, + Permission::Any + ); + } + + Some((variant.ident, MsgPermissions { specific, general })) +} + +fn is_specific_attribute(func: &Expr) -> bool { + match func { + Expr::Path(path) => path.path.is_ident("Specific"), + _ => false, + } +} + +fn parse_specific_permissions( + variant: &Variant, + args: Punctuated, +) -> impl IntoIterator + '_ { + args.into_iter().map(|arg| match arg { + Expr::Path(path) => path.path, + _ => panic!("wrong format of 'Specific' permission attribute for variant {}, only comma separated identifiers are allowed", variant.ident), + }) +} + +fn is_permission_any(path: &Path) -> bool { + path.get_ident() + .filter(|ident| ident.to_string() == format!("{:?}", Permission::Any)) + .is_some() +} + +fn build_specific_permissions_check( + enum_type: &Ident, + variants: &[Ident], + permissions: &[MsgPermissions], +) -> proc_macro2::TokenStream { + let specific_permissions = permissions.iter().map(|permission| { + let specific_permissions: &[_] = permission.specific.as_ref(); + + if permission.specific.is_empty() { + // don't do anything if there are no specific permissions + quote! {();} + } else { + // load all whitelisted addresses from storage and check if the sender is whitelisted + quote! { + #( + let stored_addr = error_stack::ResultExt::change_context( + #specific_permissions(storage, &self).map_err(|err| error_stack::Report::from(err)), + axelar_wasm_std::permission_control::Error::WhitelistNotFound{sender: sender.clone()})?; + if sender == stored_addr { + return Ok(self); + } + whitelisted.push(stored_addr); + )* + } + } + }); + + // map enum variants to specific permission checks + quote! { + let mut whitelisted = Vec::new(); + match self { + #(#enum_type::#variants {..}=> {#specific_permissions})* + }; + } +} + +fn build_general_permissions_check( + enum_type: &Ident, + variants: &[Ident], + permissions: &[MsgPermissions], +) -> proc_macro2::TokenStream { + let general_permissions_quote = permissions.iter().map(|permission| { + let general_permissions: &[_] = permission.general.as_ref(); + + if general_permissions.is_empty() && !permission.specific.is_empty() { + // getting to this point means the specific check has failed, so we return an error + quote! { + Err(axelar_wasm_std::permission_control::Error::AddressNotWhitelisted { + expected: whitelisted.clone(), + actual: sender.clone(), + }.into()) + } + } else { + // specific permissions have either failed or there were none, so check general permissions + quote! {Ok((#(axelar_wasm_std::permission_control::Permission::#general_permissions )|*).into())} + } + }); + + // map enum variants to general permission checks. Exclude checks for the 'Any' case, + // because it allows any address, compare permissions to the sender's role otherwise. + quote! { + let permission : Result, axelar_wasm_std::permission_control::Error > = match self { + #(#enum_type::#variants {..}=> {#general_permissions_quote})* + }; + + let permission = permission?; + + if permission.contains(axelar_wasm_std::permission_control::Permission::Any) { + return Ok(self); + } + + let role = error_stack::ResultExt::change_context( + axelar_wasm_std::permission_control::sender_role(storage, sender), + axelar_wasm_std::permission_control::Error::PermissionDenied { + expected: permission.clone(), + actual: axelar_wasm_std::permission_control::Permission::NoPrivilege.into(), + }, + )?; + + if (*permission & *role).is_empty() { + return Err(axelar_wasm_std::permission_control::Error::PermissionDenied { + expected: permission, + actual: role, + } + .into()); + } + + Ok(self) + } +} + +fn build_full_check_function( + permissions: &[MsgPermissions], + specific_permission_body: proc_macro2::TokenStream, + general_permission_body: proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + let unique_specific_permissions = permissions + .iter() + .flat_map(|permission| permission.specific.iter()) + .unique() + .collect::>(); + + let comments = quote! { + /// Ensure the annotated permissions are met by the sender. + /// If the sender does not have the required permissions, an error is returned. + }; + + // the function signature is different depending on how many specific permissions are defined + if unique_specific_permissions.is_empty() { + quote! { + #comments + /// # Arguments + /// * `storage` - The storage to load the sender's role from. + /// * `sender` - The sender's address to check for whitelisting. + pub fn ensure_permissions(self, storage: &dyn cosmwasm_std::Storage, sender: &cosmwasm_std::Addr) + -> error_stack::Result { + + #general_permission_body + } + } + } else { + // due to how rust handles closures, the easiest way to define functions as parameters is with independent generic types, + // one function with one return value for each specific permission + let fs: Vec<_> = (0..unique_specific_permissions.len()) + .map(|i| format_ident!("F{}", i)) + .collect(); + + let cs: Vec<_> = (0..unique_specific_permissions.len()) + .map(|i| format_ident!("C{}", i)) + .collect(); + + let args = quote!(#(#unique_specific_permissions),*); + let format = format!( + "* `{}` - The function(s) to load whitelisted addresses from storage.", + args + ); + quote! { + #comments + /// # Arguments + /// * `storage` - The storage to load the whitelisted addresses and the sender's role from. + /// * `sender` - The sender's address to check for whitelisting. + #[doc = #format] + pub fn ensure_permissions<#(#fs),*, #(#cs),*>( + self, + storage: &dyn cosmwasm_std::Storage, + sender: &cosmwasm_std::Addr, + #(#unique_specific_permissions: #fs),*) + -> error_stack::Result + where + #(#fs:FnOnce(&dyn cosmwasm_std::Storage, &Self) -> error_stack::Result),*, + #(#cs: error_stack::Context),* + { + #specific_permission_body + + #general_permission_body + } + } + } +} diff --git a/packages/msgs-derive/tests/macro.rs b/packages/msgs-derive/tests/macro.rs new file mode 100644 index 000000000..4d4da6c44 --- /dev/null +++ b/packages/msgs-derive/tests/macro.rs @@ -0,0 +1,322 @@ +use std::fmt::Display; + +use axelar_wasm_std::permission_control; +use cosmwasm_std::testing::MockStorage; +use cosmwasm_std::{Addr, Storage}; +use error_stack::{report, Report}; + +#[derive(msgs_derive::EnsurePermissions, Clone, Debug)] +#[allow(dead_code)] // the msg fields are only defined to make sure the derive attribute can handle fields correctly +enum TestMsg { + #[permission(NoPrivilege)] + NoPrivilege, + #[permission(Admin)] + Admin, + #[permission(Governance)] + Governance, + #[permission(Any)] + Any { test: u8 }, + #[permission(Elevated)] + Elevated(bool), + #[permission(Admin, NoPrivilege)] + Multi, +} + +#[derive(msgs_derive::EnsurePermissions, Clone, Debug)] +enum TestMsg2 { + #[permission(Any)] + Any, + #[permission(Specific(gateway1))] + Specific1, + #[permission(Elevated, Specific(gateway1))] + Specific2, + #[permission(Admin, Specific(gateway1), Specific(gateway2), NoPrivilege)] + Specific3, + #[permission(Specific(gateway1, gateway2, gateway3))] + Specific4, +} + +#[test] +fn test_general_ensure_permission() { + let no_privilege = Addr::unchecked("regular user"); + let admin = Addr::unchecked("admin"); + let governance = Addr::unchecked("governance"); + + let mut storage = MockStorage::new(); + permission_control::set_admin(&mut storage, &admin).unwrap(); + permission_control::set_governance(&mut storage, &governance).unwrap(); + + assert!(TestMsg::NoPrivilege + .ensure_permissions(&storage, &no_privilege) + .is_ok()); + assert!(matches!( + TestMsg::NoPrivilege + .ensure_permissions(&storage, &admin) + .unwrap_err() + .current_context(), + permission_control::Error::PermissionDenied { .. } + )); + assert!(matches!( + TestMsg::NoPrivilege + .ensure_permissions(&storage, &governance) + .unwrap_err() + .current_context(), + permission_control::Error::PermissionDenied { .. } + )); + + assert!(matches!( + TestMsg::Admin + .ensure_permissions(&storage, &no_privilege) + .unwrap_err() + .current_context(), + permission_control::Error::PermissionDenied { .. } + )); + assert!(TestMsg::Admin.ensure_permissions(&storage, &admin).is_ok()); + assert!(matches!( + TestMsg::Admin + .ensure_permissions(&storage, &governance) + .unwrap_err() + .current_context(), + permission_control::Error::PermissionDenied { .. } + )); + + assert!(matches!( + TestMsg::Governance + .ensure_permissions(&storage, &no_privilege) + .unwrap_err() + .current_context(), + permission_control::Error::PermissionDenied { .. } + )); + assert!(matches!( + TestMsg::Governance + .ensure_permissions(&storage, &admin) + .unwrap_err() + .current_context(), + permission_control::Error::PermissionDenied { .. } + )); + assert!(TestMsg::Governance + .ensure_permissions(&storage, &governance) + .is_ok()); + + assert!(TestMsg::Any { test: 0 } + .ensure_permissions(&storage, &no_privilege) + .is_ok()); + assert!(TestMsg::Any { test: 0 } + .ensure_permissions(&storage, &admin) + .is_ok()); + assert!(TestMsg::Any { test: 0 } + .ensure_permissions(&storage, &governance) + .is_ok()); + + assert!(matches!( + TestMsg::Elevated(true) + .ensure_permissions(&storage, &no_privilege) + .unwrap_err() + .current_context(), + permission_control::Error::PermissionDenied { .. } + )); + assert!(TestMsg::Elevated(true) + .ensure_permissions(&storage, &admin) + .is_ok()); + assert!(TestMsg::Elevated(true) + .ensure_permissions(&storage, &governance) + .is_ok()); + + assert!(TestMsg::Multi + .ensure_permissions(&storage, &no_privilege) + .is_ok()); + assert!(TestMsg::Multi.ensure_permissions(&storage, &admin).is_ok()); + assert!(matches!( + TestMsg::Multi + .ensure_permissions(&storage, &governance) + .unwrap_err() + .current_context(), + permission_control::Error::PermissionDenied { .. } + )); +} + +#[test] +fn ensure_specific_permissions() { + let no_privilege = Addr::unchecked("regular user"); + let admin = Addr::unchecked("admin"); + let governance = Addr::unchecked("governance"); + + let gateway1_addr = Addr::unchecked("gateway1"); + let gateway2_addr = Addr::unchecked("gateway2"); + let gateway3_addr = Addr::unchecked("gateway3"); + + let gateway1 = + |_: &dyn Storage, _: &TestMsg2| Ok::>(Addr::unchecked("gateway1")); + let gateway2 = + |_: &dyn Storage, _: &TestMsg2| Ok::>(Addr::unchecked("gateway2")); + let gateway3 = + |_: &dyn Storage, _: &TestMsg2| Ok::>(Addr::unchecked("gateway3")); + + let mut storage = MockStorage::new(); + permission_control::set_admin(&mut storage, &admin).unwrap(); + permission_control::set_governance(&mut storage, &governance).unwrap(); + + assert!(TestMsg2::Any + .ensure_permissions(&storage, &no_privilege, gateway1, gateway2, gateway3) + .is_ok()); + assert!(TestMsg2::Any + .ensure_permissions(&storage, &admin, gateway1, gateway2, gateway3) + .is_ok()); + assert!(TestMsg2::Any + .ensure_permissions(&storage, &governance, gateway1, gateway2, gateway3) + .is_ok()); + assert!(TestMsg2::Any + .ensure_permissions(&storage, &gateway1_addr, gateway1, gateway2, gateway3) + .is_ok()); + assert!(TestMsg2::Any + .ensure_permissions(&storage, &gateway2_addr, gateway1, gateway2, gateway3) + .is_ok()); + assert!(TestMsg2::Any + .ensure_permissions(&storage, &gateway3_addr, gateway1, gateway2, gateway3) + .is_ok()); + + assert!(matches!( + TestMsg2::Specific1 + .ensure_permissions(&storage, &no_privilege, gateway1, gateway2, gateway3) + .unwrap_err() + .current_context(), + permission_control::Error::AddressNotWhitelisted { .. } + )); + assert!(matches!( + TestMsg2::Specific1 + .ensure_permissions(&storage, &admin, gateway1, gateway2, gateway3) + .unwrap_err() + .current_context(), + permission_control::Error::AddressNotWhitelisted { .. } + )); + assert!(matches!( + TestMsg2::Specific1 + .ensure_permissions(&storage, &governance, gateway1, gateway2, gateway3) + .unwrap_err() + .current_context(), + permission_control::Error::AddressNotWhitelisted { .. } + )); + assert!(TestMsg2::Specific1 + .ensure_permissions(&storage, &gateway1_addr, gateway1, gateway2, gateway3) + .is_ok()); + assert!(matches!( + TestMsg2::Specific1 + .ensure_permissions(&storage, &gateway2_addr, gateway1, gateway2, gateway3) + .unwrap_err() + .current_context(), + permission_control::Error::AddressNotWhitelisted { .. } + )); + assert!(matches!( + TestMsg2::Specific1 + .ensure_permissions(&storage, &gateway3_addr, gateway1, gateway2, gateway3) + .unwrap_err() + .current_context(), + permission_control::Error::AddressNotWhitelisted { .. } + )); + + assert!(matches!( + TestMsg2::Specific2 + .ensure_permissions(&storage, &no_privilege, gateway1, gateway2, gateway3) + .unwrap_err() + .current_context(), + permission_control::Error::PermissionDenied { .. } + )); + assert!(TestMsg2::Specific2 + .ensure_permissions(&storage, &admin, gateway1, gateway2, gateway3) + .is_ok()); + assert!(TestMsg2::Specific2 + .ensure_permissions(&storage, &governance, gateway1, gateway2, gateway3) + .is_ok()); + assert!(TestMsg2::Specific2 + .ensure_permissions(&storage, &gateway1_addr, gateway1, gateway2, gateway3) + .is_ok()); + assert!(matches!( + TestMsg2::Specific2 + .ensure_permissions(&storage, &gateway2_addr, gateway1, gateway2, gateway3) + .unwrap_err() + .current_context(), + permission_control::Error::PermissionDenied { .. } + )); + assert!(matches!( + TestMsg2::Specific2 + .ensure_permissions(&storage, &gateway3_addr, gateway1, gateway2, gateway3) + .unwrap_err() + .current_context(), + permission_control::Error::PermissionDenied { .. } + )); + + assert!(TestMsg2::Specific3 + .ensure_permissions(&storage, &no_privilege, gateway1, gateway2, gateway3) + .is_ok()); + assert!(TestMsg2::Specific3 + .ensure_permissions(&storage, &admin, gateway1, gateway2, gateway3) + .is_ok()); + assert!(matches!( + TestMsg2::Specific3 + .ensure_permissions(&storage, &governance, gateway1, gateway2, gateway3) + .unwrap_err() + .current_context(), + permission_control::Error::PermissionDenied { .. } + )); + assert!(TestMsg2::Specific3 + .ensure_permissions(&storage, &gateway1_addr, gateway1, gateway2, gateway3) + .is_ok()); + assert!(TestMsg2::Specific3 + .ensure_permissions(&storage, &gateway2_addr, gateway1, gateway2, gateway3) + .is_ok()); + assert!(TestMsg2::Specific3 + .ensure_permissions(&storage, &gateway3_addr, gateway1, gateway2, gateway3) + .is_ok()); // because of NoPrivilege + + assert!(matches!( + TestMsg2::Specific4 + .ensure_permissions(&storage, &no_privilege, gateway1, gateway2, gateway3) + .unwrap_err() + .current_context(), + permission_control::Error::AddressNotWhitelisted { .. } + )); + assert!(matches!( + TestMsg2::Specific4 + .ensure_permissions(&storage, &admin, gateway1, gateway2, gateway3) + .unwrap_err() + .current_context(), + permission_control::Error::AddressNotWhitelisted { .. } + )); + assert!(matches!( + TestMsg2::Specific4 + .ensure_permissions(&storage, &governance, gateway1, gateway2, gateway3) + .unwrap_err() + .current_context(), + permission_control::Error::AddressNotWhitelisted { .. } + )); + assert!(TestMsg2::Specific4 + .ensure_permissions(&storage, &gateway1_addr, gateway1, gateway2, gateway3) + .is_ok()); + assert!(TestMsg2::Specific4 + .ensure_permissions(&storage, &gateway2_addr, gateway1, gateway2, gateway3) + .is_ok()); + assert!(TestMsg2::Specific4 + .ensure_permissions(&storage, &gateway3_addr, gateway1, gateway2, gateway3) + .is_ok()); + + let gateway3 = |_: &dyn Storage, _: &TestMsg2| Err(report!(Error)); + + assert!(matches!( + TestMsg2::Specific4 + .ensure_permissions(&storage, &gateway3_addr, gateway1, gateway2, gateway3) + .unwrap_err() + .current_context(), + permission_control::Error::WhitelistNotFound { .. } + )); +} + +#[derive(Debug)] +struct Error; + +impl std::error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "error") + } +} diff --git a/packages/report/Cargo.toml b/packages/report/Cargo.toml index 65f56e40b..b491bd58b 100644 --- a/packages/report/Cargo.toml +++ b/packages/report/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "report" -version = "0.1.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/packages/report/src/loggable.rs b/packages/report/src/loggable.rs index 35578938d..070bbd4fa 100644 --- a/packages/report/src/loggable.rs +++ b/packages/report/src/loggable.rs @@ -81,7 +81,10 @@ impl From<&Report> for LoggableError { // because of the stack order of attachments we need to reverse them to get the historical order attachments.reverse(); error.attachments = attachments; - errors.push(error) + + if !error.msg.is_empty() || !error.attachments.is_empty() { + errors.push(error) + } } chain_causes(errors).expect("a report must have at least one error") @@ -150,11 +153,13 @@ impl<'a> From<&'a Frame> for FrameType<'a> { #[cfg(test)] mod tests { - use crate::LoggableError; - use error_stack::Report; use std::env; + + use error_stack::Report; use thiserror::Error; + use crate::LoggableError; + #[derive(Error, Debug)] enum Error { #[error("{0}")] diff --git a/packages/router-api/Cargo.toml b/packages/router-api/Cargo.toml index 08286ad7d..41a92c15d 100644 --- a/packages/router-api/Cargo.toml +++ b/packages/router-api/Cargo.toml @@ -1,18 +1,18 @@ [package] name = "router-api" -version = "0.1.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axelar-wasm-std = { workspace = true } -axelar-wasm-std-derive = { workspace = true } +axelar-wasm-std = { workspace = true, features = ["derive"] } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } error-stack = { workspace = true } flagset = { version = "0.4.3", features = ["serde"] } +msgs-derive = { workspace = true } report = { workspace = true } schemars = { workspace = true } serde = { workspace = true } diff --git a/packages/router-api/src/error.rs b/packages/router-api/src/error.rs index 769cb1902..4347cfacf 100644 --- a/packages/router-api/src/error.rs +++ b/packages/router-api/src/error.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std_derive::IntoContractError; +use axelar_wasm_std::IntoContractError; use cosmwasm_std::StdError; use thiserror::Error; @@ -14,6 +14,9 @@ pub enum Error { #[error(transparent)] Std(#[from] StdError), + #[error("amplifier routing is disabled")] + RoutingDisabled, + #[error("chain already exists")] ChainAlreadyExists, diff --git a/packages/router-api/src/msg.rs b/packages/router-api/src/msg.rs index a075a1aff..05cbe8bce 100644 --- a/packages/router-api/src/msg.rs +++ b/packages/router-api/src/msg.rs @@ -1,47 +1,49 @@ +use std::collections::HashMap; + use axelar_wasm_std::msg_id::MessageIdFormat; use cosmwasm_schema::{cw_serde, QueryResponses}; +use msgs_derive::EnsurePermissions; use crate::primitives::*; #[cw_serde] +#[derive(EnsurePermissions)] pub enum ExecuteMsg { - /* - * Governance Methods - * All the below messages should only be called by governance - */ - // Registers a new chain with the router + /// Registers a new chain with the router + #[permission(Governance)] RegisterChain { chain: ChainName, gateway_address: Address, msg_id_format: MessageIdFormat, }, - // Changes the gateway address associated with a particular chain + /// Changes the gateway address associated with a particular chain + #[permission(Governance)] UpgradeGateway { chain: ChainName, contract_address: Address, }, - - /* - * Router Admin Methods - * All the below messages should only be called by the router admin - */ - // Freezes a chain, in the specified direction. - FreezeChain { - chain: ChainName, - direction: GatewayDirection, + /// Freezes the specified chains in the specified directions. + #[permission(Elevated)] + FreezeChains { + chains: HashMap, }, - // Unfreezes a chain, in the specified direction. - UnfreezeChain { - chain: ChainName, - direction: GatewayDirection, + /// Unfreezes the specified chains in the specified directions. + #[permission(Elevated)] + UnfreezeChains { + chains: HashMap, }, - /* - * Gateway Messages - * The below messages can only be called by registered gateways - */ - // Routes a message to all outgoing gateways registered to the destination domain. - // Called by an incoming gateway + /// Emergency command to stop all amplifier routing. + #[permission(Elevated)] + DisableRouting, + + /// Resumes routing after an emergency shutdown. + #[permission(Elevated)] + EnableRouting, + + /// Routes a message to all outgoing gateways registered to the destination domain. + /// Called by an incoming gateway + #[permission(Specific(gateway))] RouteMessages(Vec), } @@ -49,7 +51,7 @@ pub enum ExecuteMsg { #[derive(QueryResponses)] pub enum QueryMsg { #[returns(ChainEndpoint)] - GetChainInfo(ChainName), + ChainInfo(ChainName), // Returns a list of chains registered with the router // The list is paginated by: @@ -60,4 +62,6 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, + #[returns(bool)] + IsEnabled, } diff --git a/packages/router-api/src/primitives.rs b/packages/router-api/src/primitives.rs index 724d11404..8bc5638b1 100644 --- a/packages/router-api/src/primitives.rs +++ b/packages/router-api/src/primitives.rs @@ -1,14 +1,17 @@ -use std::{any::type_name, fmt, ops::Deref, str::FromStr}; +use std::any::type_name; +use std::fmt; +use std::fmt::Display; +use std::ops::Deref; +use std::str::FromStr; use axelar_wasm_std::flagset::FlagSet; +use axelar_wasm_std::hash::Hash; use axelar_wasm_std::msg_id::MessageIdFormat; -use axelar_wasm_std::{hash::Hash, nonempty, FnExt}; +use axelar_wasm_std::{nonempty, FnExt}; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Attribute, HexBinary}; -use cosmwasm_std::{StdError, StdResult}; +use cosmwasm_std::{Addr, Attribute, HexBinary, StdError, StdResult}; use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey}; -use error_stack::Report; -use error_stack::ResultExt; +use error_stack::{Context, Report, ResultExt}; use flagset::flags; use schemars::gen::SchemaGenerator; use schemars::schema::Schema; @@ -19,7 +22,9 @@ use valuable::Valuable; use crate::error::*; -pub const CHAIN_NAME_DELIMITER: char = '_'; +/// Delimiter used when concatenating fields to prevent ambiguous encodings. +/// The delimiter must be prevented from being contained in values that are used as fields. +pub const FIELD_DELIMITER: char = '_'; #[cw_serde] #[derive(Eq, Hash)] @@ -38,11 +43,18 @@ pub struct Message { impl Message { pub fn hash(&self) -> Hash { let mut hasher = Keccak256::new(); + let delimiter_bytes = &[FIELD_DELIMITER as u8]; + hasher.update(self.cc_id.to_string()); + hasher.update(delimiter_bytes); hasher.update(self.source_address.as_str()); + hasher.update(delimiter_bytes); hasher.update(self.destination_chain.as_ref()); + hasher.update(delimiter_bytes); hasher.update(self.destination_address.as_str()); + hasher.update(delimiter_bytes); hasher.update(self.payload_hash); + hasher.finalize().into() } } @@ -50,11 +62,11 @@ impl Message { impl From for Vec { fn from(other: Message) -> Self { vec![ - ("id", other.cc_id.id).into(), - ("source_chain", other.cc_id.chain).into(), - ("source_addresses", other.source_address.deref()).into(), + ("message_id", other.cc_id.message_id).into(), + ("source_chain", other.cc_id.source_chain).into(), + ("source_address", other.source_address.deref()).into(), ("destination_chain", other.destination_chain).into(), - ("destination_addresses", other.destination_address.deref()).into(), + ("destination_address", other.destination_address.deref()).into(), ( "payload_hash", HexBinary::from(other.payload_hash).to_string(), @@ -65,6 +77,7 @@ impl From for Vec { } #[cw_serde] +#[serde(try_from = "String")] #[derive(Eq, Hash)] pub struct Address(nonempty::String); @@ -88,6 +101,10 @@ impl TryFrom for Address { type Error = Report; fn try_from(value: String) -> Result { + if value.contains(FIELD_DELIMITER) { + return Err(Report::new(Error::InvalidAddress)); + } + Ok(Address( value .parse::() @@ -99,47 +116,35 @@ impl TryFrom for Address { #[cw_serde] #[derive(Eq, Hash)] pub struct CrossChainId { - pub chain: ChainName, - pub id: nonempty::String, + pub source_chain: ChainNameRaw, + pub message_id: nonempty::String, } -/// todo: remove this when state::NewMessage is used -impl FromStr for CrossChainId { - type Err = Error; - - fn from_str(s: &str) -> Result { - let parts = s.split_once(CHAIN_NAME_DELIMITER); - let (chain, id) = parts - .map(|(chain, id)| { - ( - chain.parse::(), - id.parse::() - .map_err(|_| Error::InvalidMessageId), - ) - }) - .ok_or(Error::InvalidMessageId)?; +impl CrossChainId { + pub fn new( + chain: impl TryInto, + id: impl TryInto, + ) -> error_stack::Result + where + S: Context, + T: Context, + { Ok(CrossChainId { - chain: chain?, - id: id?, + source_chain: chain.try_into().change_context(Error::InvalidChainName)?, + message_id: id.try_into().change_context(Error::InvalidMessageId)?, }) } } -impl fmt::Display for CrossChainId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}{}{}", self.chain, CHAIN_NAME_DELIMITER, *self.id) - } -} - impl PrimaryKey<'_> for CrossChainId { - type Prefix = ChainName; + type Prefix = ChainNameRaw; type SubPrefix = (); type Suffix = String; - type SuperSuffix = (ChainName, String); + type SuperSuffix = (ChainNameRaw, String); fn key(&self) -> Vec { - let mut keys = self.chain.key(); - keys.extend(self.id.key()); + let mut keys = self.source_chain.key(); + keys.extend(self.message_id.key()); keys } } @@ -148,16 +153,32 @@ impl KeyDeserialize for CrossChainId { type Output = Self; fn from_vec(value: Vec) -> StdResult { - let (chain, id) = <(ChainName, String)>::from_vec(value)?; + let (source_chain, id) = <(ChainNameRaw, String)>::from_vec(value)?; Ok(CrossChainId { - chain, - id: id + source_chain, + message_id: id .try_into() .map_err(|err| StdError::parse_err(type_name::(), err))?, }) } } +impl Display for CrossChainId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}{}{}", + self.source_chain, FIELD_DELIMITER, *self.message_id + ) + } +} +/// ChainName represents the identifier used for chains in Amplifier. +/// Certain restrictions apply on the string: +/// - Must not be empty +/// - Must not exceed `ChainName::MAX_LEN` bytes +/// - Only ASCII characters are allowed +/// - Must not contain the `FIELD_DELIMITER` character +/// - The string is lowercased #[cw_serde] #[serde(try_from = "String")] #[derive(Eq, Hash, Valuable)] @@ -167,11 +188,9 @@ impl FromStr for ChainName { type Err = Error; fn from_str(s: &str) -> Result { - if s.contains(CHAIN_NAME_DELIMITER) || s.is_empty() { - return Err(Error::InvalidChainName); - } + let chain_name: ChainNameRaw = s.parse()?; - Ok(ChainName(s.to_lowercase())) + Ok(ChainName(chain_name.0.to_lowercase())) } } @@ -189,15 +208,35 @@ impl TryFrom for ChainName { } } -impl fmt::Display for ChainName { +impl TryFrom<&str> for ChainName { + type Error = Error; + + fn try_from(value: &str) -> Result { + value.parse() + } +} + +impl Display for ChainName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } +impl PartialEq for ChainName { + fn eq(&self, other: &ChainNameRaw) -> bool { + self == &other.as_ref() + } +} + impl PartialEq for ChainName { fn eq(&self, other: &String) -> bool { - self.0 == other.to_lowercase() + self == &other.as_str() + } +} + +impl PartialEq<&str> for ChainName { + fn eq(&self, other: &&str) -> bool { + self.0 == *other } } @@ -245,6 +284,134 @@ impl AsRef for ChainName { } } +/// ChainNameRaw represents the raw case-sensitive identifier used for source chains in Amplifier. +/// It is backwards compatible with case-sensitive chain names used in axelar-core (e.g. `Ethereum`). +/// +/// Certain restrictions apply on the string: +/// - Must not be empty +/// - Must not exceed `ChainNameRaw::MAX_LEN` bytes +/// - Only ASCII characters are allowed +/// - Must not contain the `FIELD_DELIMITER` character +/// - Case-sensitivity is preserved +#[cw_serde] +#[serde(try_from = "String")] +#[derive(Eq, Hash)] +pub struct ChainNameRaw(String); + +impl From for ChainNameRaw { + fn from(other: ChainName) -> Self { + ChainNameRaw(other.0) + } +} + +impl ChainNameRaw { + /// Maximum length of a chain name (in bytes). + /// This MUST NOT be changed without a corresponding change to the ChainName validation in axelar-core. + const MAX_LEN: usize = 20; +} + +impl FromStr for ChainNameRaw { + type Err = Error; + + fn from_str(s: &str) -> Result { + if s.is_empty() || s.len() > Self::MAX_LEN || !s.is_ascii() || s.contains(FIELD_DELIMITER) { + return Err(Error::InvalidChainName); + } + + Ok(ChainNameRaw(s.to_owned())) + } +} + +impl From for String { + fn from(d: ChainNameRaw) -> Self { + d.0 + } +} + +impl TryFrom for ChainNameRaw { + type Error = Error; + + fn try_from(value: String) -> Result { + value.parse() + } +} + +impl TryFrom<&str> for ChainNameRaw { + type Error = Error; + + fn try_from(value: &str) -> Result { + value.parse() + } +} + +impl Display for ChainNameRaw { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl AsRef for ChainNameRaw { + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +impl PartialEq for ChainNameRaw { + fn eq(&self, other: &ChainName) -> bool { + self == &other.as_ref() + } +} + +impl PartialEq for ChainNameRaw { + fn eq(&self, other: &String) -> bool { + self == &other.as_str() + } +} + +impl PartialEq<&str> for ChainNameRaw { + fn eq(&self, other: &&str) -> bool { + self.0 == *other + } +} + +impl<'a> PrimaryKey<'a> for ChainNameRaw { + type Prefix = (); + type SubPrefix = (); + type Suffix = Self; + type SuperSuffix = Self; + + fn key(&self) -> Vec { + vec![Key::Ref(self.0.as_bytes())] + } +} + +impl<'a> Prefixer<'a> for ChainNameRaw { + fn prefix(&self) -> Vec { + vec![Key::Ref(self.0.as_bytes())] + } +} + +impl KeyDeserialize for ChainNameRaw { + type Output = Self; + + #[inline(always)] + fn from_vec(value: Vec) -> StdResult { + String::from_utf8(value) + .map_err(StdError::invalid_utf8)? + .then(ChainNameRaw::try_from) + .map_err(StdError::invalid_utf8) + } +} + +impl KeyDeserialize for &ChainNameRaw { + type Output = ChainNameRaw; + + #[inline(always)] + fn from_vec(value: Vec) -> StdResult { + ChainNameRaw::from_vec(value) + } +} + flags! { #[repr(u8)] #[derive(Deserialize, Serialize, Hash)] @@ -291,19 +458,19 @@ impl ChainEndpoint { #[cfg(test)] mod tests { - use super::*; - use cosmwasm_std::to_json_vec; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use sha3::{Digest, Sha3_256}; + use super::*; + #[test] // Any modifications to the Message struct fields or their types // will cause this test to fail, indicating that a migration is needed. fn test_message_struct_unchanged() { let expected_message_hash = - "e8052da3a89c90468cc6e4e242a827f8579fb0ea8e298b1650d73a0f7e81abc3"; + "3a0edbeb590d12cf9f71864469d9e7afd52cccf2798db09c55def296af3a8e89"; let msg = dummy_message(); @@ -317,7 +484,7 @@ mod tests { #[test] fn hash_id_unchanged() { let expected_message_hash = - "d30a374a795454706b43259998aafa741267ecbc8b6d5771be8d7b8c9a9db263"; + "e6b9cc9b6962c997b44ded605ebfb4f861e2db2ddff7e8be84a7a79728cea61e"; let msg = dummy_message(); @@ -325,69 +492,209 @@ mod tests { } #[test] - fn should_fail_to_parse_invalid_chain_name() { - // empty + fn should_not_deserialize_invalid_chain_name() { assert_eq!( - "".parse::().unwrap_err(), - Error::InvalidChainName + "chain name is invalid", + serde_json::from_str::("\"\"") + .unwrap_err() + .to_string() ); - // name contains id separator assert_eq!( - format!("chain {CHAIN_NAME_DELIMITER}") - .parse::() - .unwrap_err(), - Error::InvalidChainName + "chain name is invalid", + serde_json::from_str::(format!("\"chain{FIELD_DELIMITER}\"").as_str()) + .unwrap_err() + .to_string() ); } #[test] - fn should_parse_to_case_insensitive_chain_name() { - let rand_str: String = thread_rng() - .sample_iter(&Alphanumeric) - .take(10) - .map(char::from) - .collect(); - - let chain_name: ChainName = rand_str.parse().unwrap(); - - assert_eq!( - chain_name, - rand_str.to_lowercase().parse::().unwrap() - ); - assert_eq!( - chain_name, - rand_str.to_uppercase().parse::().unwrap() - ); + fn ensure_chain_name_parsing_respect_restrictions() { + struct TestCase<'a> { + input: &'a str, + can_parse: bool, + is_normalized: bool, + } + let random_lower = random_chain_name().to_lowercase(); + let random_upper = random_chain_name().to_uppercase(); + + let test_cases = [ + TestCase { + input: "", + can_parse: false, + is_normalized: false, + }, + TestCase { + input: "chain_with_prohibited_symbols", + can_parse: false, + is_normalized: false, + }, + TestCase { + input: "!@#$%^&*()+=-", + can_parse: true, + is_normalized: true, + }, + TestCase { + input: "1234567890", + can_parse: true, + is_normalized: true, + }, + TestCase { + input: "ethereum", + can_parse: true, + is_normalized: true, + }, + TestCase { + input: "ETHEREUM", + can_parse: true, + is_normalized: false, + }, + TestCase { + input: "ethereum-1", + can_parse: true, + is_normalized: true, + }, + TestCase { + input: "ETHEREUM-1", + can_parse: true, + is_normalized: false, + }, + TestCase { + input: "long chain name over max len", + can_parse: false, + is_normalized: false, + }, + TestCase { + input: "UTF-8 ❤", + can_parse: false, + is_normalized: false, + }, + TestCase { + input: random_lower.as_str(), + can_parse: true, + is_normalized: true, + }, + TestCase { + input: random_upper.as_str(), + can_parse: true, + is_normalized: false, + }, + ]; + + let conversions = [ + |input: &str| ChainName::from_str(input), + |input: &str| ChainName::try_from(input), + |input: &str| ChainName::try_from(input.to_string()), + ]; + + let raw_conversions = [ + |input: &str| ChainNameRaw::from_str(input), + |input: &str| ChainNameRaw::try_from(input), + |input: &str| ChainNameRaw::try_from(input.to_string()), + ]; + + for case in test_cases.into_iter() { + for conversion in conversions.into_iter() { + let result = conversion(case.input); + assert_eq!(result.is_ok(), case.can_parse, "input: {}", case.input); + if case.can_parse { + if case.is_normalized { + assert_eq!(result.unwrap(), case.input); + } else { + assert_ne!(result.unwrap(), case.input); + } + } + } + + for conversion in raw_conversions.into_iter() { + let result = conversion(case.input); + assert_eq!(result.is_ok(), case.can_parse, "input: {}", case.input); + if case.can_parse { + assert_eq!(result.unwrap(), case.input); + } + } + } } #[test] - fn should_not_deserialize_invalid_chain_name() { + fn should_not_deserialize_invalid_address() { assert_eq!( - "chain name is invalid", - serde_json::from_str::("\"\"") + "address is invalid", + serde_json::from_str::
("\"\"") .unwrap_err() .to_string() ); assert_eq!( - "chain name is invalid", - serde_json::from_str::(format!("\"chain{CHAIN_NAME_DELIMITER}\"").as_str()) + "address is invalid", + serde_json::from_str::
(format!("\"address{FIELD_DELIMITER}\"").as_str()) .unwrap_err() .to_string() ); } #[test] - fn chain_name_case_insensitive_comparison() { - let chain_name = ChainName::from_str("ethereum").unwrap(); - - assert!(chain_name.eq(&"Ethereum".to_string())); - assert!(chain_name.eq(&"ETHEREUM".to_string())); - assert!(chain_name.eq(&"ethereum".to_string())); - assert!(chain_name.eq(&"ethEReum".to_string())); + fn ensure_address_parsing_respect_restrictions() { + struct TestCase<'a> { + input: &'a str, + can_parse: bool, + } + let random_lower = random_address().to_lowercase(); + let random_upper = random_address().to_uppercase(); - assert!(!chain_name.eq(&"Ethereum-1".to_string())); + let test_cases = [ + TestCase { + input: "", + can_parse: false, + }, + TestCase { + input: "address_with_prohibited_symbols", + can_parse: false, + }, + TestCase { + input: "!@#$%^&*()+=-1234567890", + can_parse: true, + }, + TestCase { + input: "0x4F4495243837681061C4743b74B3eEdf548D56A5", + can_parse: true, + }, + TestCase { + input: "0x4f4495243837681061c4743b74b3eedf548d56a5", + can_parse: true, + }, + TestCase { + input: "GARRAOPAA5MNY3Y5V2OOYXUMBC54UDHHJTUMLRQBY2DIZKT62G5WSJP4Copy", + can_parse: true, + }, + TestCase { + input: "ETHEREUM-1", + can_parse: true, + }, + TestCase { + input: random_lower.as_str(), + can_parse: true, + }, + TestCase { + input: random_upper.as_str(), + can_parse: true, + }, + ]; + + let conversions: [fn(&str) -> Result; 2] = [ + |input: &str| Address::from_str(input), + |input: &str| Address::try_from(input.to_string()), + ]; + + for case in test_cases.into_iter() { + for conversion in conversions.into_iter() { + let result = conversion(case.input); + assert_eq!(result.is_ok(), case.can_parse, "input: {}", case.input); + if case.can_parse { + assert_eq!(result.unwrap().to_string(), case.input); + } + } + } } #[test] @@ -402,14 +709,27 @@ mod tests { fn dummy_message() -> Message { Message { - cc_id: CrossChainId { - id: "hash-index".parse().unwrap(), - chain: "chain".parse().unwrap(), - }, - source_address: "source_address".parse().unwrap(), + cc_id: CrossChainId::new("chain", "hash-index").unwrap(), + source_address: "source-address".parse().unwrap(), destination_chain: "destination-chain".parse().unwrap(), - destination_address: "destination_address".parse().unwrap(), + destination_address: "destination-address".parse().unwrap(), payload_hash: [1; 32], } } + + fn random_chain_name() -> String { + thread_rng() + .sample_iter(&Alphanumeric) + .take(10) + .map(char::from) + .collect() + } + + fn random_address() -> String { + thread_rng() + .sample_iter(&Alphanumeric) + .take(10) + .map(char::from) + .collect() + } } diff --git a/packages/signature-verifier-api/Cargo.toml b/packages/signature-verifier-api/Cargo.toml index 1602a84e2..813633a23 100644 --- a/packages/signature-verifier-api/Cargo.toml +++ b/packages/signature-verifier-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "signature-verifier-api" -version = "0.1.0" +version = "1.0.0" rust-version = { workspace = true } edition = "2021" diff --git a/release.toml b/release.toml index da7a87a48..7259e24f6 100644 --- a/release.toml +++ b/release.toml @@ -1,4 +1,4 @@ -pre-release-commit-message = "chore: release {{crate_name}} {{version}} [skip ci]" +pre-release-commit-message = "chore: release {{crate_name}} {{version}}" consolidate-commits = false release = true publish = false diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..3a3f3f1dd --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +imports_granularity = "Module" +group_imports = "StdExternalCrate"