├── .cargo └── config ├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .gitattributes ├── .github └── workflows │ ├── pr.yaml │ ├── release.yaml │ └── test.yaml ├── .gitignore ├── .prettierrc ├── .yarn └── install-state.gz ├── .yarnrc.yml ├── Beaker.toml ├── Cargo.toml ├── DEPLOYMENT.md ├── Makefile ├── README.md ├── asset ├── hyperlane-1.png ├── hyperlane-2.png ├── hyperlane-3.png └── hyperlane-all.png ├── codegen.ts ├── config.example.yaml ├── context ├── .gitkeep ├── neutron-1.json └── osmosis-1.json ├── contracts ├── core │ ├── mailbox │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── contract.rs │ │ │ ├── error.rs │ │ │ ├── event.rs │ │ │ ├── execute.rs │ │ │ ├── lib.rs │ │ │ ├── query.rs │ │ │ └── state.rs │ └── va │ │ ├── Cargo.toml │ │ └── src │ │ ├── contract.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ └── state.rs ├── hooks │ ├── aggregate │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── fee │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── merkle │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── pausable │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── routing-custom │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── routing-fallback │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── routing │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── igps │ ├── core │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── contract.rs │ │ │ ├── error.rs │ │ │ ├── event.rs │ │ │ ├── execute.rs │ │ │ ├── lib.rs │ │ │ ├── query.rs │ │ │ └── tests │ │ │ ├── contract.rs │ │ │ └── mod.rs │ └── oracle │ │ ├── Cargo.toml │ │ └── src │ │ ├── contract.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── state.rs │ │ └── tests │ │ ├── contract.rs │ │ └── mod.rs ├── isms │ ├── aggregate │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── error.rs │ │ │ └── lib.rs │ ├── multisig │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── contract.rs │ │ │ ├── error.rs │ │ │ ├── lib.rs │ │ │ ├── query.rs │ │ │ └── state.rs │ ├── pausable │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── routing │ │ ├── Cargo.toml │ │ └── src │ │ ├── contract.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── state.rs │ │ └── tests │ │ ├── contract.rs │ │ └── mod.rs ├── mocks │ ├── mock-hook │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── contract.rs │ │ │ └── lib.rs │ ├── mock-ism │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── contract.rs │ │ │ └── lib.rs │ └── mock-msg-receiver │ │ ├── Cargo.toml │ │ └── src │ │ ├── contract.rs │ │ └── lib.rs └── warp │ ├── cw20 │ ├── Cargo.toml │ └── src │ │ ├── contract.rs │ │ ├── conv.rs │ │ ├── error.rs │ │ └── lib.rs │ └── native │ ├── Cargo.toml │ └── src │ ├── contract.rs │ ├── conv.rs │ ├── error.rs │ ├── lib.rs │ └── proto.rs ├── example ├── .gitignore ├── docker-compose.yml ├── hyperlane │ ├── agent-config.docker.json │ ├── relayer.json │ ├── validator.osmotest5.json │ └── validator.sepolia.json ├── index.ts ├── src │ ├── constants.ts │ ├── ioc.ts │ ├── recipient.ts │ ├── utils.ts │ └── warp.ts └── warp │ ├── umilktia.json │ ├── uosmo.json │ └── utia.json ├── integration-test ├── .gitignore ├── Cargo.toml ├── abis │ ├── FastHypERC20.json │ ├── FastHypERC20Collateral.json │ ├── Mailbox.json │ ├── TestMultisigIsm.json │ └── TestRecipient.json ├── build.rs ├── cw │ └── cw20_base.wasm ├── src │ └── lib.rs └── tests │ ├── constants.rs │ ├── contracts │ ├── cw │ │ ├── deploy.rs │ │ ├── hook.rs │ │ ├── igp.rs │ │ ├── ism.rs │ │ ├── mod.rs │ │ ├── setup.rs │ │ ├── store.rs │ │ └── types.rs │ ├── eth │ │ ├── bind │ │ │ └── mod.rs │ │ ├── deploy.rs │ │ ├── mod.rs │ │ ├── setup.rs │ │ └── types.rs │ └── mod.rs │ ├── event.rs │ ├── mailbox.rs │ ├── validator.rs │ └── warp.rs ├── package.json ├── packages ├── connection │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── interface │ ├── Cargo.toml │ └── src │ │ ├── connection.rs │ │ ├── core │ │ ├── mailbox.rs │ │ ├── mod.rs │ │ └── va.rs │ │ ├── hook │ │ ├── aggregate.rs │ │ ├── fee.rs │ │ ├── merkle.rs │ │ ├── mod.rs │ │ ├── pausable.rs │ │ ├── routing.rs │ │ ├── routing_custom.rs │ │ └── routing_fallback.rs │ │ ├── igp │ │ ├── core.rs │ │ ├── mod.rs │ │ └── oracle.rs │ │ ├── ism │ │ ├── aggregate.rs │ │ ├── mod.rs │ │ ├── multisig.rs │ │ ├── pausable.rs │ │ └── routing.rs │ │ ├── lib.rs │ │ ├── macros.rs │ │ ├── ownable.rs │ │ ├── pausable.rs │ │ ├── router.rs │ │ ├── types │ │ ├── bech32.rs │ │ ├── crypto.rs │ │ ├── merkle.rs │ │ ├── message.rs │ │ ├── metadata.rs │ │ └── mod.rs │ │ └── warp │ │ ├── cw20.rs │ │ ├── mod.rs │ │ └── native.rs ├── ownable │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── test.rs ├── pausable │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── test.rs ├── router │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── test.rs ├── schema │ ├── Cargo.toml │ └── src │ │ └── main.rs └── utils │ ├── Cargo.toml │ └── src │ └── lib.rs ├── script ├── commands │ ├── context.ts │ ├── contract.ts │ ├── deploy.ts │ ├── index.ts │ ├── migrate.ts │ ├── upload.ts │ ├── wallet.ts │ └── warp.ts ├── deploy │ ├── hook.ts │ ├── igp.ts │ ├── index.ts │ ├── ism.ts │ └── warp.ts ├── index.ts └── shared │ ├── agent.ts │ ├── config.ts │ ├── constants.ts │ ├── context.ts │ ├── contract.ts │ ├── crypto.ts │ ├── github.ts │ ├── ioc.ts │ ├── logger.ts │ ├── utils.ts │ └── wasm.ts ├── tsconfig.json └── yarn.lock /.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --release --lib --target wasm32-unknown-unknown" 3 | unit-test = "test --lib" 4 | all-test = "test --workspace" 5 | schema = "run --package hpl-schema --bin hpl-schema" 6 | 7 | [env] 8 | RUSTFLAGS = "-C link-arg=-s" 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.rs] 11 | indent_size = 4 12 | 13 | [*.ts] 14 | quote_type = single 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .cargo/ 2 | .github/ 3 | .vscode/ 4 | .yarn/ 5 | asset/ 6 | contracts/ 7 | integration-test/ 8 | node_modules/ 9 | packages/ 10 | target/ 11 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint'], 5 | extends: [ 6 | 'eslint:recommended', 7 | 'plugin:@typescript-eslint/recommended', 8 | 'plugin:prettier/recommended', 9 | ], 10 | }; 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.yarn/** linguist-vendored 2 | /.yarn/releases/* binary 3 | /.yarn/plugins/**/* binary 4 | /.pnp.* binary linguist-generated 5 | -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: pr 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - 'contracts/**' 7 | - 'packages/**' 8 | - 'Cargo.toml' 9 | branches: 10 | - 'main' 11 | 12 | jobs: 13 | artifact: 14 | permissions: 15 | contents: write 16 | pull-requests: write 17 | 18 | name: artifact 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Install Rust 24 | run: | 25 | rustup toolchain install 1.72 \ 26 | --profile minimal \ 27 | --target wasm32-unknown-unknown \ 28 | --no-self-update 29 | 30 | - name: Cache dependencies 31 | uses: Swatinem/rust-cache@v2 32 | 33 | - name: Install Deps 34 | run: make install-prod 35 | 36 | - name: Setup Docker Buildx 37 | uses: docker/setup-buildx-action@v2 38 | 39 | - name: Build wasm 40 | run: | 41 | cargo generate-lockfile 42 | make ci-build 43 | 44 | - name: Pull request artifacts 45 | uses: gavv/pull-request-artifacts@v2 46 | with: 47 | commit: ${{ github.event.pull_request.head.sha }} 48 | repo-token: ${{ secrets.GITHUB_TOKEN }} 49 | artifacts-branch: artifacts 50 | artifacts: | 51 | wasm_codes.zip 52 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | jobs: 9 | crate: 10 | name: crate 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Install Rust 16 | run: | 17 | rustup toolchain install 1.72 \ 18 | --profile minimal \ 19 | --target wasm32-unknown-unknown \ 20 | --no-self-update 21 | 22 | - name: Cache dependencies 23 | uses: Swatinem/rust-cache@v2 24 | 25 | - name: Install Deps 26 | run: make install-prod 27 | 28 | - name: Publish hpl-interface 29 | run: cargo publish --token ${{ secrets.CRATES_TOKEN }} --package hpl-interface 30 | 31 | artifact: 32 | permissions: 33 | contents: write 34 | pull-requests: write 35 | 36 | name: artifact 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v4 40 | 41 | - name: Install Rust 42 | run: | 43 | rustup toolchain install 1.72 \ 44 | --profile minimal \ 45 | --target wasm32-unknown-unknown \ 46 | --no-self-update 47 | 48 | - name: Cache dependencies 49 | uses: Swatinem/rust-cache@v2 50 | 51 | - name: Install Deps 52 | run: make install-prod 53 | 54 | - name: Setup Docker Buildx 55 | uses: docker/setup-buildx-action@v2 56 | 57 | - name: Build wasm 58 | run: | 59 | cargo generate-lockfile 60 | make ci-build 61 | mv wasm_codes.zip ${{ env.ARTIFACT_NAME }} 62 | sha256sum ${{ env.ARTIFACT_NAME }} > ${{ env.ARTIFACT_NAME }}.CHECKSUM 63 | md5sum ${{ env.ARTIFACT_NAME }} > ${{ env.ARTIFACT_NAME }}.CHECKSUM.MD5 64 | env: 65 | ARTIFACT_NAME: ${{ github.event.repository.name }}-${{ github.ref_name }}.zip 66 | 67 | - name: Release Artifact 68 | uses: softprops/action-gh-release@v1 69 | with: 70 | files: | 71 | ${{ env.ARTIFACT_NAME }} 72 | ${{ env.ARTIFACT_NAME }}.CHECKSUM 73 | ${{ env.ARTIFACT_NAME }}.CHECKSUM.MD5 74 | env: 75 | ARTIFACT_NAME: ${{ github.event.repository.name }}-${{ github.ref_name }}.zip 76 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - 'contracts/**' 7 | - 'packages/**' 8 | - 'Cargo.toml' 9 | push: 10 | branches: 11 | - 'main' 12 | 13 | jobs: 14 | unit-test: 15 | strategy: 16 | fail-fast: true 17 | 18 | name: unit-test 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - uses: actions/setup-go@v4 24 | with: 25 | go-version: '1.21' 26 | 27 | - name: Install Rust 28 | run: | 29 | rustup toolchain install 1.72 \ 30 | --profile minimal \ 31 | --target wasm32-unknown-unknown \ 32 | --no-self-update 33 | 34 | - name: Cache dependencies 35 | uses: Swatinem/rust-cache@v2 36 | 37 | - name: Run tests 38 | run: cargo test --workspace --exclude hpl-tests 39 | 40 | coverage: 41 | runs-on: ubuntu-latest 42 | env: 43 | CARGO_TERM_COLOR: always 44 | steps: 45 | - uses: actions/checkout@v4 46 | 47 | - uses: actions/setup-go@v4 48 | with: 49 | go-version: '1.21' 50 | 51 | - name: Install Rust 52 | run: | 53 | rustup toolchain install 1.72 \ 54 | --profile minimal \ 55 | --target wasm32-unknown-unknown \ 56 | --no-self-update 57 | 58 | - name: Cache dependencies 59 | uses: Swatinem/rust-cache@v2 60 | 61 | - name: Install cargo-llvm-cov 62 | uses: taiki-e/install-action@cargo-llvm-cov 63 | 64 | - name: Generate code coverage 65 | run: cargo llvm-cov --workspace --exclude hpl-tests --codecov --output-path codecov.json 66 | 67 | - name: Upload to codecov.io 68 | uses: codecov/codecov-action@v3 69 | with: 70 | token: ${{secrets.CODECOV_TOKEN}} 71 | fail_ci_if_error: true 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | schema 7 | **/*/schema 8 | !packages/schema 9 | 10 | # Generated by rust-optimizer 11 | artifacts/ 12 | 13 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 14 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 15 | Cargo.lock 16 | 17 | # These are backup files generated by rustfmt 18 | **/*.rs.bk 19 | 20 | # MSVC Windows builds of rustc generate these, which store debugging information 21 | *.pdb 22 | 23 | # Ignores local beaker state 24 | **/state.local.json 25 | .idea/ 26 | 27 | .DS_Store 28 | .idea/ 29 | 30 | **/*.profraw 31 | 32 | # Script setup 33 | 34 | config.yaml 35 | config.*.yaml 36 | !config.example.yaml 37 | 38 | context/*.config.json 39 | 40 | tmp/ 41 | dist/ 42 | node_modules/ 43 | wasm_codes.zip -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "trailingComma": "all", 5 | "singleQuote": true, 6 | "semi": true, 7 | "useTabs": false, 8 | "importOrder": ["^[./]"], 9 | "importOrderSeparation": true, 10 | "importOrderSortSpecifiers": true, 11 | "plugins": ["@trivago/prettier-plugin-sort-imports"] 12 | } 13 | -------------------------------------------------------------------------------- /.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zikc2023/cw-hyperlane/00f64a9e8df5b384c2648d3f0f38baa549f204b3/.yarn/install-state.gz -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /Beaker.toml: -------------------------------------------------------------------------------- 1 | name = "cw-hyperlane" 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PWD:=$(shell pwd) 2 | BASE:=$(shell basename "$(PWD)") 3 | 4 | clean: 5 | @cargo clean 6 | @rm -rf ./artifacts 7 | 8 | install: install-dev 9 | 10 | install-dev: install-prod 11 | cargo install --force cargo-llvm-cov cw-optimizoor beaker 12 | 13 | install-prod: 14 | cargo install --force cosmwasm-check 15 | rustup target add wasm32-unknown-unknown 16 | 17 | schema: 18 | ls ./contracts | xargs -n 1 -t beaker wasm ts-gen 19 | 20 | check: 21 | ls -d ./artifacts/*.wasm | xargs -I x cosmwasm-check x 22 | 23 | optimize: 24 | docker run --rm -v "$(PWD)":/code \ 25 | --mount type=volume,source="$(BASE)_cache",target=/code/target \ 26 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 27 | cosmwasm/optimizer:0.15.0 28 | 29 | optimize-fast: 30 | cargo cw-optimizoor 31 | rename --force 's/(.*)-(.*)\.wasm/$$1\.wasm/d' artifacts/* 32 | 33 | build: optimize-fast check 34 | cargo build 35 | cargo wasm 36 | 37 | ci-build: optimize check 38 | zip -jr wasm_codes.zip artifacts 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CW Hyperlane 2 | 3 | [![codecov](https://codecov.io/gh/many-things/cw-hyperlane/branch/main/graph/badge.svg?token=SGYE7FBTAO)](https://codecov.io/gh/many-things/cw-hyperlane) 4 | [![crates.io](https://img.shields.io/crates/v/hpl-interface)](https://crates.io/crates/hpl-interface) 5 | 6 | ## Table of Contents 7 | 8 | - [Architecture](#architecture) 9 | - [Project Structure](#project-structure) 10 | - [Prerequisites](#prerequisites) 11 | - [How to build](#how-to-build) 12 | - [How to test](#how-to-test) 13 | - [How to deploy](#how-to-deploy) 14 | 15 | ## Architecture 16 | 17 | ![Architecture](./asset/hyperlane-all.png) 18 | 19 | ## Prerequisites 20 | 21 | - rust (wasm32-wasm32-unknown target) 22 | - go 1.20 or higher 23 | - llvm-cov 24 | 25 | ## How to build 26 | 27 | ```bash 28 | make install-dev 29 | 30 | make build 31 | ``` 32 | 33 | ## How to test 34 | 35 | ```bash 36 | cargo test --workspace --exclude hpl-tests 37 | 38 | cargo llvm-cov --workspace --exclude hpl-tests 39 | ``` 40 | 41 | ## [How to deploy](./DEPLOYMENT.md) 42 | 43 | ## Project Structure 44 | 45 | ```text 46 | ├── contracts 47 | │ │ 48 | │ ├── core 49 | │ │ ├── mailbox 50 | │ │ └── va 51 | │ │ 52 | │ ├── hooks 53 | │ │ ├── aggregate 54 | │ │ ├── fee # protocol fee 55 | │ │ ├── merkle 56 | │ │ ├── pausable 57 | │ │ ├── routing 58 | │ │ ├── routing-custom 59 | │ │ └── routing-fallback 60 | │ │ 61 | │ ├── igps # also this is a part of `hook` 62 | │ │ ├── core 63 | │ │ └── oracle 64 | │ │ 65 | │ ├── isms 66 | │ │ ├── aggregate 67 | │ │ ├── multisig 68 | │ │ ├── pausable 69 | │ │ └── routing 70 | │ │ 71 | │ ├── mocks # for testing 72 | │ │ ├── mock-hook 73 | │ │ ├── mock-ism 74 | │ │ └── mock-msg-receiver 75 | │ │ 76 | │ └── warp 77 | │ ├── cw20 78 | │ └── native 79 | │ 80 | ├── integration-test 81 | │ 82 | ├── packages 83 | │ │ 84 | │ ├── connection # same as `MailboxClient` of evm implementation 85 | │ ├── interface # package for contract interfaces (external) 86 | │ ├── ownable 87 | │ ├── pausable 88 | │ └── router 89 | │ 90 | ├── scripts # useful scripts for development (e.g. code uploading. contract deployment) 91 | │ 92 | └── ts 93 | └── sdk # typescript sdk for contract integration. (auto generated via ts-codegen) 94 | ``` 95 | -------------------------------------------------------------------------------- /asset/hyperlane-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zikc2023/cw-hyperlane/00f64a9e8df5b384c2648d3f0f38baa549f204b3/asset/hyperlane-1.png -------------------------------------------------------------------------------- /asset/hyperlane-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zikc2023/cw-hyperlane/00f64a9e8df5b384c2648d3f0f38baa549f204b3/asset/hyperlane-2.png -------------------------------------------------------------------------------- /asset/hyperlane-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zikc2023/cw-hyperlane/00f64a9e8df5b384c2648d3f0f38baa549f204b3/asset/hyperlane-3.png -------------------------------------------------------------------------------- /asset/hyperlane-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zikc2023/cw-hyperlane/00f64a9e8df5b384c2648d3f0f38baa549f204b3/asset/hyperlane-all.png -------------------------------------------------------------------------------- /codegen.ts: -------------------------------------------------------------------------------- 1 | import codegen from '@cosmwasm/ts-codegen'; 2 | import * as fs from 'fs'; 3 | import path from 'path'; 4 | 5 | const SCHEMA_DIR = process.env.SCHEMA_DIR || path.join(process.cwd(), 'schema'); 6 | 7 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 8 | const capitalize = (str: string): string => 9 | str.charAt(0).toUpperCase() + str.slice(1); 10 | 11 | const contracts = fs 12 | .readdirSync(SCHEMA_DIR, { withFileTypes: true }) 13 | .filter((c) => !c.isDirectory()) 14 | .map((c) => ({ 15 | name: c.name.replace('.json', ''), 16 | dir: SCHEMA_DIR, 17 | })); 18 | 19 | codegen({ 20 | contracts, 21 | outPath: './dist/', 22 | 23 | // options are completely optional ;) 24 | options: { 25 | bundle: { 26 | bundleFile: 'index.ts', 27 | scope: 'contracts', 28 | }, 29 | client: { 30 | enabled: true, 31 | }, 32 | messageComposer: { 33 | enabled: true, 34 | }, 35 | }, 36 | }).then(() => { 37 | console.log('✨ all done!'); 38 | }); 39 | -------------------------------------------------------------------------------- /context/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zikc2023/cw-hyperlane/00f64a9e8df5b384c2648d3f0f38baa549f204b3/context/.gitkeep -------------------------------------------------------------------------------- /context/neutron-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v0.0.6-rc6", 3 | "artifacts": { 4 | "hpl-validator-announce": 432, 5 | "hpl-ism-multisig": 431, 6 | "hpl-mailbox": 416, 7 | "hpl-igp": 433, 8 | "hpl-igp-oracle": 412, 9 | "hpl-hook-merkle": 406, 10 | "hpl-test-mock-msg-receiver": 418, 11 | "hpl-test-mock-hook": 417, 12 | "hpl-warp-native": 421, 13 | "hpl-warp-cw20": 420, 14 | "hpl-hook-pausable": 407, 15 | "hpl-hook-aggregate": 405 16 | }, 17 | "deployments": { 18 | "core": { 19 | "mailbox": { 20 | "type": "hpl_mailbox", 21 | "address": "neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4", 22 | "hexed": "0x848426d50eb2104d5c6381ec63757930b1c14659c40db8b8081e516e7c5238fc" 23 | }, 24 | "validator_announce": { 25 | "type": "hpl_validator_announce", 26 | "address": "neutron17w4q6efzym3p4c6umyp4cjf2ustjtmwfqdhd7rt2fpcpk9fmjzsq0kj0f8", 27 | "hexed": "0xf3aa0d652226e21ae35cd9035c492ae41725edc9036edf0d6a48701b153b90a0" 28 | } 29 | }, 30 | "isms": { 31 | "type": "hpl_ism_multisig", 32 | "address": "neutron1q75ky8reksqzh0lkhk9k3csvjwv74jjquahrj233xc7dvzz5fv4qtvw0qg", 33 | "hexed": "0x07a9621c79b4002bbff6bd8b68e20c9399eaca40e76e392a31363cd608544b2a" 34 | }, 35 | "hooks": { 36 | "default": { 37 | "type": "hpl_hook_merkle", 38 | "address": "neutron1e5c2qqquc86rd3q77aj2wyht40z6z3q5pclaq040ue9f5f8yuf7qnpvkzk", 39 | "hexed": "0xcd30a0001cc1f436c41ef764a712ebabc5a144140e3fd03eafe64a9a24e4e27c" 40 | }, 41 | "required": { 42 | "type": "hpl_igp", 43 | "address": "neutron1ww9yg48qvmpmedyvkrcrmjsudxeu840l5n6ywqykpqlsdq9pdxkqg2zq7e", 44 | "hexed": "0x738a4454e066c3bcb48cb0f03dca1c69b3c3d5ffa4f4470096083f0680a169ac", 45 | "oracle": { 46 | "type": "hpl_igp_oracle", 47 | "address": "neutron1sjxus3rynpwq0ncnm0m0dfun9x3flwmalsmveh4kuml0650wsq4q8n4mus", 48 | "hexed": "0x848dc84464985c07cf13dbf6f6a79329a29fbb7dfc36ccdeb6e6fefd51ee802a" 49 | } 50 | } 51 | }, 52 | "warp": { 53 | "native": [ 54 | { 55 | "id": "MantaPacific TIA.n", 56 | "type": "hpl_warp_native", 57 | "address": "neutron1ch7x3xgpnj62weyes8vfada35zff6z59kt2psqhnx9gjnt2ttqdqtva3pa", 58 | "hexed": "0xc5fc6899019cb4a7649981d89eb7b1a0929d0a85b2d41802f3315129ad4b581a" 59 | }, 60 | { 61 | "id": "Arbitrum TIA.n", 62 | "type": "hpl_warp_native", 63 | "address": "neutron1jyyjd3x0jhgswgm6nnctxvzla8ypx50tew3ayxxwkrjfxhvje6kqzvzudq", 64 | "hexed": "0x910926c4cf95d107237a9cf0b3305fe9c81351ebcba3d218ceb0e4935d92ceac" 65 | } 66 | ] 67 | }, 68 | "test": { 69 | "msg_receiver": { 70 | "type": "hpl_test_mock_msg_receiver", 71 | "address": "neutron1v0t8nztzhdsan2cv23x66xjrak9844zz9hq7gkz0w4j6xl4lmpzq89kt5g", 72 | "hexed": "63d6798962bb61d9ab0c544dad1a43ed8a7ad4422dc1e4584f7565a37ebfd844" 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /contracts/core/mailbox/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-mailbox" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | cw-utils.workspace = true 29 | 30 | serde.workspace = true 31 | schemars.workspace = true 32 | bech32.workspace = true 33 | 34 | thiserror.workspace = true 35 | 36 | hpl-utils.workspace = true 37 | hpl-ownable.workspace = true 38 | hpl-interface.workspace = true 39 | 40 | [dev-dependencies] 41 | rstest.workspace = true 42 | 43 | serde.workspace = true 44 | 45 | cw-multi-test.workspace = true 46 | ibcx-test-utils.workspace = true 47 | -------------------------------------------------------------------------------- /contracts/core/mailbox/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{Coin, StdError}; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug, PartialEq)] 5 | pub enum ContractError { 6 | #[error("{0}")] 7 | Std(#[from] StdError), 8 | 9 | #[error("{0}")] 10 | Payment(#[from] cw_utils::PaymentError), 11 | 12 | #[error("insufficient hook payment: wanted {wanted:?}, received {received:?}")] 13 | HookPayment { 14 | wanted: Vec, 15 | received: Vec, 16 | }, 17 | 18 | #[error("{0}")] 19 | CoinsError(#[from] cosmwasm_std::CoinsError), 20 | 21 | #[error("{0}")] 22 | MigrationError(#[from] hpl_utils::MigrationError), 23 | 24 | #[error("unauthorized")] 25 | Unauthorized {}, 26 | 27 | #[error("ism verify failed")] 28 | VerifyFailed {}, 29 | 30 | #[error("invalid config. reason: {reason:?}")] 31 | InvalidConfig { reason: String }, 32 | 33 | #[error("invalid address length: {len:?}")] 34 | InvalidAddressLength { len: usize }, 35 | 36 | #[error("invalid message version: {version:?}")] 37 | InvalidMessageVersion { version: u8 }, 38 | 39 | #[error("invalid destination domain: {domain:?}")] 40 | InvalidDestinationDomain { domain: u32 }, 41 | 42 | #[error("message already delivered")] 43 | AlreadyDeliveredMessage {}, 44 | } 45 | 46 | impl ContractError { 47 | pub fn invalid_config(reason: &str) -> Self { 48 | Self::InvalidConfig { 49 | reason: reason.to_string(), 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /contracts/core/mailbox/src/event.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{Addr, Event, HexBinary}; 2 | use hpl_interface::types::Message; 3 | 4 | pub fn emit_instantiated(owner: Addr) -> Event { 5 | Event::new("mailbox_instantiated").add_attribute("owner", owner) 6 | } 7 | 8 | pub fn emit_default_ism_set(owner: Addr, new_default_ism: Addr) -> Event { 9 | Event::new("mailbox_default_ism_set") 10 | .add_attribute("owner", owner) 11 | .add_attribute("new_default_ism", new_default_ism) 12 | } 13 | 14 | pub fn emit_default_hook_set(owner: Addr, new_default_hook: Addr) -> Event { 15 | Event::new("mailbox_default_hook_set") 16 | .add_attribute("owner", owner) 17 | .add_attribute("new_default_hook", new_default_hook) 18 | } 19 | 20 | pub fn emit_required_hook_set(owner: Addr, new_required_hook: Addr) -> Event { 21 | Event::new("mailbox_required_hook_set") 22 | .add_attribute("owner", owner) 23 | .add_attribute("new_required_hook", new_required_hook) 24 | } 25 | 26 | pub fn emit_dispatch_id(id: HexBinary) -> Event { 27 | Event::new("mailbox_dispatch_id").add_attribute("message_id", id.to_hex()) 28 | } 29 | 30 | pub fn emit_dispatch(msg: Message) -> Event { 31 | Event::new("mailbox_dispatch") 32 | .add_attribute("sender", msg.sender.clone().to_hex()) 33 | .add_attribute("destination", msg.dest_domain.to_string()) 34 | .add_attribute("recipient", msg.recipient.clone().to_hex()) 35 | .add_attribute("message", HexBinary::from(msg).to_hex()) 36 | } 37 | 38 | pub fn emit_process_id(id: HexBinary) -> Event { 39 | Event::new("mailbox_process_id").add_attribute("message_id", id.to_hex()) 40 | } 41 | 42 | pub fn emit_process(origin: u32, sender: HexBinary, recipient: HexBinary) -> Event { 43 | Event::new("mailbox_process") 44 | .add_attribute("origin", format!("{origin}")) 45 | .add_attribute("sender", sender.to_hex()) 46 | .add_attribute("recipient", recipient.to_hex()) 47 | } 48 | -------------------------------------------------------------------------------- /contracts/core/mailbox/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | mod error; 3 | mod event; 4 | pub mod execute; 5 | pub mod query; 6 | mod state; 7 | 8 | pub use crate::error::ContractError; 9 | 10 | pub const MAILBOX_VERSION: u8 = 3; 11 | 12 | // version info for migration info 13 | const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); 14 | const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 15 | -------------------------------------------------------------------------------- /contracts/core/mailbox/src/state.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | use cosmwasm_std::Addr; 3 | use cw_storage_plus::{Item, Map}; 4 | 5 | #[cw_serde] 6 | #[derive(Default)] 7 | pub struct Config { 8 | pub hrp: String, 9 | pub local_domain: u32, 10 | pub default_ism: Option, 11 | pub default_hook: Option, 12 | pub required_hook: Option, 13 | } 14 | 15 | #[allow(dead_code)] 16 | impl Config { 17 | pub fn new(hrp: impl Into, local_domain: u32) -> Self { 18 | Self { 19 | hrp: hrp.into(), 20 | local_domain, 21 | ..Default::default() 22 | } 23 | } 24 | 25 | pub fn with_ism(mut self, default_ism: Addr) -> Self { 26 | self.default_ism = Some(default_ism); 27 | self 28 | } 29 | 30 | pub fn with_hook(mut self, default_hook: Addr, required_hook: Addr) -> Self { 31 | self.default_hook = Some(default_hook); 32 | self.required_hook = Some(required_hook); 33 | self 34 | } 35 | 36 | pub fn get_default_ism(&self) -> Addr { 37 | self.default_ism.clone().expect("default_ism not set") 38 | } 39 | 40 | pub fn get_default_hook(&self) -> Addr { 41 | self.default_hook.clone().expect("default_hook not set") 42 | } 43 | 44 | pub fn get_required_hook(&self) -> Addr { 45 | self.required_hook.clone().expect("required_hook not set") 46 | } 47 | } 48 | 49 | #[cw_serde] 50 | pub struct Delivery { 51 | pub sender: Addr, 52 | pub block_number: u64, 53 | } 54 | 55 | pub const CONFIG_KEY: &str = "config"; 56 | pub const CONFIG: Item = Item::new(CONFIG_KEY); 57 | 58 | pub const NONCE_KEY: &str = "nonce"; 59 | pub const NONCE: Item = Item::new(NONCE_KEY); 60 | 61 | pub const LATEST_DISPATCHED_ID_KEY: &str = "latest_dispatched_id"; 62 | pub const LATEST_DISPATCHED_ID: Item> = Item::new(LATEST_DISPATCHED_ID_KEY); 63 | 64 | pub const DELIVERIES_PREFIX: &str = "deliveries"; 65 | pub const DELIVERIES: Map, Delivery> = Map::new(DELIVERIES_PREFIX); 66 | -------------------------------------------------------------------------------- /contracts/core/va/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-validator-announce" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | 29 | k256.workspace = true 30 | sha2.workspace = true 31 | ripemd.workspace = true 32 | 33 | bech32.workspace = true 34 | schemars.workspace = true 35 | serde.workspace = true 36 | 37 | thiserror.workspace = true 38 | 39 | hpl-utils.workspace = true 40 | hpl-interface.workspace = true 41 | 42 | [dev-dependencies] 43 | rstest.workspace = true 44 | ibcx-test-utils.workspace = true 45 | 46 | sha3.workspace = true 47 | digest.workspace = true 48 | 49 | anyhow.workspace = true 50 | -------------------------------------------------------------------------------- /contracts/core/va/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{RecoverPubkeyError, StdError}; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum ContractError { 6 | #[error("{0}")] 7 | Std(#[from] StdError), 8 | 9 | #[error("{0}")] 10 | RecoverPubkeyError(#[from] RecoverPubkeyError), 11 | 12 | #[error("{0}")] 13 | MigrationError(#[from] hpl_utils::MigrationError), 14 | 15 | #[error("unauthorized. reason: {0}")] 16 | Unauthorized(String), 17 | 18 | #[error("invalid address. reason: {0}")] 19 | InvalidAddress(String), 20 | 21 | #[error("verify failed")] 22 | VerifyFailed {}, 23 | } 24 | 25 | impl ContractError { 26 | pub fn unauthorized(reason: &str) -> Self { 27 | ContractError::Unauthorized(reason.into()) 28 | } 29 | 30 | pub fn invalid_addr(reason: &str) -> Self { 31 | ContractError::InvalidAddress(reason.into()) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/core/va/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | pub mod error; 3 | mod state; 4 | 5 | // version info for migration info 6 | pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); 7 | pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 8 | -------------------------------------------------------------------------------- /contracts/core/va/src/state.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::Empty; 2 | use cw_storage_plus::{Item, Map}; 3 | 4 | pub const MAILBOX_KEY: &str = "mailbox"; 5 | pub const MAILBOX: Item> = Item::new(MAILBOX_KEY); 6 | 7 | pub const LOCAL_DOMAIN_KEY: &str = "local_domain"; 8 | pub const LOCAL_DOMAIN: Item = Item::new(LOCAL_DOMAIN_KEY); 9 | 10 | pub const VALIDATORS_PREFIX: &str = "validators"; 11 | pub const VALIDATORS: Map, Empty> = Map::new(VALIDATORS_PREFIX); 12 | 13 | pub const STORAGE_LOCATIONS_PREFIX: &str = "storage_locations"; 14 | pub const STORAGE_LOCATIONS: Map, Vec> = Map::new(STORAGE_LOCATIONS_PREFIX); 15 | 16 | pub const REPLAY_PROTECTIONS_PREFIX: &str = "replay_protections"; 17 | pub const REPLAY_PROTECITONS: Map, Empty> = Map::new(REPLAY_PROTECTIONS_PREFIX); 18 | -------------------------------------------------------------------------------- /contracts/hooks/aggregate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-hook-aggregate" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | cw-utils.workspace = true 29 | 30 | schemars.workspace = true 31 | serde-json-wasm.workspace = true 32 | 33 | thiserror.workspace = true 34 | 35 | hpl-utils.workspace = true 36 | hpl-ownable.workspace = true 37 | hpl-interface.workspace = true 38 | 39 | [dev-dependencies] 40 | rstest.workspace = true 41 | ibcx-test-utils.workspace = true 42 | 43 | anyhow.workspace = true 44 | -------------------------------------------------------------------------------- /contracts/hooks/fee/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-hook-fee" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | cw-utils.workspace = true 29 | 30 | schemars.workspace = true 31 | serde-json-wasm.workspace = true 32 | 33 | thiserror.workspace = true 34 | 35 | hpl-utils.workspace = true 36 | hpl-ownable.workspace = true 37 | hpl-interface.workspace = true 38 | 39 | [dev-dependencies] 40 | rstest.workspace = true 41 | ibcx-test-utils.workspace = true 42 | 43 | anyhow.workspace = true 44 | -------------------------------------------------------------------------------- /contracts/hooks/merkle/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-hook-merkle" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | cw-utils.workspace = true 29 | 30 | schemars.workspace = true 31 | serde-json-wasm.workspace = true 32 | 33 | thiserror.workspace = true 34 | 35 | hpl-utils.workspace = true 36 | hpl-ownable.workspace = true 37 | hpl-interface.workspace = true 38 | 39 | [dev-dependencies] 40 | rstest.workspace = true 41 | ibcx-test-utils.workspace = true 42 | 43 | anyhow.workspace = true 44 | -------------------------------------------------------------------------------- /contracts/hooks/pausable/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-hook-pausable" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | cw-utils.workspace = true 29 | 30 | schemars.workspace = true 31 | serde-json-wasm.workspace = true 32 | 33 | thiserror.workspace = true 34 | 35 | hpl-utils.workspace = true 36 | hpl-ownable.workspace = true 37 | hpl-pausable.workspace = true 38 | hpl-interface.workspace = true 39 | 40 | [dev-dependencies] 41 | rstest.workspace = true 42 | ibcx-test-utils.workspace = true 43 | 44 | anyhow.workspace = true 45 | -------------------------------------------------------------------------------- /contracts/hooks/routing-custom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-hook-routing-custom" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | cw-utils.workspace = true 29 | 30 | schemars.workspace = true 31 | serde-json-wasm.workspace = true 32 | 33 | thiserror.workspace = true 34 | 35 | hpl-utils.workspace = true 36 | hpl-ownable.workspace = true 37 | hpl-router.workspace = true 38 | hpl-interface.workspace = true 39 | 40 | [dev-dependencies] 41 | rstest.workspace = true 42 | ibcx-test-utils.workspace = true 43 | 44 | anyhow.workspace = true 45 | -------------------------------------------------------------------------------- /contracts/hooks/routing-fallback/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-hook-routing-fallback" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | cw-utils.workspace = true 29 | 30 | schemars.workspace = true 31 | serde-json-wasm.workspace = true 32 | 33 | thiserror.workspace = true 34 | 35 | hpl-utils.workspace = true 36 | hpl-ownable.workspace = true 37 | hpl-router.workspace = true 38 | hpl-interface.workspace = true 39 | 40 | [dev-dependencies] 41 | rstest.workspace = true 42 | ibcx-test-utils.workspace = true 43 | 44 | anyhow.workspace = true 45 | -------------------------------------------------------------------------------- /contracts/hooks/routing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-hook-routing" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | cw-utils.workspace = true 29 | 30 | schemars.workspace = true 31 | serde-json-wasm.workspace = true 32 | 33 | thiserror.workspace = true 34 | 35 | hpl-utils.workspace = true 36 | hpl-ownable.workspace = true 37 | hpl-router.workspace = true 38 | hpl-interface.workspace = true 39 | 40 | [dev-dependencies] 41 | rstest.workspace = true 42 | ibcx-test-utils.workspace = true 43 | 44 | anyhow.workspace = true 45 | -------------------------------------------------------------------------------- /contracts/igps/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-igp" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | cw-utils.workspace = true 29 | 30 | serde.workspace = true 31 | prost.workspace = true 32 | schemars.workspace = true 33 | 34 | thiserror.workspace = true 35 | 36 | hpl-utils.workspace = true 37 | hpl-ownable.workspace = true 38 | hpl-router.workspace = true 39 | hpl-interface.workspace = true 40 | 41 | [dev-dependencies] 42 | rstest.workspace = true 43 | ibcx-test-utils.workspace = true 44 | 45 | serde.workspace = true 46 | 47 | anyhow.workspace = true 48 | -------------------------------------------------------------------------------- /contracts/igps/core/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::Uint256; 2 | 3 | #[derive(thiserror::Error, Debug, PartialEq)] 4 | pub enum ContractError { 5 | #[error("{0}")] 6 | Std(#[from] cosmwasm_std::StdError), 7 | 8 | #[error("{0}")] 9 | PaymentError(#[from] cw_utils::PaymentError), 10 | 11 | #[error("{0}")] 12 | ParseIntError(#[from] std::num::ParseIntError), 13 | 14 | #[error("{0}")] 15 | MigrationError(#[from] hpl_utils::MigrationError), 16 | 17 | #[error("unauthorized")] 18 | Unauthorized {}, 19 | 20 | #[error("insufficient funds: needed {gas_needed:?}, but only received {received:?}")] 21 | InsufficientFunds { 22 | received: Uint256, 23 | gas_needed: Uint256, 24 | }, 25 | 26 | #[error("invalid config. reason: {reason:?}")] 27 | InvalidConfig { reason: String }, 28 | 29 | #[error("gas oracle not found for {0}")] 30 | GasOracleNotFound(u32), 31 | } 32 | 33 | impl ContractError { 34 | pub fn invalid_config(reason: &str) -> Self { 35 | Self::InvalidConfig { 36 | reason: reason.to_string(), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /contracts/igps/core/src/event.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{Addr, Coin, Event, HexBinary, Uint128, Uint256}; 2 | 3 | pub fn emit_set_default_gas(owner: Addr, default_gas: u128) -> Event { 4 | Event::new("igp-core-set-default-gas") 5 | .add_attribute("owner", owner) 6 | .add_attribute("default-gas", default_gas.to_string()) 7 | } 8 | 9 | pub fn emit_set_gas_for_domain(owner: Addr, gas_for_domain: Vec<(u32, u128)>) -> Event { 10 | Event::new("igp-core-set-gas-for-domain") 11 | .add_attribute("owner", owner) 12 | .add_attribute( 13 | "domains", 14 | gas_for_domain 15 | .into_iter() 16 | .map(|v| v.0.to_string()) 17 | .collect::>() 18 | .join(","), 19 | ) 20 | } 21 | 22 | pub fn emit_unset_gas_for_domain(owner: Addr, domains: Vec) -> Event { 23 | Event::new("igp-core-unset-gas-for-domain") 24 | .add_attribute("owner", owner) 25 | .add_attribute( 26 | "domains", 27 | domains 28 | .into_iter() 29 | .map(|v| v.to_string()) 30 | .collect::>() 31 | .join(","), 32 | ) 33 | } 34 | 35 | pub fn emit_set_beneficiary(owner: Addr, beneficiary: String) -> Event { 36 | Event::new("igp-core-set-beneficiary") 37 | .add_attribute("owner", owner) 38 | .add_attribute("beneficiary", beneficiary) 39 | } 40 | 41 | pub fn emit_claim(beneficiary: Addr, balance: Coin) -> Event { 42 | Event::new("igp-core-claim") 43 | .add_attribute("beneficiary", beneficiary) 44 | .add_attribute("collected", balance.to_string()) 45 | } 46 | 47 | pub fn emit_post_dispatch(metadata: HexBinary, message: HexBinary) -> Event { 48 | Event::new("igp-core-post-dispatch") 49 | .add_attribute( 50 | "metadata", 51 | if metadata.is_empty() { 52 | "0x".to_string() 53 | } else { 54 | metadata.to_string() 55 | }, 56 | ) 57 | .add_attribute("message", message.to_string()) 58 | } 59 | 60 | pub fn emit_pay_for_gas( 61 | sender: Addr, 62 | dest_domain: u32, 63 | message_id: HexBinary, 64 | gas_amount: Uint256, 65 | gas_refunded: Uint128, 66 | gas_required: Uint256, 67 | payment: Uint256, 68 | ) -> Event { 69 | Event::new("igp-core-pay-for-gas") 70 | .add_attribute("sender", sender) 71 | .add_attribute("dest_domain", dest_domain.to_string()) 72 | .add_attribute("message_id", message_id.to_hex()) 73 | .add_attribute("gas_amount", gas_amount) 74 | .add_attribute("gas_refunded", gas_refunded) 75 | .add_attribute("gas_required", gas_required) 76 | .add_attribute("payment", payment) 77 | } 78 | -------------------------------------------------------------------------------- /contracts/igps/core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | mod error; 3 | mod event; 4 | pub mod execute; 5 | pub mod query; 6 | 7 | #[cfg(test)] 8 | pub mod tests; 9 | 10 | use cosmwasm_std::{Addr, StdResult, Storage}; 11 | use cw_storage_plus::{Item, Map}; 12 | pub use error::ContractError; 13 | 14 | // version info for migration info 15 | pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); 16 | pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 17 | 18 | // constants 19 | pub const TOKEN_EXCHANGE_RATE_SCALE: u128 = 10_000_000_000; 20 | 21 | pub const HRP_KEY: &str = "hrp"; 22 | pub const HRP: Item = Item::new(HRP_KEY); 23 | 24 | pub const GAS_TOKEN_KEY: &str = "gas_token"; 25 | pub const GAS_TOKEN: Item = Item::new(GAS_TOKEN_KEY); 26 | 27 | pub const DEFAULT_GAS_USAGE_KEY: &str = "default_gas_usage"; 28 | pub const DEFAULT_GAS_USAGE: Item = Item::new(DEFAULT_GAS_USAGE_KEY); 29 | 30 | pub const GAS_FOR_DOMAIN_PREFIX: &str = "gas_for_domain"; 31 | pub const GAS_FOR_DOMAIN: Map = Map::new(GAS_FOR_DOMAIN_PREFIX); 32 | 33 | pub const BENEFICIARY_KEY: &str = "beneficiary"; 34 | pub const BENEFICIARY: Item = Item::new(BENEFICIARY_KEY); 35 | 36 | pub fn get_default_gas(storage: &dyn Storage, domain: u32) -> StdResult { 37 | let custom_gas = GAS_FOR_DOMAIN.may_load(storage, domain)?; 38 | let default_gas = DEFAULT_GAS_USAGE.load(storage)?; 39 | 40 | Ok(custom_gas.unwrap_or(default_gas)) 41 | } 42 | -------------------------------------------------------------------------------- /contracts/igps/oracle/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-igp-oracle" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | 29 | schemars.workspace = true 30 | 31 | thiserror.workspace = true 32 | 33 | hpl-utils.workspace = true 34 | hpl-ownable.workspace = true 35 | hpl-interface.workspace = true 36 | 37 | [dev-dependencies] 38 | serde.workspace = true 39 | 40 | cw-multi-test.workspace = true 41 | 42 | anyhow.workspace = true 43 | -------------------------------------------------------------------------------- /contracts/igps/oracle/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::StdError; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum ContractError { 6 | #[error("{0}")] 7 | Std(#[from] StdError), 8 | 9 | #[error("{0}")] 10 | MigrationError(#[from] hpl_utils::MigrationError), 11 | 12 | #[error("unauthorized")] 13 | Unauthorized {}, 14 | 15 | #[error("invalid config. reason: {reason:?}")] 16 | InvalidConfig { reason: String }, 17 | } 18 | 19 | impl ContractError { 20 | pub fn invalid_config(reason: &str) -> Self { 21 | Self::InvalidConfig { 22 | reason: reason.to_string(), 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/igps/oracle/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | pub mod error; 3 | pub mod state; 4 | 5 | #[cfg(test)] 6 | pub mod tests; 7 | 8 | // version info for migration info 9 | pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); 10 | pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 11 | -------------------------------------------------------------------------------- /contracts/igps/oracle/src/state.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | use cosmwasm_std::{StdResult, Storage, Uint128}; 3 | use cw_storage_plus::Map; 4 | use hpl_interface::igp::oracle::RemoteGasDataConfig; 5 | 6 | #[cw_serde] 7 | pub struct RemoteGasData { 8 | pub token_exchange_rate: Uint128, 9 | pub gas_price: Uint128, 10 | } 11 | 12 | pub const REMOTE_GAS_DATA_PREFIX: &str = "remote_gas_data"; 13 | pub const REMOTE_GAS_DATA: Map = Map::new(REMOTE_GAS_DATA_PREFIX); 14 | 15 | pub fn insert_gas_data(storage: &mut dyn Storage, config: RemoteGasDataConfig) -> StdResult<()> { 16 | REMOTE_GAS_DATA.save( 17 | storage, 18 | config.remote_domain, 19 | &RemoteGasData { 20 | token_exchange_rate: config.token_exchange_rate, 21 | gas_price: config.gas_price, 22 | }, 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /contracts/igps/oracle/src/tests/contract.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{ 2 | testing::{mock_dependencies, mock_env}, 3 | Addr, Uint128, 4 | }; 5 | use hpl_interface::igp::oracle::RemoteGasDataConfig; 6 | 7 | use crate::{error::ContractError, tests::IGPGasOracle}; 8 | 9 | #[test] 10 | fn test_gas_data() -> anyhow::Result<()> { 11 | let owner = Addr::unchecked("owner"); 12 | let deployer = Addr::unchecked("deployer"); 13 | 14 | let mut oracle = IGPGasOracle::new(mock_dependencies(), mock_env()); 15 | oracle.init(&deployer, &owner)?; 16 | 17 | // test single 18 | let gas_config = RemoteGasDataConfig { 19 | remote_domain: 1u32, 20 | token_exchange_rate: Uint128::new(120921), 21 | gas_price: Uint128::new(9120321), 22 | }; 23 | 24 | oracle.set_remote_gas_data(&owner, gas_config.clone())?; 25 | 26 | let ret = oracle.get_exchange_rate_and_gas_price(gas_config.remote_domain)?; 27 | assert_eq!(ret.exchange_rate, gas_config.token_exchange_rate); 28 | assert_eq!(ret.gas_price, gas_config.gas_price); 29 | 30 | // test multi 31 | let gas_config = RemoteGasDataConfig { 32 | remote_domain: 2u32, 33 | token_exchange_rate: Uint128::new(120921), 34 | gas_price: Uint128::new(9120321), 35 | }; 36 | 37 | oracle.set_remote_gas_data_configs(&owner, vec![gas_config.clone()])?; 38 | 39 | let ret = oracle.get_exchange_rate_and_gas_price(gas_config.remote_domain)?; 40 | assert_eq!(ret.exchange_rate, gas_config.token_exchange_rate); 41 | assert_eq!(ret.gas_price, gas_config.gas_price); 42 | 43 | Ok(()) 44 | } 45 | 46 | #[test] 47 | fn test_set_remote_gas_data_configs() -> anyhow::Result<()> { 48 | let owner = Addr::unchecked("owner"); 49 | let deployer = Addr::unchecked("deployer"); 50 | let abuser = Addr::unchecked("abuser"); 51 | 52 | let mut oracle = IGPGasOracle::new(mock_dependencies(), mock_env()); 53 | oracle.init(&deployer, &owner)?; 54 | 55 | // fail - sender is not owner 56 | let err = oracle 57 | .set_remote_gas_data_configs(&abuser, vec![]) 58 | .unwrap_err(); 59 | assert!(matches!(err, ContractError::Unauthorized {})); 60 | 61 | // ok 62 | oracle.set_remote_gas_data_configs(&owner, vec![])?; 63 | 64 | Ok(()) 65 | } 66 | 67 | #[test] 68 | fn test_set_remote_gas_data() -> anyhow::Result<()> { 69 | let owner = Addr::unchecked("owner"); 70 | let deployer = Addr::unchecked("deployer"); 71 | let abuser = Addr::unchecked("abuser"); 72 | 73 | let mut oracle = IGPGasOracle::new(mock_dependencies(), mock_env()); 74 | oracle.init(&deployer, &owner)?; 75 | 76 | let gas_config = RemoteGasDataConfig { 77 | remote_domain: 1u32, 78 | token_exchange_rate: Uint128::new(103202), 79 | gas_price: Uint128::new(120943), 80 | }; 81 | 82 | // fail - sender is not owner 83 | let err = oracle 84 | .set_remote_gas_data(&abuser, gas_config.clone()) 85 | .unwrap_err(); 86 | assert!(matches!(err, ContractError::Unauthorized {})); 87 | 88 | // ok 89 | oracle.set_remote_gas_data(&owner, gas_config)?; 90 | 91 | Ok(()) 92 | } 93 | -------------------------------------------------------------------------------- /contracts/igps/oracle/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{ 2 | from_json, testing::mock_info, Addr, Api, Empty, Env, MessageInfo, OwnedDeps, Querier, 3 | Response, Storage, 4 | }; 5 | use hpl_interface::igp::oracle::{ 6 | ExecuteMsg, GetExchangeRateAndGasPriceResponse, IgpGasOracleQueryMsg, InstantiateMsg, QueryMsg, 7 | RemoteGasDataConfig, 8 | }; 9 | use serde::de::DeserializeOwned; 10 | 11 | use crate::{ 12 | contract::{execute, instantiate, query}, 13 | error::ContractError, 14 | }; 15 | 16 | mod contract; 17 | 18 | pub struct IGPGasOracle { 19 | pub deps: OwnedDeps, 20 | pub env: Env, 21 | } 22 | 23 | impl IGPGasOracle 24 | where 25 | S: Storage, 26 | A: Api, 27 | Q: Querier, 28 | { 29 | pub fn new(deps: OwnedDeps, env: Env) -> Self { 30 | Self { deps, env } 31 | } 32 | 33 | pub fn with_env(&mut self, env: Env) { 34 | self.env = env 35 | } 36 | 37 | pub fn init(&mut self, sender: &Addr, owner: &Addr) -> Result { 38 | instantiate( 39 | self.deps.as_mut(), 40 | self.env.clone(), 41 | mock_info(sender.as_str(), &[]), 42 | InstantiateMsg { 43 | owner: owner.into(), 44 | }, 45 | ) 46 | } 47 | 48 | fn execute(&mut self, info: MessageInfo, msg: ExecuteMsg) -> Result { 49 | execute(self.deps.as_mut(), self.env.clone(), info, msg) 50 | } 51 | 52 | fn query(&self, msg: QueryMsg) -> Result { 53 | query(self.deps.as_ref(), self.env.clone(), msg) 54 | .map(|v| from_json::(&v))? 55 | .map_err(|e| e.into()) 56 | } 57 | 58 | pub fn set_remote_gas_data_configs( 59 | &mut self, 60 | sender: &Addr, 61 | configs: Vec, 62 | ) -> Result { 63 | self.execute( 64 | mock_info(sender.as_str(), &[]), 65 | ExecuteMsg::SetRemoteGasDataConfigs { configs }, 66 | ) 67 | } 68 | 69 | pub fn set_remote_gas_data( 70 | &mut self, 71 | sender: &Addr, 72 | config: RemoteGasDataConfig, 73 | ) -> Result { 74 | self.execute( 75 | mock_info(sender.as_str(), &[]), 76 | ExecuteMsg::SetRemoteGasData { config }, 77 | ) 78 | } 79 | 80 | pub fn get_exchange_rate_and_gas_price( 81 | &self, 82 | dest_domain: u32, 83 | ) -> Result { 84 | self.query(IgpGasOracleQueryMsg::GetExchangeRateAndGasPrice { dest_domain }.wrap()) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /contracts/isms/aggregate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-ism-aggregate" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | 29 | sha2.workspace = true 30 | ripemd.workspace = true 31 | 32 | bech32.workspace = true 33 | schemars.workspace = true 34 | 35 | thiserror.workspace = true 36 | 37 | hpl-utils.workspace = true 38 | hpl-ownable.workspace = true 39 | hpl-interface.workspace = true 40 | 41 | [dev-dependencies] 42 | 43 | serde.workspace = true 44 | anyhow.workspace = true 45 | -------------------------------------------------------------------------------- /contracts/isms/aggregate/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{StdError, VerificationError}; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug, PartialEq)] 5 | pub enum ContractError { 6 | #[error("{0}")] 7 | Std(#[from] StdError), 8 | 9 | #[error("{0}")] 10 | VerificationError(#[from] VerificationError), 11 | 12 | #[error("{0}")] 13 | MigrationError(#[from] hpl_utils::MigrationError), 14 | 15 | #[error("unauthorized")] 16 | Unauthorized, 17 | 18 | #[error("invalid threshold. reason: {0}")] 19 | InvalidThreshold(String), 20 | } 21 | -------------------------------------------------------------------------------- /contracts/isms/multisig/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-ism-multisig" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | 29 | sha2.workspace = true 30 | ripemd.workspace = true 31 | 32 | bech32.workspace = true 33 | schemars.workspace = true 34 | 35 | thiserror.workspace = true 36 | 37 | hpl-utils.workspace = true 38 | hpl-ownable.workspace = true 39 | hpl-interface.workspace = true 40 | 41 | [dev-dependencies] 42 | rstest.workspace = true 43 | ibcx-test-utils.workspace = true 44 | cw-multi-test.workspace = true 45 | k256.workspace = true 46 | -------------------------------------------------------------------------------- /contracts/isms/multisig/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{RecoverPubkeyError, StdError, VerificationError}; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum ContractError { 6 | #[error("{0}")] 7 | Std(#[from] StdError), 8 | 9 | #[error("{0}")] 10 | VerificationError(#[from] VerificationError), 11 | 12 | #[error("{0}")] 13 | RecoverPubkeyError(#[from] RecoverPubkeyError), 14 | 15 | #[error("{0}")] 16 | MigrationError(#[from] hpl_utils::MigrationError), 17 | 18 | #[error("unauthorized")] 19 | Unauthorized, 20 | 21 | #[error("wrong length")] 22 | WrongLength, 23 | 24 | #[error("invalid pubkey")] 25 | InvalidPubKey, 26 | 27 | #[error("invalid address. reason: {0}")] 28 | InvalidAddress(String), 29 | 30 | #[error("invalid arguments. reason: {reason:?}")] 31 | InvalidArguments { reason: String }, 32 | 33 | #[error("duplicate validator")] 34 | ValidatorDuplicate, 35 | 36 | #[error("validator not exists")] 37 | ValidatorNotExist, 38 | } 39 | 40 | impl ContractError { 41 | pub fn invalid_addr(reason: &str) -> Self { 42 | ContractError::InvalidAddress(reason.into()) 43 | } 44 | 45 | pub fn invalid_args(reason: &str) -> Self { 46 | ContractError::InvalidArguments { 47 | reason: reason.into(), 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/isms/multisig/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | mod error; 3 | pub mod query; 4 | pub mod state; 5 | 6 | use cosmwasm_std::{HexBinary, StdResult}; 7 | use hpl_interface::types::keccak256_hash; 8 | 9 | pub use crate::error::ContractError; 10 | 11 | // version info for migration info 12 | pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); 13 | pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 14 | 15 | pub fn domain_hash(local_domain: u32, address: HexBinary) -> StdResult { 16 | let mut bz = vec![]; 17 | bz.append(&mut local_domain.to_be_bytes().to_vec()); 18 | bz.append(&mut address.to_vec()); 19 | bz.append(&mut "HYPERLANE".as_bytes().to_vec()); 20 | 21 | let hash = keccak256_hash(&bz); 22 | 23 | Ok(hash) 24 | } 25 | 26 | pub fn multisig_hash( 27 | mut domain_hash: Vec, 28 | mut root: Vec, 29 | index: u32, 30 | mut message_id: Vec, 31 | ) -> Result { 32 | let mut bz = vec![]; 33 | 34 | bz.append(&mut domain_hash); 35 | bz.append(&mut root); 36 | bz.append(&mut index.to_be_bytes().to_vec()); 37 | bz.append(&mut message_id); 38 | 39 | let hash = keccak256_hash(&bz); 40 | 41 | Ok(hash) 42 | } 43 | -------------------------------------------------------------------------------- /contracts/isms/multisig/src/state.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::HexBinary; 2 | use cw_storage_plus::Map; 3 | 4 | pub const VALIDATORS_PREFIX: &str = "validators"; 5 | pub const VALIDATORS: Map> = Map::new(VALIDATORS_PREFIX); 6 | 7 | pub const THRESHOLD_PREFIX: &str = "threshold"; 8 | pub const THRESHOLD: Map = Map::new(THRESHOLD_PREFIX); 9 | -------------------------------------------------------------------------------- /contracts/isms/pausable/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-ism-pausable" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | cw-utils.workspace = true 29 | 30 | schemars.workspace = true 31 | serde-json-wasm.workspace = true 32 | 33 | thiserror.workspace = true 34 | 35 | hpl-utils.workspace = true 36 | hpl-ownable.workspace = true 37 | hpl-pausable.workspace = true 38 | hpl-interface.workspace = true 39 | 40 | [dev-dependencies] 41 | rstest.workspace = true 42 | ibcx-test-utils.workspace = true 43 | 44 | anyhow.workspace = true 45 | -------------------------------------------------------------------------------- /contracts/isms/routing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-ism-routing" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | 29 | sha2.workspace = true 30 | ripemd.workspace = true 31 | 32 | bech32.workspace = true 33 | schemars.workspace = true 34 | 35 | thiserror.workspace = true 36 | 37 | hpl-utils.workspace = true 38 | hpl-interface.workspace = true 39 | hpl-ownable.workspace = true 40 | 41 | [dev-dependencies] 42 | 43 | serde.workspace = true 44 | anyhow.workspace = true 45 | -------------------------------------------------------------------------------- /contracts/isms/routing/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{StdError, VerificationError}; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug, PartialEq)] 5 | pub enum ContractError { 6 | #[error("{0}")] 7 | Std(#[from] StdError), 8 | 9 | #[error("{0}")] 10 | VerificationError(#[from] VerificationError), 11 | 12 | #[error("{0}")] 13 | MigrationError(#[from] hpl_utils::MigrationError), 14 | 15 | #[error("Unauthorized")] 16 | Unauthorized, 17 | 18 | #[error("RouteNotFound")] 19 | RouteNotFound {}, 20 | } 21 | -------------------------------------------------------------------------------- /contracts/isms/routing/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | mod error; 3 | pub mod state; 4 | 5 | #[cfg(test)] 6 | mod tests; 7 | 8 | use cosmwasm_std::Event; 9 | 10 | pub use crate::error::ContractError; 11 | 12 | fn new_event(name: &str) -> Event { 13 | Event::new(format!("hpl_ism_routing::{}", name)) 14 | } 15 | 16 | // version info for migration info 17 | pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); 18 | pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 19 | -------------------------------------------------------------------------------- /contracts/isms/routing/src/state.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::Addr; 2 | use cw_storage_plus::Map; 3 | 4 | pub const MODULES_PREFIX: &str = "modules"; 5 | pub const MODULES: Map = Map::new(MODULES_PREFIX); 6 | -------------------------------------------------------------------------------- /contracts/isms/routing/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{ 2 | from_json, 3 | testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, 4 | Addr, Empty, Env, HexBinary, MessageInfo, OwnedDeps, Response, 5 | }; 6 | use hpl_interface::ism::{ 7 | routing::{ExecuteMsg, InstantiateMsg, IsmSet, QueryMsg, RouteResponse, RoutingIsmQueryMsg}, 8 | IsmQueryMsg, ModuleTypeResponse, VerifyResponse, 9 | }; 10 | use serde::de::DeserializeOwned; 11 | 12 | use crate::{ 13 | contract::{execute, instantiate, query}, 14 | ContractError, 15 | }; 16 | 17 | mod contract; 18 | 19 | pub struct IsmRouting { 20 | pub deps: OwnedDeps, 21 | pub env: Env, 22 | } 23 | 24 | impl Default for IsmRouting { 25 | fn default() -> Self { 26 | Self { 27 | deps: mock_dependencies(), 28 | env: mock_env(), 29 | } 30 | } 31 | } 32 | 33 | impl IsmRouting { 34 | #[allow(dead_code)] 35 | pub fn new(deps: OwnedDeps, env: Env) -> Self { 36 | Self { deps, env } 37 | } 38 | 39 | pub fn init( 40 | &mut self, 41 | sender: &Addr, 42 | owner: &Addr, 43 | isms: Vec, 44 | ) -> Result { 45 | instantiate( 46 | self.deps.as_mut(), 47 | self.env.clone(), 48 | mock_info(sender.as_str(), &[]), 49 | InstantiateMsg { 50 | owner: owner.to_string(), 51 | isms, 52 | }, 53 | ) 54 | } 55 | 56 | fn execute(&mut self, info: MessageInfo, msg: ExecuteMsg) -> Result { 57 | execute(self.deps.as_mut(), self.env.clone(), info, msg) 58 | } 59 | 60 | fn query(&self, msg: QueryMsg) -> Result { 61 | query(self.deps.as_ref(), self.env.clone(), msg) 62 | .map(|v| from_json::(&v))? 63 | .map_err(|e| e.into()) 64 | } 65 | 66 | pub fn set(&mut self, sender: &Addr, ism: &IsmSet) -> Result { 67 | self.execute( 68 | mock_info(sender.as_str(), &[]), 69 | ExecuteMsg::Set { ism: ism.clone() }, 70 | ) 71 | } 72 | 73 | pub fn get_module_type(&self) -> Result { 74 | self.query(QueryMsg::Ism(IsmQueryMsg::ModuleType {})) 75 | } 76 | 77 | pub fn query_verify( 78 | &self, 79 | metadata: HexBinary, 80 | message: HexBinary, 81 | ) -> Result { 82 | self.query(QueryMsg::Ism(IsmQueryMsg::Verify { metadata, message })) 83 | } 84 | 85 | pub fn query_route(&self, message: HexBinary) -> Result { 86 | self.query(QueryMsg::RoutingIsm(RoutingIsmQueryMsg::Route { message })) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /contracts/mocks/mock-hook/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-test-mock-hook" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw2.workspace = true 27 | cw-storage-plus.workspace = true 28 | 29 | hpl-utils.workspace = true 30 | hpl-interface.workspace = true 31 | 32 | [dev-dependencies] 33 | ibcx-test-utils.workspace = true 34 | cw-multi-test.workspace = true 35 | -------------------------------------------------------------------------------- /contracts/mocks/mock-hook/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | 3 | // version info for migration info 4 | pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); 5 | pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 6 | -------------------------------------------------------------------------------- /contracts/mocks/mock-ism/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-test-mock-ism" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | 29 | sha3.workspace = true 30 | ripemd.workspace = true 31 | 32 | bech32.workspace = true 33 | schemars.workspace = true 34 | 35 | thiserror.workspace = true 36 | 37 | hpl-utils.workspace = true 38 | hpl-interface.workspace = true 39 | 40 | [dev-dependencies] 41 | serde.workspace = true 42 | 43 | cw-multi-test.workspace = true 44 | -------------------------------------------------------------------------------- /contracts/mocks/mock-ism/src/contract.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | #[cfg(not(feature = "library"))] 3 | use cosmwasm_std::entry_point; 4 | use cosmwasm_std::{ 5 | to_json_binary, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Response, StdResult, 6 | }; 7 | use cw2::set_contract_version; 8 | use hpl_interface::ism::{ 9 | ExpectedIsmQueryMsg, IsmQueryMsg, IsmType, VerifyInfoResponse, VerifyResponse, 10 | }; 11 | 12 | use crate::{CONTRACT_NAME, CONTRACT_VERSION}; 13 | 14 | #[cw_serde] 15 | pub struct InstantiateMsg {} 16 | 17 | #[cw_serde] 18 | pub struct MigrateMsg {} 19 | 20 | #[cw_serde] 21 | pub struct ExecuteMsg {} 22 | 23 | #[cfg_attr(not(feature = "library"), entry_point)] 24 | pub fn instantiate( 25 | deps: DepsMut, 26 | _env: Env, 27 | _info: MessageInfo, 28 | _msg: InstantiateMsg, 29 | ) -> StdResult { 30 | set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; 31 | 32 | Ok(Response::new().add_attribute("method", "instantiate")) 33 | } 34 | 35 | /// Handling contract execution 36 | #[cfg_attr(not(feature = "library"), entry_point)] 37 | pub fn execute( 38 | _deps: DepsMut, 39 | _env: Env, 40 | _info: MessageInfo, 41 | _msg: ExecuteMsg, 42 | ) -> StdResult { 43 | Ok(Response::default()) 44 | } 45 | 46 | /// Handling contract query 47 | #[cfg_attr(not(feature = "library"), entry_point)] 48 | pub fn query(_deps: Deps, _env: Env, msg: ExpectedIsmQueryMsg) -> StdResult { 49 | use IsmQueryMsg::*; 50 | 51 | match msg { 52 | ExpectedIsmQueryMsg::Ism(msg) => match msg { 53 | ModuleType {} => Ok(to_json_binary(&IsmType::Null)?), 54 | Verify { .. } => Ok(to_json_binary(&VerifyResponse { verified: true })?), 55 | VerifyInfo { .. } => Ok(to_json_binary(&VerifyInfoResponse { 56 | threshold: 1u8, 57 | validators: vec![], 58 | })?), 59 | }, 60 | } 61 | } 62 | 63 | #[cfg_attr(not(feature = "library"), entry_point)] 64 | pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> StdResult { 65 | hpl_utils::migrate(deps.storage, CONTRACT_NAME, CONTRACT_VERSION).unwrap(); 66 | Ok(Response::default()) 67 | } 68 | -------------------------------------------------------------------------------- /contracts/mocks/mock-ism/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | 3 | // version info for migration info 4 | pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); 5 | pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 6 | -------------------------------------------------------------------------------- /contracts/mocks/mock-msg-receiver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-test-mock-msg-receiver" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw2.workspace = true 28 | 29 | sha3.workspace = true 30 | ripemd.workspace = true 31 | 32 | bech32.workspace = true 33 | schemars.workspace = true 34 | 35 | thiserror.workspace = true 36 | 37 | hpl-utils.workspace = true 38 | hpl-interface.workspace = true 39 | 40 | [dev-dependencies] 41 | serde.workspace = true 42 | 43 | cw-multi-test.workspace = true 44 | -------------------------------------------------------------------------------- /contracts/mocks/mock-msg-receiver/src/contract.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | #[cfg(not(feature = "library"))] 3 | use cosmwasm_std::entry_point; 4 | use cosmwasm_std::{ 5 | attr, to_json_binary, Deps, DepsMut, Empty, Env, Event, MessageInfo, QueryResponse, Response, 6 | StdResult, 7 | }; 8 | use cw2::set_contract_version; 9 | use cw_storage_plus::Item; 10 | use hpl_interface::{core::ExpectedHandleMsg, ism, types::bech32_encode}; 11 | 12 | use crate::{CONTRACT_NAME, CONTRACT_VERSION}; 13 | 14 | #[cw_serde] 15 | pub struct InstantiateMsg { 16 | pub hrp: String, 17 | } 18 | 19 | #[cw_serde] 20 | pub struct ExecuteMsg {} 21 | 22 | pub const HRP_KEY: &str = "hrp"; 23 | pub const HRP: Item = Item::new(HRP_KEY); 24 | 25 | #[cfg_attr(not(feature = "library"), entry_point)] 26 | pub fn instantiate( 27 | deps: DepsMut, 28 | _env: Env, 29 | _info: MessageInfo, 30 | msg: InstantiateMsg, 31 | ) -> StdResult { 32 | set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; 33 | 34 | HRP.save(deps.storage, &msg.hrp)?; 35 | 36 | Ok(Response::new().add_attribute("method", "instantiate")) 37 | } 38 | 39 | /// Handling contract execution 40 | #[cfg_attr(not(feature = "library"), entry_point)] 41 | pub fn execute( 42 | deps: DepsMut, 43 | _env: Env, 44 | _info: MessageInfo, 45 | msg: ExpectedHandleMsg, 46 | ) -> StdResult { 47 | match msg { 48 | ExpectedHandleMsg::Handle(msg) => Ok(Response::default().add_event( 49 | Event::new("mailbox_msg_received").add_attributes(vec![ 50 | attr( 51 | "sender", 52 | bech32_encode(&HRP.load(deps.storage)?, &msg.sender)?, 53 | ), 54 | attr("origin", msg.origin.to_string()), 55 | attr("body", std::str::from_utf8(&msg.body)?), 56 | ]), 57 | )), 58 | } 59 | } 60 | 61 | /// Handling contract query 62 | #[cfg_attr(not(feature = "library"), entry_point)] 63 | pub fn query( 64 | _deps: Deps, 65 | _env: Env, 66 | msg: ism::ExpectedIsmSpecifierQueryMsg, 67 | ) -> StdResult { 68 | match msg { 69 | ism::ExpectedIsmSpecifierQueryMsg::IsmSpecifier( 70 | ism::IsmSpecifierQueryMsg::InterchainSecurityModule(), 71 | ) => Ok(to_json_binary(&ism::InterchainSecurityModuleResponse { 72 | ism: None, 73 | })?), 74 | } 75 | } 76 | 77 | #[cfg_attr(not(feature = "library"), entry_point)] 78 | pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> StdResult { 79 | hpl_utils::migrate(deps.storage, CONTRACT_NAME, CONTRACT_VERSION).unwrap(); 80 | Ok(Response::default()) 81 | } 82 | -------------------------------------------------------------------------------- /contracts/mocks/mock-msg-receiver/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | 3 | // version info for migration info 4 | pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); 5 | pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 6 | -------------------------------------------------------------------------------- /contracts/warp/cw20/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-warp-cw20" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw-utils.workspace = true 28 | cw2.workspace = true 29 | cw20.workspace = true 30 | cw20-base.workspace = true 31 | 32 | sha2.workspace = true 33 | ripemd.workspace = true 34 | 35 | serde.workspace = true 36 | bech32.workspace = true 37 | schemars.workspace = true 38 | 39 | thiserror.workspace = true 40 | 41 | hpl-utils.workspace = true 42 | hpl-connection.workspace = true 43 | hpl-ownable.workspace = true 44 | hpl-router.workspace = true 45 | hpl-interface.workspace = true 46 | 47 | [dev-dependencies] 48 | serde-json-wasm.workspace = true 49 | 50 | osmosis-test-tube.workspace = true 51 | ibcx-test-utils.workspace = true 52 | rstest.workspace = true 53 | anyhow.workspace = true 54 | k256.workspace = true 55 | sha3.workspace = true 56 | -------------------------------------------------------------------------------- /contracts/warp/cw20/src/conv.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use cosmwasm_std::{wasm_execute, StdResult, Uint128, Uint256, WasmMsg}; 4 | use cw20::Cw20ExecuteMsg; 5 | 6 | use crate::error::ContractError; 7 | 8 | pub fn to_mint_msg( 9 | contract: impl Into, 10 | recipient: impl Into, 11 | amount: Uint256, 12 | ) -> Result { 13 | Ok(wasm_execute( 14 | contract, 15 | &Cw20ExecuteMsg::Mint { 16 | recipient: recipient.into(), 17 | amount: to_uint128(amount)?, 18 | }, 19 | vec![], 20 | )?) 21 | } 22 | 23 | pub fn to_burn_msg(contract: impl Into, amount: Uint128) -> StdResult { 24 | wasm_execute(contract, &Cw20ExecuteMsg::Burn { amount }, vec![]) 25 | } 26 | 27 | pub fn to_send_msg( 28 | contract: impl Into, 29 | recipient: impl Into, 30 | amount: Uint256, 31 | ) -> Result { 32 | Ok(wasm_execute( 33 | contract, 34 | &Cw20ExecuteMsg::Transfer { 35 | recipient: recipient.into(), 36 | amount: to_uint128(amount)?, 37 | }, 38 | vec![], 39 | )?) 40 | } 41 | 42 | pub fn to_uint128(v: Uint256) -> Result { 43 | Ok(Uint128::from_str(&v.to_string())?) 44 | } 45 | -------------------------------------------------------------------------------- /contracts/warp/cw20/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::StdError; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, PartialEq, Debug)] 5 | pub enum ContractError { 6 | #[error("{0}")] 7 | Std(#[from] StdError), 8 | 9 | #[error("{0}")] 10 | ParseReplyError(#[from] cw_utils::ParseReplyError), 11 | 12 | #[error("{0}")] 13 | MigrationError(#[from] hpl_utils::MigrationError), 14 | 15 | #[error("unauthorized")] 16 | Unauthorized, 17 | 18 | #[error("wrong length")] 19 | WrongLength {}, 20 | 21 | #[error("invalid token option")] 22 | InvalidTokenOption, 23 | 24 | #[error("invalid reply id")] 25 | InvalidReplyId, 26 | 27 | #[error("invalid receive msg")] 28 | InvalidReceiveMsg, 29 | 30 | #[error("no router for domain {domain:?}")] 31 | NoRouter { domain: u32 }, 32 | } 33 | -------------------------------------------------------------------------------- /contracts/warp/cw20/src/lib.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{Addr, Event}; 2 | use cw_storage_plus::Item; 3 | use hpl_interface::warp::TokenMode; 4 | 5 | pub mod contract; 6 | mod conv; 7 | pub mod error; 8 | 9 | // reply message 10 | pub const REPLY_ID_CREATE_DENOM: u64 = 0; 11 | 12 | // version info for migration info 13 | pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); 14 | pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 15 | 16 | // storage definition for token denomination 17 | const TOKEN_KEY: &str = "token"; 18 | const TOKEN: Item = Item::new(TOKEN_KEY); 19 | 20 | // storage definition for token mode 21 | const MODE_KEY: &str = "mode"; 22 | const MODE: Item = Item::new(MODE_KEY); 23 | 24 | // storage definition for token hrp 25 | const HRP_KEY: &str = "hrp"; 26 | const HRP: Item = Item::new(HRP_KEY); 27 | 28 | // storage definition for mailbox 29 | const MAILBOX_KEY: &str = "mailbox"; 30 | const MAILBOX: Item = Item::new(MAILBOX_KEY); 31 | 32 | fn new_event(name: &str) -> Event { 33 | Event::new(format!("hpl_warp_cw20::{name}")) 34 | } 35 | -------------------------------------------------------------------------------- /contracts/warp/native/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-warp-native" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cosmwasm-schema.workspace = true 25 | 26 | cw-storage-plus.workspace = true 27 | cw-utils.workspace = true 28 | cw2.workspace = true 29 | 30 | sha2.workspace = true 31 | ripemd.workspace = true 32 | 33 | bech32.workspace = true 34 | schemars.workspace = true 35 | prost.workspace = true 36 | prost-types.workspace = true 37 | serde.workspace = true 38 | serde-json-wasm.workspace = true 39 | 40 | thiserror.workspace = true 41 | 42 | hpl-utils.workspace = true 43 | hpl-connection.workspace = true 44 | hpl-ownable.workspace = true 45 | hpl-router.workspace = true 46 | hpl-interface.workspace = true 47 | 48 | [dev-dependencies] 49 | serde-json-wasm.workspace = true 50 | 51 | ibcx-test-utils.workspace = true 52 | rstest.workspace = true 53 | anyhow.workspace = true 54 | k256.workspace = true 55 | sha3.workspace = true 56 | -------------------------------------------------------------------------------- /contracts/warp/native/src/conv.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use cosmwasm_std::{coin, Addr, BankMsg, Coin, Uint128, Uint256}; 4 | use hpl_interface::warp::native; 5 | 6 | use crate::{error::ContractError, proto}; 7 | 8 | pub fn to_mint_msg(sender: &Addr, denom: &str, amount: impl ToString) -> proto::MsgMint { 9 | proto::MsgMint { 10 | sender: sender.to_string(), 11 | amount: Some(proto::Coin { 12 | denom: denom.to_string(), 13 | amount: amount.to_string(), 14 | }), 15 | } 16 | } 17 | 18 | pub fn to_burn_msg(sender: &Addr, denom: &str, amount: impl ToString) -> proto::MsgBurn { 19 | proto::MsgBurn { 20 | sender: sender.to_string(), 21 | amount: Some(proto::Coin { 22 | denom: denom.to_string(), 23 | amount: amount.to_string(), 24 | }), 25 | } 26 | } 27 | 28 | pub fn to_send_msg(recipient: &Addr, amount: Vec) -> BankMsg { 29 | BankMsg::Send { 30 | to_address: recipient.to_string(), 31 | amount, 32 | } 33 | } 34 | 35 | pub fn to_coin(amount: impl Into, denom: impl Into) -> Coin { 36 | coin(amount.into(), denom) 37 | } 38 | 39 | pub fn to_coin_u256(amount: Uint256, denom: impl Into) -> Result { 40 | Ok(to_coin(to_uint128(amount)?, denom)) 41 | } 42 | 43 | pub fn to_uint128(v: Uint256) -> Result { 44 | Ok(Uint128::from_str(&v.to_string())?) 45 | } 46 | 47 | pub fn to_set_metadata_msg(sender: &Addr, data: native::Metadata) -> proto::MsgSetDenomMetadata { 48 | proto::MsgSetDenomMetadata { 49 | sender: sender.to_string(), 50 | metadata: Some(proto::Metadata { 51 | description: data.description, 52 | denom_units: data 53 | .denom_units 54 | .into_iter() 55 | .map(|v| proto::DenomUnit { 56 | denom: v.denom, 57 | exponent: v.exponent, 58 | aliases: v.aliases, 59 | }) 60 | .collect(), 61 | base: data.base, 62 | display: data.display, 63 | name: data.name, 64 | symbol: data.symbol, 65 | }), 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/warp/native/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{RecoverPubkeyError, StdError}; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, PartialEq, Debug)] 5 | pub enum ContractError { 6 | #[error("{0}")] 7 | Std(#[from] StdError), 8 | 9 | #[error("{0}")] 10 | PaymentError(#[from] cw_utils::PaymentError), 11 | 12 | #[error("{0}")] 13 | RecoverPubkeyError(#[from] RecoverPubkeyError), 14 | 15 | #[error("{0}")] 16 | MigrationError(#[from] hpl_utils::MigrationError), 17 | 18 | #[error("unauthorized")] 19 | Unauthorized, 20 | 21 | #[error("wrong length")] 22 | WrongLength, 23 | 24 | #[error("invalid reply id")] 25 | InvalidReplyId, 26 | 27 | #[error("insufficient funds")] 28 | InsufficientFunds, 29 | 30 | #[error("no route for domain {domain:?}")] 31 | NoRouter { domain: u32 }, 32 | } 33 | -------------------------------------------------------------------------------- /contracts/warp/native/src/lib.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{Addr, Event}; 2 | use cw_storage_plus::Item; 3 | use hpl_interface::warp::TokenMode; 4 | 5 | pub mod contract; 6 | mod conv; 7 | pub mod error; 8 | mod proto; 9 | 10 | // reply message 11 | pub const REPLY_ID_CREATE_DENOM: u64 = 0; 12 | 13 | // version info for migration info 14 | pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); 15 | pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); 16 | 17 | // storage definition for token denomination 18 | const TOKEN_KEY: &str = "token"; 19 | const TOKEN: Item = Item::new(TOKEN_KEY); 20 | 21 | // storage definition for token mode 22 | const MODE_KEY: &str = "mode"; 23 | const MODE: Item = Item::new(MODE_KEY); 24 | 25 | // storage definition for token hrp 26 | const HRP_KEY: &str = "hrp"; 27 | const HRP: Item = Item::new(HRP_KEY); 28 | 29 | // storage definition for mailbox 30 | const MAILBOX_KEY: &str = "mailbox"; 31 | const MAILBOX: Item = Item::new(MAILBOX_KEY); 32 | 33 | fn new_event(name: &str) -> Event { 34 | Event::new(format!("hpl_warp_native::{name}")) 35 | } 36 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | relayer/ 2 | validator/ 3 | -------------------------------------------------------------------------------- /example/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | relayer: 4 | container_name: hpl-relayer 5 | # image: gcr.io/abacus-labs-dev/hyperlane-agent:9736164-20240307-131918 6 | image: gcr.io/abacus-labs-dev/hyperlane-agent:3bb4d87-20240129-164519 7 | user: root 8 | # restart: always 9 | entrypoint: ['sh', '-c'] 10 | command: 11 | - | 12 | rm -rf /app/config/* && \ 13 | cp "/etc/hyperlane/agent-config.docker.json" "/app/config/agent-config.json" && \ 14 | CONFIG_FILES="/etc/hyperlane/relayer.json" \ 15 | ./relayer 16 | ports: 17 | - 9110:9090 18 | volumes: 19 | - ./hyperlane:/etc/hyperlane 20 | - ./relayer:/etc/data 21 | - ./validator:/etc/validator 22 | 23 | validator-sepolia: 24 | container_name: hpl-validator-sepolia 25 | # image: gcr.io/abacus-labs-dev/hyperlane-agent:9736164-20240307-131918 26 | image: gcr.io/abacus-labs-dev/hyperlane-agent:3bb4d87-20240129-164519 27 | user: root 28 | # restart: always 29 | entrypoint: ['sh', '-c'] 30 | command: 31 | - | 32 | rm -rf /app/config/* && \ 33 | cp "/etc/hyperlane/agent-config.docker.json" "/app/config/agent-config.json" && \ 34 | CONFIG_FILES="/etc/hyperlane/validator.sepolia.json" \ 35 | ./validator 36 | ports: 37 | - 9120:9090 38 | volumes: 39 | - ./hyperlane:/etc/hyperlane 40 | - ./validator:/etc/validator 41 | - ./validator/sepolia:/etc/data 42 | 43 | validator-osmotest5: 44 | container_name: hpl-validator-osmotest5 45 | # image: gcr.io/abacus-labs-dev/hyperlane-agent:9736164-20240307-131918 46 | image: gcr.io/abacus-labs-dev/hyperlane-agent:3bb4d87-20240129-164519 47 | user: root 48 | # restart: always 49 | entrypoint: ['sh', '-c'] 50 | command: 51 | - | 52 | rm -rf /app/config/* && \ 53 | cp "/etc/hyperlane/agent-config.docker.json" "/app/config/agent-config.json" && \ 54 | CONFIG_FILES="/etc/hyperlane/validator.osmotest5.json" \ 55 | ./validator 56 | ports: 57 | - 9121:9090 58 | volumes: 59 | - ./hyperlane:/etc/hyperlane 60 | - ./validator:/etc/validator 61 | - ./validator/osmotest5:/etc/data 62 | -------------------------------------------------------------------------------- /example/hyperlane/agent-config.docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "chains": { 3 | "sepolia": { 4 | "blockExplorers": [ 5 | { 6 | "apiUrl": "https://api-sepolia.etherscan.io/api", 7 | "family": "etherscan", 8 | "name": "Etherscan", 9 | "url": "https://sepolia.etherscan.io" 10 | } 11 | ], 12 | "blocks": { 13 | "confirmations": 1, 14 | "estimateBlockTime": 13, 15 | "reorgPeriod": 2 16 | }, 17 | "chainId": 11155111, 18 | "displayName": "Sepolia", 19 | "domainId": 11155111, 20 | "isTestnet": true, 21 | "name": "sepolia", 22 | "nativeToken": { 23 | "decimals": 18, 24 | "name": "Ether", 25 | "symbol": "ETH" 26 | }, 27 | "protocol": "ethereum", 28 | "rpcUrls": [ 29 | { 30 | "http": "https://ethereum-sepolia.blockpi.network/v1/rpc/public" 31 | }, 32 | { 33 | "http": "https://eth-sepolia.g.alchemy.com/v2/demo" 34 | }, 35 | { 36 | "http": "https://rpc.sepolia.org" 37 | } 38 | ], 39 | "merkleRootMultisigIsmFactory": "0x0a71AcC99967829eE305a285750017C4916Ca269", 40 | "messageIdMultisigIsmFactory": "0xFEb9585b2f948c1eD74034205a7439261a9d27DD", 41 | "aggregationIsmFactory": "0xC83e12EF2627ACE445C298e6eC418684918a6002", 42 | "aggregationHookFactory": "0x160C28C92cA453570aD7C031972b58d5Dd128F72", 43 | "proxyAdmin": "0x97Bbc6bBaFa5Ce3b2FA966c121Af63bD09e940f8", 44 | "storageGasOracle": "0x71775B071F77F1ce52Ece810ce084451a3045FFe", 45 | "interchainGasPaymaster": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56", 46 | "aggregationHook": "0xe3147d5618f5e2e100690B50ec923009a4cde14A", 47 | "protocolFee": "0x13AC3349Cb159fE86A22cf42DdA803D9f7309DB5", 48 | "mailbox": "0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766", 49 | "merkleTreeHook": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d", 50 | "validatorAnnounce": "0xE6105C59480a1B7DD3E4f28153aFdbE12F4CfCD9", 51 | "fallbackRoutingHook": "0x17Dc724B7a2F09141C13b8AC33B396073785c2BC", 52 | "testRecipient": "0xeDc1A3EDf87187085A3ABb7A9a65E1e7aE370C07", 53 | "testTokenRecipient": "0x031AD9c560D37baC7d6Bd2d27A2443bAfd10101A", 54 | "routingIsmFactory": "0x3F100cBBE5FD5466BdB4B3a15Ac226957e7965Ad", 55 | "interchainSecurityModule": "0x958124472b14B7940Ed5317C44a2508791dB1d48", 56 | "pausableHook": "0xa68022e53Fd28119D07C8336a8eC84A298Fd38Fd", 57 | "index": { 58 | "from": 4517401, 59 | "chunk": 100000 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/hyperlane/relayer.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": "/etc/data/db", 3 | "relayChains": "osmotest5,sepolia", 4 | "allowLocalCheckpointSyncers": "true", 5 | "gasPaymentEnforcement": [{ "type": "none" }], 6 | "whitelist": [ 7 | { 8 | "origindomain": [11155111], 9 | "destinationDomain": [1304] 10 | }, 11 | { 12 | "origindomain": [1304], 13 | "destinationDomain": [11155111] 14 | } 15 | ], 16 | "chains": { 17 | "sepolia": { 18 | "signer": { 19 | "type": "hexKey", 20 | "key": "{sepolia_private_key}" 21 | } 22 | }, 23 | "osmotest5": { 24 | "signer": { 25 | "type": "cosmosKey", 26 | "key": "{osmosis_private_key}", 27 | "prefix": "osmo" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/hyperlane/validator.osmotest5.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": "/etc/data/db", 3 | "checkpointSyncer": { 4 | "type": "localStorage", 5 | "path": "/etc/validator/osmotest5/checkpoint" 6 | }, 7 | "originChainName": "osmotest5", 8 | "validator": { 9 | "type": "hexKey", 10 | "key": "{osmosis_private_key}" 11 | }, 12 | "chains": { 13 | "osmotest5": { 14 | "signer": { 15 | "type": "cosmosKey", 16 | "key": "{osmosis_private_key}", 17 | "prefix": "osmo" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/hyperlane/validator.sepolia.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": "/etc/data/db", 3 | "checkpointSyncer": { 4 | "type": "localStorage", 5 | "path": "/etc/validator/sepolia/checkpoint" 6 | }, 7 | "originChainName": "sepolia", 8 | "validator": { 9 | "signerType": "hexKey", 10 | "key": "{sepolia_private_key}", 11 | "type": "hexKey" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/index.ts: -------------------------------------------------------------------------------- 1 | import colors from 'colors'; 2 | import { Command } from 'commander'; 3 | import 'reflect-metadata'; 4 | 5 | import { version } from '../package.json'; 6 | import { injectDependencies } from './src/ioc'; 7 | import { recipientCmd } from './src/recipient'; 8 | import { warpCmd } from './src/warp'; 9 | 10 | colors.enable(); 11 | 12 | const cli = new Command(); 13 | 14 | cli 15 | .name('cw-hpl-example') 16 | .version(version) 17 | .configureHelp({ showGlobalOptions: true }) 18 | .option('--pk --private-key ', 'private key') 19 | .option('--mn --mnemonic ', 'mnemonic phrase') 20 | .option('--rpc --endpoint ', 'endpoint') 21 | .hook('preAction', injectDependencies) 22 | .description('CosmWasm Hyperlane Examples'); 23 | 24 | cli.addCommand(warpCmd); 25 | cli.addCommand(recipientCmd); 26 | 27 | cli.parseAsync(process.argv).catch(console.error); 28 | -------------------------------------------------------------------------------- /example/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const HYP_MAILBOX = '0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766'; 2 | export const HYP_MULTSIG_ISM_FACTORY = 3 | '0xFEb9585b2f948c1eD74034205a7439261a9d27DD'; 4 | -------------------------------------------------------------------------------- /example/src/ioc.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | import { Container } from 'inversify'; 3 | import { 4 | Account, 5 | Chain, 6 | Hex, 7 | PublicClient, 8 | Transport, 9 | WalletClient, 10 | createPublicClient, 11 | createWalletClient, 12 | http, 13 | } from 'viem'; 14 | import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts'; 15 | import { sepolia } from 'viem/chains'; 16 | 17 | export class Dependencies { 18 | account: Account; 19 | provider: { 20 | query: PublicClient; 21 | exec: WalletClient; 22 | }; 23 | } 24 | 25 | export const CONTAINER = new Container({ 26 | autoBindInjectable: true, 27 | defaultScope: 'Singleton', 28 | }); 29 | 30 | export async function injectDependencies(cmd: Command): Promise { 31 | const { privateKey, mnemonic, endpoint } = cmd.optsWithGlobals(); 32 | 33 | if (privateKey && mnemonic) { 34 | throw new Error('Cannot specify both private key and mnemonic'); 35 | } else if (!privateKey && !mnemonic) { 36 | throw new Error('Must specify either private key or mnemonic'); 37 | } 38 | 39 | const account = mnemonic 40 | ? mnemonicToAccount(mnemonic) 41 | : privateKeyToAccount(privateKey as Hex); 42 | 43 | const provider = { 44 | query: createPublicClient({ 45 | chain: sepolia, 46 | transport: http(endpoint), 47 | }), 48 | exec: createWalletClient({ 49 | chain: sepolia, 50 | account, 51 | transport: http(endpoint), 52 | }), 53 | }; 54 | 55 | CONTAINER.bind(Dependencies).toConstantValue({ account, provider }); 56 | } 57 | -------------------------------------------------------------------------------- /example/src/recipient.ts: -------------------------------------------------------------------------------- 1 | import { 2 | StaticMessageIdMultisigIsmFactory__factory, 3 | TestRecipient__factory, 4 | } from '@hyperlane-xyz/core'; 5 | import { Command } from 'commander'; 6 | 7 | import { HYP_MULTSIG_ISM_FACTORY } from './constants'; 8 | import { CONTAINER, Dependencies } from './ioc'; 9 | import { expectNextContractAddr, logTx } from './utils'; 10 | 11 | export const recipientCmd = new Command('deploy-test-recipient').action( 12 | deployTestRecipient, 13 | ); 14 | 15 | async function deployTestRecipient() { 16 | const { 17 | account, 18 | provider: { query, exec }, 19 | } = CONTAINER.get(Dependencies); 20 | 21 | const testRecipientAddr = await expectNextContractAddr(query, account); 22 | console.log(`Deploying TestRecipient at "${testRecipientAddr.green}"...`); 23 | 24 | // deploy test recipient 25 | { 26 | const tx = await exec.deployContract({ 27 | abi: TestRecipient__factory.abi, 28 | bytecode: TestRecipient__factory.bytecode, 29 | args: [], 30 | }); 31 | logTx('Deploy test recipient', tx); 32 | await query.waitForTransactionReceipt({ hash: tx }); 33 | } 34 | 35 | // deploy multisig ism 36 | const multisigIsmAddr = await query.readContract({ 37 | abi: StaticMessageIdMultisigIsmFactory__factory.abi, 38 | address: HYP_MULTSIG_ISM_FACTORY, 39 | functionName: 'getAddress', 40 | args: [[account.address], 1], 41 | }); 42 | console.log(`Deploying multisigIsm at "${multisigIsmAddr.green}"...`); 43 | 44 | { 45 | const tx = await exec.writeContract({ 46 | abi: StaticMessageIdMultisigIsmFactory__factory.abi, 47 | address: HYP_MULTSIG_ISM_FACTORY, 48 | functionName: 'deploy', 49 | args: [[account.address], 1], 50 | }); 51 | logTx('Deploy multisig ism', tx); 52 | await query.waitForTransactionReceipt({ hash: tx }); 53 | } 54 | 55 | // set ism of test recipient 56 | 57 | console.log(`Setting ism of test recipient to "${multisigIsmAddr.green}"...`); 58 | { 59 | const tx = await exec.writeContract({ 60 | abi: TestRecipient__factory.abi, 61 | address: testRecipientAddr, 62 | functionName: 'setInterchainSecurityModule', 63 | args: [multisigIsmAddr], 64 | }); 65 | logTx('Set multisig ism to test recipient', tx); 66 | await query.waitForTransactionReceipt({ hash: tx }); 67 | } 68 | 69 | console.log('== Done! =='); 70 | 71 | console.log({ 72 | testRecipient: testRecipientAddr, 73 | multisigIsm: multisigIsmAddr, 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /example/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { fromBech32 } from '@cosmjs/encoding'; 2 | import { 3 | Account, 4 | Address, 5 | Chain, 6 | PublicClient, 7 | Transport, 8 | getContractAddress, 9 | } from 'viem'; 10 | 11 | export const addPad = (v: string): string => { 12 | const s = v.startsWith('0x') ? v.slice(2) : v; 13 | return s.padStart(64, '0'); 14 | }; 15 | 16 | export const logTx = (title: string, tx: string) => 17 | console.log( 18 | '=>'.grey, 19 | title + '\n', 20 | '=>'.green, 21 | `tx hash: "${tx.green}". Waiting for confirmation...`, 22 | ); 23 | 24 | export const extractByte32AddrFromBech32 = (addr: string): string => { 25 | const { data } = fromBech32(addr); 26 | const hexed = Buffer.from(data).toString('hex'); 27 | return hexed.length === 64 ? hexed : addPad(hexed); 28 | }; 29 | 30 | export const expectNextContractAddr = async ( 31 | query: PublicClient, 32 | account: Account, 33 | ): Promise
=> { 34 | const nonce = await query.getTransactionCount(account); 35 | 36 | const next = getContractAddress({ 37 | opcode: 'CREATE', 38 | from: account.address, 39 | nonce: BigInt(nonce), 40 | }); 41 | 42 | return next; 43 | }; 44 | -------------------------------------------------------------------------------- /example/src/warp.ts: -------------------------------------------------------------------------------- 1 | import { HypERC20__factory } from '@hyperlane-xyz/core'; 2 | import { Command } from 'commander'; 3 | import { isAddress } from 'viem'; 4 | 5 | import { HYP_MAILBOX } from './constants'; 6 | import { CONTAINER, Dependencies } from './ioc'; 7 | import { 8 | expectNextContractAddr, 9 | extractByte32AddrFromBech32, 10 | logTx, 11 | } from './utils'; 12 | 13 | const warpCmd = new Command('warp'); 14 | 15 | warpCmd.command('deploy').action(deployWarpRoute); 16 | warpCmd 17 | .command('link') 18 | .argument('', 'address of warp route') 19 | .argument('', 'destination domain to set') 20 | .argument('', 'destination address to set') 21 | .action(linkWarpRoute); 22 | 23 | warpCmd 24 | .command('transfer') 25 | .argument('', 'address of warp route') 26 | .argument('', 'destination domain to transfer') 27 | .argument('', 'address to transfer') 28 | .action(transferWarpRoute); 29 | 30 | export { warpCmd }; 31 | 32 | async function deployWarpRoute() { 33 | const { 34 | account, 35 | provider: { query, exec }, 36 | } = CONTAINER.get(Dependencies); 37 | 38 | // deploy hyp erc20 (implementation) 39 | 40 | const hypErc20OsmoAddr = await expectNextContractAddr(query, account); 41 | console.log(`Deploying HypERC20 at "${hypErc20OsmoAddr.green}"...`); 42 | 43 | { 44 | const tx = await exec.deployContract({ 45 | abi: HypERC20__factory.abi, 46 | bytecode: HypERC20__factory.bytecode, 47 | args: [6, HYP_MAILBOX], 48 | }); 49 | logTx('Deploying HypERC20Osmo', tx); 50 | await query.waitForTransactionReceipt({ hash: tx }); 51 | } 52 | 53 | { 54 | const tx = await exec.writeContract({ 55 | abi: HypERC20__factory.abi, 56 | address: hypErc20OsmoAddr, 57 | functionName: 'initialize', 58 | args: [0n, 'Hyperlane Bridged Osmosis', 'OSMO'], 59 | }); 60 | logTx('Initialize HypERC20Osmo', tx); 61 | await query.waitForTransactionReceipt({ hash: tx }); 62 | } 63 | 64 | console.log('== Done! =='); 65 | 66 | console.log({ 67 | hypErc20Osmo: hypErc20OsmoAddr, 68 | }); 69 | } 70 | 71 | async function linkWarpRoute(warp: string, domain: string, route: string) { 72 | const { 73 | provider: { exec, query }, 74 | } = CONTAINER.get(Dependencies); 75 | 76 | if (!isAddress(warp)) throw new Error('Invalid warp address'); 77 | 78 | const tx = await exec.writeContract({ 79 | abi: HypERC20__factory.abi, 80 | address: warp, 81 | functionName: 'enrollRemoteRouter', 82 | args: [parseInt(domain), `0x${extractByte32AddrFromBech32(route)}`], 83 | }); 84 | logTx(`Linking warp route with external chain ${domain}`, tx); 85 | await query.waitForTransactionReceipt({ hash: tx }); 86 | } 87 | 88 | async function transferWarpRoute(warp: string, domain: string, to: string) { 89 | const { 90 | provider: { exec, query }, 91 | } = CONTAINER.get(Dependencies); 92 | 93 | if (!isAddress(warp)) throw new Error('Invalid warp address'); 94 | 95 | const tx = await exec.writeContract({ 96 | abi: HypERC20__factory.abi, 97 | address: warp, 98 | functionName: 'transferRemote', 99 | args: [ 100 | parseInt(domain), 101 | `0x${extractByte32AddrFromBech32(to)}`, 102 | 1_000_000n, 103 | ], 104 | }); 105 | logTx(`Transferring warp route with external chain ${domain}`, tx); 106 | await query.waitForTransactionReceipt({ hash: tx }); 107 | } 108 | -------------------------------------------------------------------------------- /example/warp/umilktia.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "native", 3 | "mode": "collateral", 4 | 5 | "id": "umilkTIA", 6 | "owner": "", 7 | "config": { 8 | "collateral": { 9 | "denom": "factory/osmo1f5vfcph2dvfeqcqkhetwv75fda69z7e5c2dldm3kvgj23crkv6wqcn47a0/umilkTIA" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/warp/uosmo.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "native", 3 | "mode": "collateral", 4 | 5 | "id": "uosmo", 6 | "owner": "", 7 | "config": { 8 | "collateral": { 9 | "denom": "uosmo" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/warp/utia.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "native", 3 | "mode": "collateral", 4 | 5 | "id": "utia", 6 | "owner": "", 7 | "config": { 8 | "collateral": { 9 | "denom": "ibc/D79E7D83AB399BFFF93433E54FAA480C191248FC556924A2A8351AE2638B3877" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /integration-test/.gitignore: -------------------------------------------------------------------------------- 1 | tests/contracts/eth/bind/*.rs 2 | !tests/contracts/eth/bind/mod.rs 3 | -------------------------------------------------------------------------------- /integration-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-tests" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | 24 | [dev_dependencies] 25 | ethers.workspace = true 26 | test-tube.workspace = true 27 | osmosis-test-tube.workspace = true 28 | serde_json.workspace = true 29 | serde-json-wasm.workspace = true 30 | cosmwasm-schema.workspace = true 31 | base64.workspace = true 32 | bech32.workspace = true 33 | tokio.workspace = true 34 | eyre.workspace = true 35 | k256.workspace = true 36 | sha2.workspace = true 37 | sha3.workspace = true 38 | ripemd.workspace = true 39 | hex-literal.workspace = true 40 | ibcx-test-utils.workspace = true 41 | 42 | rstest.workspace = true 43 | cw20.workspace = true 44 | 45 | hpl-ownable.workspace = true 46 | hpl-ism-multisig.workspace = true 47 | hpl-interface.workspace = true 48 | 49 | [build-dependencies] 50 | ethers.workspace = true 51 | -------------------------------------------------------------------------------- /integration-test/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env::current_dir, path::PathBuf}; 2 | 3 | use ethers::prelude::Abigen; 4 | 5 | fn generate_bind(name: &str, abi_file: &str, bind_out: PathBuf) { 6 | if bind_out.exists() { 7 | std::fs::remove_file(&bind_out).unwrap(); 8 | } 9 | 10 | Abigen::new(name, abi_file) 11 | .unwrap() 12 | .generate() 13 | .unwrap() 14 | .write_to_file(bind_out) 15 | .unwrap(); 16 | } 17 | 18 | fn main() { 19 | let abi_base = current_dir().unwrap().join("abis"); 20 | let bind_base = current_dir() 21 | .unwrap() 22 | .join("tests") 23 | .join("contracts/eth/bind"); 24 | let deployments = [ 25 | ("Mailbox", "mailbox"), 26 | ("FastHypERC20", "fast_hyp_erc20"), 27 | ("FastHypERC20Collateral", "fast_hyp_erc20_collateral"), 28 | ("TestMultisigIsm", "test_mock_ism"), 29 | ("TestRecipient", "test_mock_msg_receiver"), 30 | ]; 31 | 32 | for (abi_file, bind_out) in deployments { 33 | generate_bind( 34 | abi_file, 35 | abi_base.join(format!("{abi_file}.json")).to_str().unwrap(), 36 | bind_base.join(format!("{bind_out}.rs")), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /integration-test/cw/cw20_base.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zikc2023/cw-hyperlane/00f64a9e8df5b384c2648d3f0f38baa549f204b3/integration-test/cw/cw20_base.wasm -------------------------------------------------------------------------------- /integration-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /integration-test/tests/constants.rs: -------------------------------------------------------------------------------- 1 | pub const DOMAIN_EVM: u32 = 1; 2 | 3 | pub const DOMAIN_OSMO: u32 = 2; 4 | pub const PREFIX_OSMO: &str = "osmo"; 5 | 6 | pub const DOMAIN_NTRN: u32 = 3; 7 | pub const PREFIX_NTRN: &str = "neutron"; 8 | -------------------------------------------------------------------------------- /integration-test/tests/contracts/cw/igp.rs: -------------------------------------------------------------------------------- 1 | use hpl_interface::{ 2 | igp::{self, oracle::RemoteGasDataConfig}, 3 | router::{DomainRouteSet, RouterMsg}, 4 | }; 5 | use ibcx_test_utils::addr; 6 | use test_tube::{Account, Runner, SigningAccount, Wasm}; 7 | 8 | use super::types::Codes; 9 | 10 | #[derive(Clone)] 11 | pub struct Igp { 12 | pub hrp: String, 13 | pub gas_token: String, 14 | pub beneficiary: String, 15 | pub oracle_configs: Vec, 16 | } 17 | 18 | pub struct IgpDeployment { 19 | pub core: String, 20 | pub oracle: String, 21 | } 22 | 23 | impl Igp { 24 | pub fn deploy<'a, R: Runner<'a>>( 25 | self, 26 | wasm: &Wasm<'a, R>, 27 | codes: &Codes, 28 | owner: &SigningAccount, 29 | deployer: &SigningAccount, 30 | ) -> eyre::Result { 31 | let igp = wasm 32 | .instantiate( 33 | codes.igp, 34 | &igp::core::InstantiateMsg { 35 | hrp: self.hrp, 36 | owner: owner.address(), 37 | gas_token: self.gas_token, 38 | beneficiary: self.beneficiary, 39 | default_gas_usage: 25_000, 40 | }, 41 | Some(deployer.address().as_str()), 42 | Some("cw-hpl-igp"), 43 | &[], 44 | deployer, 45 | )? 46 | .data 47 | .address; 48 | 49 | let igp_oracle = wasm 50 | .instantiate( 51 | codes.igp_oracle, 52 | &igp::oracle::InstantiateMsg { 53 | owner: owner.address(), 54 | }, 55 | Some(deployer.address().as_str()), 56 | Some("cw-hpl-igp-oracle"), 57 | &[], 58 | deployer, 59 | )? 60 | .data 61 | .address; 62 | 63 | wasm.execute( 64 | &igp, 65 | &igp::core::ExecuteMsg::Router(RouterMsg::SetRoutes { 66 | set: self 67 | .oracle_configs 68 | .iter() 69 | .map(|v| DomainRouteSet { 70 | domain: v.remote_domain, 71 | route: Some(addr(&igp_oracle)), 72 | }) 73 | .collect(), 74 | }), 75 | &[], 76 | owner, 77 | )?; 78 | 79 | if !self.oracle_configs.is_empty() { 80 | wasm.execute( 81 | &igp_oracle, 82 | &igp::oracle::ExecuteMsg::SetRemoteGasDataConfigs { 83 | configs: self.oracle_configs, 84 | }, 85 | &[], 86 | owner, 87 | )?; 88 | } 89 | 90 | Ok(IgpDeployment { 91 | core: igp, 92 | oracle: igp_oracle, 93 | }) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /integration-test/tests/contracts/cw/mod.rs: -------------------------------------------------------------------------------- 1 | mod deploy; 2 | mod hook; 3 | mod igp; 4 | mod ism; 5 | mod setup; 6 | mod store; 7 | mod types; 8 | 9 | pub use deploy::*; 10 | pub use hook::Hook; 11 | pub use ism::{prepare_routing_ism, Ism}; 12 | pub use setup::{setup_env, Env}; 13 | pub use store::store_code; 14 | -------------------------------------------------------------------------------- /integration-test/tests/contracts/cw/setup.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, path::PathBuf}; 2 | 3 | use cosmwasm_std::{coin, Coin, Uint256}; 4 | use hpl_interface::igp::oracle::RemoteGasDataConfig; 5 | use test_tube::{Account, Module, Runner, SigningAccount, Wasm}; 6 | 7 | use crate::validator::TestValidators; 8 | 9 | use super::{ 10 | deploy_core, 11 | igp::Igp, 12 | prepare_routing_ism, store_code, 13 | types::{Codes, CoreDeployments}, 14 | Hook, 15 | }; 16 | 17 | const DEFAULT_GAS: u128 = 300_000; 18 | 19 | pub struct Env<'a, R: Runner<'a>> { 20 | validators: BTreeMap, 21 | 22 | pub app: &'a R, 23 | pub core: CoreDeployments, 24 | pub codes: Codes, 25 | pub domain: u32, 26 | 27 | acc_gen: Box SigningAccount>, 28 | pub acc_owner: SigningAccount, 29 | pub acc_tester: SigningAccount, 30 | pub acc_deployer: SigningAccount, 31 | } 32 | 33 | impl<'a, R: Runner<'a>> Env<'a, R> { 34 | pub fn get_validator_set(&self, domain: u32) -> eyre::Result<&TestValidators> { 35 | self.validators 36 | .get(&domain) 37 | .ok_or(eyre::eyre!("no validator set found")) 38 | } 39 | 40 | #[allow(dead_code)] 41 | pub fn gen_account(&'a self, coins: &'a [Coin]) -> SigningAccount { 42 | (self.acc_gen)(self.app, coins) 43 | } 44 | } 45 | 46 | pub fn setup_env<'a, R: Runner<'a>>( 47 | app: &'a R, 48 | acc_gen: impl Fn(&R, &[Coin]) -> SigningAccount + 'static, 49 | artifacts: Option>, 50 | hrp: &str, 51 | domain: u32, 52 | validators: &[TestValidators], 53 | oracle_config: &[RemoteGasDataConfig], 54 | ) -> eyre::Result> { 55 | let owner = acc_gen(app, &[coin(1_000_000u128.pow(3), "uosmo")]); 56 | let deployer = acc_gen(app, &[coin(1_000_000u128.pow(3), "uosmo")]); 57 | let tester = acc_gen(app, &[coin(1_000_000u128.pow(3), "uosmo")]); 58 | 59 | let default_ism = 60 | prepare_routing_ism(validators.iter().map(|v| (v.domain, v.clone())).collect()); 61 | 62 | let default_hook = Hook::mock(Uint256::from_u128(DEFAULT_GAS)); 63 | 64 | let required_hook = Hook::Aggregate { 65 | hooks: vec![ 66 | Hook::Merkle {}, 67 | Hook::Igp(Igp { 68 | hrp: hrp.to_string(), 69 | gas_token: "uosmo".to_string(), 70 | beneficiary: deployer.address(), 71 | oracle_configs: oracle_config.to_vec(), 72 | }), 73 | ], 74 | }; 75 | 76 | let wasm = Wasm::new(app); 77 | let codes = store_code(&wasm, &deployer, artifacts)?; 78 | let core = deploy_core( 79 | &wasm, 80 | &owner, 81 | &deployer, 82 | &codes, 83 | domain, 84 | hrp, 85 | default_ism, 86 | default_hook, 87 | required_hook, 88 | )?; 89 | 90 | Ok(Env { 91 | validators: validators.iter().map(|v| (v.domain, v.clone())).collect(), 92 | 93 | app, 94 | core, 95 | codes, 96 | domain, 97 | 98 | acc_gen: Box::new(acc_gen), 99 | acc_owner: owner, 100 | acc_tester: tester, 101 | acc_deployer: deployer, 102 | }) 103 | } 104 | -------------------------------------------------------------------------------- /integration-test/tests/contracts/cw/store.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use osmosis_test_tube::Wasm; 4 | use test_tube::{Runner, SigningAccount}; 5 | 6 | use super::types::{Codes, CodesMap}; 7 | 8 | const DEFAULT_ARTIFACTS_PATH: &str = "../target/wasm32-unknown-unknown/release/"; 9 | 10 | const CONTRACTS: [&str; 18] = [ 11 | "mailbox", 12 | "validator_announce", 13 | "hook_aggregate", 14 | "hook_merkle", 15 | "hook_pausable", 16 | "hook_routing", 17 | "hook_routing_custom", 18 | "hook_routing_fallback", 19 | "igp", 20 | "igp_oracle", 21 | "ism_aggregate", 22 | "ism_multisig", 23 | "ism_routing", 24 | "test_mock_hook", 25 | "test_mock_ism", 26 | "test_mock_msg_receiver", 27 | "warp_cw20", 28 | "warp_native", 29 | ]; 30 | 31 | pub fn store_code<'a, R: Runner<'a>>( 32 | wasm: &Wasm<'a, R>, 33 | deployer: &SigningAccount, 34 | artifacts: Option>, 35 | ) -> eyre::Result { 36 | let base_path: PathBuf = artifacts 37 | .map(|v| v.into()) 38 | .unwrap_or(DEFAULT_ARTIFACTS_PATH.into()); 39 | 40 | let mut artifacts = CONTRACTS 41 | .into_iter() 42 | .map(|name| { 43 | let filename = format!("hpl_{name}.wasm"); 44 | let path = base_path.join(filename); 45 | let code = std::fs::read(path)?; 46 | let store_resp = wasm.store_code(&code, None, deployer)?; 47 | let code_id = store_resp.data.code_id; 48 | 49 | Ok((name.to_string(), code_id)) 50 | }) 51 | .collect::>()?; 52 | 53 | // precompiles 54 | { 55 | let name = "cw20_base"; 56 | let code = std::fs::read(format!("./cw/{name}.wasm"))?; 57 | let store_resp = wasm.store_code(&code, None, deployer)?; 58 | 59 | artifacts 60 | .0 61 | .insert(name.to_string(), store_resp.data.code_id); 62 | } 63 | 64 | artifacts.try_into() 65 | } 66 | -------------------------------------------------------------------------------- /integration-test/tests/contracts/cw/types.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use cosmwasm_schema::cw_serde; 4 | 5 | #[cw_serde] 6 | pub struct CodesMap(pub BTreeMap); 7 | 8 | impl FromIterator<(String, u64)> for CodesMap { 9 | fn from_iter>(iter: T) -> Self { 10 | Self(iter.into_iter().collect()) 11 | } 12 | } 13 | 14 | #[cw_serde] 15 | pub struct Codes { 16 | pub mailbox: u64, 17 | #[serde(rename = "validator_announce")] 18 | pub va: u64, 19 | 20 | pub hook_aggregate: u64, 21 | pub hook_merkle: u64, 22 | pub hook_pausable: u64, 23 | pub hook_routing: u64, 24 | pub hook_routing_custom: u64, 25 | pub hook_routing_fallback: u64, 26 | 27 | pub igp: u64, 28 | pub igp_oracle: u64, 29 | 30 | pub ism_aggregate: u64, 31 | pub ism_multisig: u64, 32 | pub ism_routing: u64, 33 | 34 | pub test_mock_hook: u64, 35 | pub test_mock_ism: u64, 36 | pub test_mock_msg_receiver: u64, 37 | 38 | pub warp_cw20: u64, 39 | pub warp_native: u64, 40 | 41 | pub cw20_base: u64, 42 | } 43 | 44 | impl TryFrom for Codes { 45 | type Error = eyre::Error; 46 | 47 | fn try_from(v: CodesMap) -> Result { 48 | let bin = serde_json::to_vec(&v)?; 49 | 50 | let ret = serde_json::from_slice(&bin)?; 51 | 52 | Ok(ret) 53 | } 54 | } 55 | 56 | #[cw_serde] 57 | pub struct CoreDeployments { 58 | pub mailbox: String, 59 | pub default_ism: String, 60 | pub default_hook: String, 61 | pub required_hook: String, 62 | pub msg_receiver: String, 63 | } 64 | 65 | #[cw_serde] 66 | pub struct WarpDeployments(pub BTreeMap); 67 | -------------------------------------------------------------------------------- /integration-test/tests/contracts/eth/bind/mod.rs: -------------------------------------------------------------------------------- 1 | #[allow(clippy::all)] 2 | pub mod fast_hyp_erc20; 3 | #[allow(clippy::all)] 4 | pub mod fast_hyp_erc20_collateral; 5 | #[allow(clippy::all)] 6 | pub mod mailbox; 7 | #[allow(clippy::all)] 8 | pub mod test_mock_ism; 9 | #[allow(clippy::all)] 10 | pub mod test_mock_msg_receiver; 11 | -------------------------------------------------------------------------------- /integration-test/tests/contracts/eth/deploy.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ethers::{prelude::SignerMiddleware, providers::Middleware, signers::Signer}; 4 | 5 | use super::{mailbox, test_mock_ism, test_mock_msg_receiver, types}; 6 | 7 | pub async fn deploy<'a, M: Middleware + 'static, S: Signer + 'static>( 8 | signer: Arc>, 9 | evm_domain: u32, 10 | ) -> eyre::Result> { 11 | let ism_multisig_contract = test_mock_ism::TestMultisigIsm::deploy(signer.clone(), ())? 12 | .send() 13 | .await?; 14 | 15 | let msg_receiver_contract = test_mock_msg_receiver::TestRecipient::deploy(signer.clone(), ())? 16 | .send() 17 | .await?; 18 | 19 | let mailbox_contract = mailbox::Mailbox::deploy(signer.clone(), evm_domain)? 20 | .send() 21 | .await?; 22 | 23 | let _ = mailbox_contract 24 | .initialize(signer.address(), ism_multisig_contract.address()) 25 | .send() 26 | .await? 27 | .await?; 28 | 29 | let deployments = types::Deployments { 30 | mailbox: mailbox_contract, 31 | ism: ism_multisig_contract, 32 | msg_receiver: msg_receiver_contract, 33 | }; 34 | 35 | Ok(deployments) 36 | } 37 | -------------------------------------------------------------------------------- /integration-test/tests/contracts/eth/mod.rs: -------------------------------------------------------------------------------- 1 | mod bind; 2 | mod deploy; 3 | mod setup; 4 | mod types; 5 | 6 | pub use bind::*; 7 | pub use deploy::deploy; 8 | pub use setup::{setup_env, Env}; 9 | -------------------------------------------------------------------------------- /integration-test/tests/contracts/eth/setup.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::Duration}; 2 | 3 | use ethers::{ 4 | prelude::{k256::ecdsa::SigningKey, SignerMiddleware}, 5 | providers::{Http, Middleware, Provider}, 6 | signers::{LocalWallet, Signer, Wallet}, 7 | utils::{Anvil, AnvilInstance}, 8 | }; 9 | 10 | use super::{deploy, types::Deployments}; 11 | 12 | pub struct Env { 13 | pub app: AnvilInstance, 14 | pub core: Deployments, 15 | pub domain: u32, 16 | 17 | pub acc_owner: S, 18 | } 19 | 20 | pub async fn setup_env(domain: u32) -> eyre::Result, Wallet>> { 21 | let app = Anvil::new().spawn(); 22 | 23 | let wallet = LocalWallet::from(app.keys()[0].clone()); 24 | let wallet = wallet.with_chain_id(app.chain_id()); 25 | 26 | let provider = 27 | Provider::::try_from(&app.endpoint())?.interval(Duration::from_millis(10u64)); 28 | let signer = Arc::new(SignerMiddleware::new(provider, wallet.clone())); 29 | 30 | let core = deploy(signer.clone(), domain).await?; 31 | 32 | Ok(Env { 33 | app, 34 | core, 35 | domain, 36 | 37 | acc_owner: wallet, 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /integration-test/tests/contracts/eth/types.rs: -------------------------------------------------------------------------------- 1 | use ethers::{prelude::SignerMiddleware, providers::Middleware, signers::Signer}; 2 | 3 | use super::{ 4 | mailbox::Mailbox, test_mock_ism::TestMultisigIsm, test_mock_msg_receiver::TestRecipient, 5 | }; 6 | 7 | pub struct Deployments { 8 | pub mailbox: Mailbox>, 9 | 10 | pub ism: TestMultisigIsm>, 11 | pub msg_receiver: TestRecipient>, 12 | } 13 | -------------------------------------------------------------------------------- /integration-test/tests/contracts/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cw; 2 | pub mod eth; 3 | -------------------------------------------------------------------------------- /integration-test/tests/event.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | use cosmwasm_std::{Event, HexBinary}; 3 | 4 | #[cw_serde] 5 | pub struct CwDispatchEvent { 6 | pub sender: HexBinary, 7 | pub destination: u32, 8 | pub recipient: HexBinary, 9 | pub message: HexBinary, 10 | } 11 | 12 | pub fn parse_dispatch_from_res(events: &[Event]) -> CwDispatchEvent { 13 | let found = events 14 | .iter() 15 | .find(|v| v.ty == "wasm-mailbox_dispatch") 16 | .unwrap(); 17 | 18 | let actual = &found.attributes[1..]; 19 | 20 | CwDispatchEvent { 21 | sender: HexBinary::from_hex(&actual[0].value).unwrap(), 22 | destination: actual[1].value.parse::().unwrap(), 23 | recipient: HexBinary::from_hex(&actual[2].value).unwrap(), 24 | message: HexBinary::from_hex(&actual[3].value).unwrap(), 25 | } 26 | } 27 | 28 | #[cw_serde] 29 | pub struct CwDispatchId { 30 | pub id: HexBinary, 31 | } 32 | 33 | pub fn parse_dispatch_id_from_res(events: &[Event]) -> CwDispatchId { 34 | let found = events 35 | .iter() 36 | .find(|v| v.ty == "wasm-mailbox_dispatch_id") 37 | .unwrap(); 38 | 39 | let actual = &found.attributes[1..]; 40 | 41 | CwDispatchId { 42 | id: HexBinary::from_hex(&actual[0].value).unwrap(), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@many-things/cw-hyperlane", 3 | "packageManager": "yarn@4.1.0", 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "clean": "rm -rf dist", 9 | "lint": "eslint . --ext ts", 10 | "prettier": "prettier ./script ./example ./codegen.ts --write", 11 | "build": "yarn clean && esbuild ./script --bundle --outfile=./dist/index.js --platform=node", 12 | "start": "yarn build && node ./dist/index.js", 13 | "cw-hpl": "yarn start", 14 | "cw-hpl-exp": "tsx ./example", 15 | "dev": "tsx ./script" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "ISC", 20 | "dependencies": { 21 | "@cosmjs/cosmwasm-stargate": "^0.32.2", 22 | "@cosmjs/crypto": "^0.32.2", 23 | "@cosmjs/encoding": "^0.32.2", 24 | "@cosmjs/proto-signing": "^0.32.2", 25 | "@cosmjs/stargate": "^0.32.2", 26 | "@cosmjs/tendermint-rpc": "^0.32.2", 27 | "@hyperlane-xyz/sdk": "^3.7.0", 28 | "@hyperlane-xyz/utils": "^3.7.0", 29 | "axios": "^1.6.7", 30 | "colors": "^1.4.0", 31 | "commander": "^11.1.0", 32 | "decompress": "^4.2.1", 33 | "inversify": "^6.0.1", 34 | "readline": "^1.3.0", 35 | "reflect-metadata": "^0.1.13" 36 | }, 37 | "devDependencies": { 38 | "@cosmwasm/ts-codegen": "^0.35.7", 39 | "@trivago/prettier-plugin-sort-imports": "^4.3.0", 40 | "@types/decompress": "^4.2.7", 41 | "@types/eslint": "^8", 42 | "@types/js-yaml": "^4.0.8", 43 | "@types/node": "^20.11.20", 44 | "@typescript-eslint/eslint-plugin": "^7.1.1", 45 | "@typescript-eslint/parser": "^7.1.1", 46 | "esbuild": "^0.20.1", 47 | "eslint": "^8.57.0", 48 | "eslint-config-prettier": "^9.1.0", 49 | "eslint-plugin-prettier": "^5.1.3", 50 | "prettier": "^3.2.5", 51 | "ts-node": "^10.9.1", 52 | "ts-yaml": "^1.0.0", 53 | "tsx": "^3.13.0", 54 | "typescript": "^5.1.6" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/connection/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-connection" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cw-storage-plus.workspace = true 25 | cw2.workspace = true 26 | bech32.workspace = true 27 | sha3.workspace = true 28 | schemars.workspace = true 29 | serde.workspace = true 30 | serde-json-wasm.workspace = true 31 | thiserror.workspace = true 32 | cosmwasm-schema.workspace = true 33 | 34 | hpl-ownable.workspace = true 35 | hpl-interface.workspace = true 36 | 37 | [dev-dependencies] 38 | anyhow.workspace = true 39 | -------------------------------------------------------------------------------- /packages/connection/src/lib.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{ 2 | ensure_eq, to_json_binary, Addr, CustomQuery, Deps, DepsMut, Env, Event, MessageInfo, 3 | QueryResponse, Response, StdError, StdResult, Storage, 4 | }; 5 | use cw_storage_plus::Item; 6 | use hpl_interface::connection::{ 7 | ConnectionMsg, ConnectionQueryMsg, HookResponse, IsmResponse, MailboxResponse, 8 | }; 9 | 10 | const MAILBOX_KEY: &str = "conn::mailbox"; 11 | const MAILBOX: Item = Item::new(MAILBOX_KEY); 12 | 13 | const ISM_KEY: &str = "conn::ism"; 14 | const ISM: Item = Item::new(ISM_KEY); 15 | 16 | const HOOK_KEY: &str = "conn::hook"; 17 | const HOOK: Item = Item::new(HOOK_KEY); 18 | 19 | fn event_to_resp(event: Event) -> Response { 20 | Response::new().add_event(event) 21 | } 22 | 23 | fn new_event(name: &str) -> Event { 24 | Event::new(format!("hpl_connection::{}", name)) 25 | } 26 | 27 | pub fn handle( 28 | deps: DepsMut<'_, C>, 29 | _env: Env, 30 | info: MessageInfo, 31 | msg: ConnectionMsg, 32 | ) -> StdResult { 33 | use ConnectionMsg::*; 34 | 35 | ensure_eq!( 36 | hpl_ownable::get_owner(deps.storage)?, 37 | info.sender, 38 | StdError::generic_err("unauthorized") 39 | ); 40 | 41 | match msg { 42 | SetMailbox { mailbox } => { 43 | let mailbox_addr = deps.api.addr_validate(&mailbox)?; 44 | 45 | MAILBOX.save(deps.storage, &mailbox_addr)?; 46 | 47 | Ok(event_to_resp( 48 | new_event("set_mailbox").add_attribute("mailbox", mailbox), 49 | )) 50 | } 51 | SetIsm { ism } => { 52 | let ism_addr = deps.api.addr_validate(&ism)?; 53 | 54 | ISM.save(deps.storage, &ism_addr)?; 55 | 56 | Ok(event_to_resp( 57 | new_event("set_ism").add_attribute("ism", ism), 58 | )) 59 | } 60 | SetHook { hook } => { 61 | let hook_addr = deps.api.addr_validate(&hook)?; 62 | 63 | HOOK.save(deps.storage, &hook_addr)?; 64 | 65 | Ok(event_to_resp( 66 | new_event("set_hook").add_attribute("hook", hook), 67 | )) 68 | } 69 | } 70 | } 71 | 72 | pub fn handle_query( 73 | deps: Deps<'_, C>, 74 | _env: Env, 75 | msg: ConnectionQueryMsg, 76 | ) -> StdResult { 77 | match msg { 78 | ConnectionQueryMsg::GetMailbox {} => Ok(to_json_binary(&MailboxResponse { 79 | mailbox: get_mailbox(deps.storage)?.map(|v| v.into()), 80 | })?), 81 | ConnectionQueryMsg::GetHook {} => Ok(to_json_binary(&HookResponse { 82 | hook: get_hook(deps.storage)?.map(|v| v.into()), 83 | })?), 84 | ConnectionQueryMsg::GetIsm {} => Ok(to_json_binary(&IsmResponse { 85 | ism: get_ism(deps.storage)?.map(|v| v.into()), 86 | })?), 87 | } 88 | } 89 | 90 | pub fn get_mailbox(storage: &dyn Storage) -> StdResult> { 91 | MAILBOX.may_load(storage) 92 | } 93 | 94 | pub fn get_ism(storage: &dyn Storage) -> StdResult> { 95 | ISM.may_load(storage) 96 | } 97 | 98 | pub fn get_hook(storage: &dyn Storage) -> StdResult> { 99 | HOOK.may_load(storage) 100 | } 101 | -------------------------------------------------------------------------------- /packages/interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-interface" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | description = "Interface definitions for CosmWasm Hyperlane contracts" 12 | 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | 16 | [features] 17 | # for more explicit tests, cargo test --features=backtraces 18 | backtraces = ["cosmwasm-std/backtraces"] 19 | # use library feature to disable all instantiate/execute/query exports 20 | library = [] 21 | 22 | [dependencies] 23 | cosmwasm-std.workspace = true 24 | cosmwasm-storage.workspace = true 25 | cosmwasm-schema.workspace = true 26 | 27 | cw-storage-plus.workspace = true 28 | cw2.workspace = true 29 | cw20.workspace = true 30 | cw20-base.workspace = true 31 | 32 | bech32.workspace = true 33 | schemars.workspace = true 34 | serde.workspace = true 35 | 36 | ripemd.workspace = true 37 | sha2.workspace = true 38 | sha3.workspace = true 39 | 40 | thiserror.workspace = true 41 | 42 | [dev-dependencies] 43 | rstest.workspace = true 44 | ibcx-test-utils.workspace = true 45 | 46 | cw-multi-test.workspace = true 47 | -------------------------------------------------------------------------------- /packages/interface/src/connection.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | 3 | #[cw_serde] 4 | pub enum ConnectionMsg { 5 | SetMailbox { mailbox: String }, 6 | 7 | SetHook { hook: String }, 8 | 9 | SetIsm { ism: String }, 10 | } 11 | 12 | #[cw_serde] 13 | #[derive(QueryResponses)] 14 | pub enum ConnectionQueryMsg { 15 | #[returns(MailboxResponse)] 16 | GetMailbox {}, 17 | 18 | #[returns(HookResponse)] 19 | GetHook {}, 20 | 21 | #[returns(IsmResponse)] 22 | GetIsm {}, 23 | } 24 | 25 | #[cw_serde] 26 | pub struct MailboxResponse { 27 | pub mailbox: Option, 28 | } 29 | 30 | #[cw_serde] 31 | pub struct HookResponse { 32 | pub hook: Option, 33 | } 34 | 35 | #[cw_serde] 36 | pub struct IsmResponse { 37 | pub ism: Option, 38 | } 39 | -------------------------------------------------------------------------------- /packages/interface/src/core/mod.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | use cosmwasm_std::HexBinary; 3 | 4 | pub mod mailbox; 5 | pub mod va; 6 | 7 | #[cw_serde] 8 | #[derive(Default)] 9 | pub struct HandleMsg { 10 | pub origin: u32, 11 | pub sender: HexBinary, 12 | pub body: HexBinary, 13 | } 14 | 15 | impl HandleMsg { 16 | pub fn wrap(self) -> ExpectedHandleMsg { 17 | ExpectedHandleMsg::Handle(self) 18 | } 19 | } 20 | 21 | #[cw_serde] 22 | pub enum ExpectedHandleMsg { 23 | Handle(HandleMsg), 24 | } 25 | -------------------------------------------------------------------------------- /packages/interface/src/core/va.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::HexBinary; 3 | 4 | #[cw_serde] 5 | pub struct InstantiateMsg { 6 | pub hrp: String, 7 | pub mailbox: String, 8 | } 9 | 10 | #[cw_serde] 11 | pub enum ExecuteMsg { 12 | Announce { 13 | validator: HexBinary, 14 | signature: HexBinary, 15 | storage_location: String, 16 | }, 17 | } 18 | 19 | #[cw_serde] 20 | #[derive(QueryResponses)] 21 | pub enum QueryMsg { 22 | #[returns(GetAnnounceStorageLocationsResponse)] 23 | GetAnnounceStorageLocations { validators: Vec }, 24 | 25 | #[returns(GetAnnouncedValidatorsResponse)] 26 | GetAnnouncedValidators {}, 27 | 28 | #[returns(MailboxResponse)] 29 | Mailbox {}, 30 | 31 | #[returns(LocalDomainResponse)] 32 | LocalDomain {}, 33 | } 34 | 35 | #[cw_serde] 36 | pub struct GetAnnounceStorageLocationsResponse { 37 | pub storage_locations: Vec<(String, Vec)>, 38 | } 39 | 40 | #[cw_serde] 41 | pub struct GetAnnouncedValidatorsResponse { 42 | pub validators: Vec, 43 | } 44 | 45 | #[cw_serde] 46 | pub struct MailboxResponse { 47 | pub mailbox: String, 48 | } 49 | 50 | #[cw_serde] 51 | pub struct LocalDomainResponse { 52 | pub local_domain: u32, 53 | } 54 | -------------------------------------------------------------------------------- /packages/interface/src/hook/aggregate.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | 3 | use crate::ownable::{OwnableMsg, OwnableQueryMsg}; 4 | 5 | use super::{HookQueryMsg, PostDispatchMsg}; 6 | 7 | pub const TREE_DEPTH: usize = 32; 8 | 9 | #[cw_serde] 10 | pub struct InstantiateMsg { 11 | pub owner: String, 12 | pub hooks: Vec, 13 | } 14 | 15 | #[cw_serde] 16 | pub enum ExecuteMsg { 17 | Ownable(OwnableMsg), 18 | PostDispatch(PostDispatchMsg), 19 | SetHooks { hooks: Vec }, 20 | } 21 | 22 | #[cw_serde] 23 | #[derive(QueryResponses)] 24 | #[query_responses(nested)] 25 | pub enum QueryMsg { 26 | Ownable(OwnableQueryMsg), 27 | Hook(HookQueryMsg), 28 | AggregateHook(AggregateHookQueryMsg), 29 | } 30 | 31 | #[cw_serde] 32 | #[derive(QueryResponses)] 33 | pub enum AggregateHookQueryMsg { 34 | #[returns(HooksResponse)] 35 | Hooks {}, 36 | } 37 | 38 | #[cw_serde] 39 | pub struct HooksResponse { 40 | pub hooks: Vec, 41 | } 42 | 43 | #[cfg(test)] 44 | mod test { 45 | use cosmwasm_std::HexBinary; 46 | 47 | use super::*; 48 | use crate::{ 49 | hook::{ExpectedHookQueryMsg, PostDispatchMsg, QuoteDispatchMsg}, 50 | msg_checker, 51 | }; 52 | 53 | #[test] 54 | fn test_hook_interface() { 55 | let _checked: ExecuteMsg = msg_checker( 56 | PostDispatchMsg { 57 | metadata: HexBinary::default(), 58 | message: HexBinary::default(), 59 | } 60 | .wrap(), 61 | ); 62 | 63 | let _checked: QueryMsg = msg_checker(ExpectedHookQueryMsg::Hook(HookQueryMsg::Mailbox {})); 64 | let _checked: QueryMsg = msg_checker( 65 | QuoteDispatchMsg { 66 | metadata: HexBinary::default(), 67 | message: HexBinary::default(), 68 | } 69 | .request(), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/interface/src/hook/fee.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::{Addr, Coin}; 3 | 4 | use crate::ownable::{OwnableMsg, OwnableQueryMsg}; 5 | 6 | use super::{HookQueryMsg, PostDispatchMsg}; 7 | 8 | pub const TREE_DEPTH: usize = 32; 9 | 10 | #[cw_serde] 11 | pub struct InstantiateMsg { 12 | pub owner: String, 13 | pub fee: Coin, 14 | } 15 | 16 | #[cw_serde] 17 | pub enum ExecuteMsg { 18 | Ownable(OwnableMsg), 19 | PostDispatch(PostDispatchMsg), 20 | FeeHook(FeeHookMsg), 21 | } 22 | 23 | #[cw_serde] 24 | pub enum FeeHookMsg { 25 | SetFee { 26 | fee: Coin, 27 | }, 28 | Claim { 29 | recipient: Option 30 | } 31 | } 32 | 33 | #[cw_serde] 34 | #[derive(QueryResponses)] 35 | #[query_responses(nested)] 36 | pub enum QueryMsg { 37 | Ownable(OwnableQueryMsg), 38 | Hook(HookQueryMsg), 39 | FeeHook(FeeHookQueryMsg), 40 | } 41 | 42 | #[cw_serde] 43 | #[derive(QueryResponses)] 44 | pub enum FeeHookQueryMsg { 45 | #[returns(FeeResponse)] 46 | Fee {} 47 | } 48 | 49 | #[cw_serde] 50 | pub struct FeeResponse { 51 | pub fee: Coin, 52 | } 53 | 54 | #[cfg(test)] 55 | mod test { 56 | use cosmwasm_std::HexBinary; 57 | 58 | use super::*; 59 | use crate::{ 60 | hook::{ExpectedHookQueryMsg, PostDispatchMsg, QuoteDispatchMsg}, 61 | msg_checker, 62 | }; 63 | 64 | #[test] 65 | fn test_hook_interface() { 66 | let _checked: ExecuteMsg = msg_checker( 67 | PostDispatchMsg { 68 | metadata: HexBinary::default(), 69 | message: HexBinary::default(), 70 | } 71 | .wrap(), 72 | ); 73 | 74 | let _checked: QueryMsg = msg_checker(ExpectedHookQueryMsg::Hook(HookQueryMsg::Mailbox {})); 75 | let _checked: QueryMsg = msg_checker( 76 | QuoteDispatchMsg { 77 | metadata: HexBinary::default(), 78 | message: HexBinary::default(), 79 | } 80 | .request(), 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /packages/interface/src/hook/merkle.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::HexBinary; 3 | 4 | use super::{HookQueryMsg, PostDispatchMsg}; 5 | 6 | pub const TREE_DEPTH: usize = 32; 7 | 8 | #[cw_serde] 9 | pub struct InstantiateMsg { 10 | pub mailbox: String, 11 | } 12 | 13 | #[cw_serde] 14 | pub enum ExecuteMsg { 15 | PostDispatch(PostDispatchMsg), 16 | } 17 | 18 | #[cw_serde] 19 | #[derive(QueryResponses)] 20 | #[query_responses(nested)] 21 | pub enum QueryMsg { 22 | Hook(HookQueryMsg), 23 | MerkleHook(MerkleHookQueryMsg), 24 | } 25 | 26 | #[cw_serde] 27 | #[derive(QueryResponses)] 28 | pub enum MerkleHookQueryMsg { 29 | #[returns(CountResponse)] 30 | Count {}, 31 | 32 | #[returns(RootResponse)] 33 | Root {}, 34 | 35 | #[returns(BranchResponse)] 36 | Branch {}, 37 | 38 | #[returns(TreeResponse)] 39 | Tree {}, 40 | 41 | #[returns(CheckPointResponse)] 42 | CheckPoint {}, 43 | } 44 | 45 | #[cw_serde] 46 | pub struct CountResponse { 47 | pub count: u32, 48 | } 49 | 50 | #[cw_serde] 51 | pub struct RootResponse { 52 | pub root: HexBinary, 53 | } 54 | 55 | #[cw_serde] 56 | pub struct BranchResponse { 57 | pub branch: [HexBinary; TREE_DEPTH], 58 | } 59 | 60 | #[cw_serde] 61 | pub struct TreeResponse { 62 | pub branch: [HexBinary; TREE_DEPTH], 63 | pub count: u32, 64 | } 65 | 66 | #[cw_serde] 67 | pub struct CheckPointResponse { 68 | pub root: HexBinary, 69 | pub count: u32, 70 | } 71 | 72 | #[cfg(test)] 73 | mod test { 74 | use super::*; 75 | use crate::{ 76 | hook::{ExpectedHookQueryMsg, PostDispatchMsg, QuoteDispatchMsg}, 77 | msg_checker, 78 | }; 79 | 80 | #[test] 81 | fn test_hook_interface() { 82 | let _checked: ExecuteMsg = msg_checker( 83 | PostDispatchMsg { 84 | metadata: HexBinary::default(), 85 | message: HexBinary::default(), 86 | } 87 | .wrap(), 88 | ); 89 | 90 | let _checked: QueryMsg = msg_checker(ExpectedHookQueryMsg::Hook(HookQueryMsg::Mailbox {})); 91 | let _checked: QueryMsg = msg_checker( 92 | QuoteDispatchMsg { 93 | metadata: HexBinary::default(), 94 | message: HexBinary::default(), 95 | } 96 | .request(), 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /packages/interface/src/hook/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod aggregate; 2 | pub mod merkle; 3 | pub mod pausable; 4 | pub mod routing; 5 | pub mod routing_custom; 6 | pub mod routing_fallback; 7 | pub mod fee; 8 | 9 | use cosmwasm_schema::{cw_serde, QueryResponses}; 10 | use cosmwasm_std::{ 11 | wasm_execute, Coin, CustomQuery, HexBinary, QuerierWrapper, StdResult, WasmMsg, 12 | }; 13 | 14 | #[cw_serde] 15 | pub struct PostDispatchMsg { 16 | pub metadata: HexBinary, 17 | pub message: HexBinary, 18 | } 19 | 20 | impl PostDispatchMsg { 21 | pub fn wrap(self) -> ExpectedHookMsg { 22 | ExpectedHookMsg::PostDispatch(self) 23 | } 24 | } 25 | 26 | #[cw_serde] 27 | #[derive(Default)] 28 | pub struct QuoteDispatchMsg { 29 | pub metadata: HexBinary, 30 | pub message: HexBinary, 31 | } 32 | 33 | impl QuoteDispatchMsg { 34 | pub fn wrap(self) -> HookQueryMsg { 35 | HookQueryMsg::QuoteDispatch(self) 36 | } 37 | 38 | pub fn request(self) -> ExpectedHookQueryMsg { 39 | ExpectedHookQueryMsg::Hook(self.wrap()) 40 | } 41 | } 42 | 43 | /// This is the basic message to demonstrate the required interface 44 | #[cw_serde] 45 | pub enum ExpectedHookMsg { 46 | PostDispatch(PostDispatchMsg), 47 | } 48 | 49 | #[cw_serde] 50 | #[derive(QueryResponses)] 51 | pub enum HookQueryMsg { 52 | #[returns(QuoteDispatchResponse)] 53 | QuoteDispatch(QuoteDispatchMsg), 54 | 55 | #[returns(MailboxResponse)] 56 | Mailbox {}, 57 | } 58 | 59 | #[cw_serde] 60 | #[derive(QueryResponses)] 61 | #[query_responses(nested)] 62 | pub enum ExpectedHookQueryMsg { 63 | Hook(HookQueryMsg), 64 | } 65 | 66 | #[cw_serde] 67 | pub struct MailboxResponse { 68 | pub mailbox: String, 69 | } 70 | 71 | #[cw_serde] 72 | pub struct QuoteDispatchResponse { 73 | pub fees: Vec, 74 | } 75 | 76 | pub fn post_dispatch( 77 | hook: impl Into, 78 | metadata: impl Into, 79 | message: impl Into, 80 | funds: Option>, 81 | ) -> StdResult { 82 | wasm_execute( 83 | hook, 84 | &PostDispatchMsg { 85 | metadata: metadata.into(), 86 | message: message.into(), 87 | } 88 | .wrap(), 89 | funds.unwrap_or_default(), 90 | ) 91 | } 92 | 93 | pub fn quote_dispatch( 94 | querier: &QuerierWrapper, 95 | hook: impl Into, 96 | metadata: impl Into, 97 | message: impl Into, 98 | ) -> StdResult { 99 | querier.query_wasm_smart( 100 | hook, 101 | &QuoteDispatchMsg { 102 | metadata: metadata.into(), 103 | message: message.into(), 104 | } 105 | .request(), 106 | ) 107 | } 108 | -------------------------------------------------------------------------------- /packages/interface/src/hook/pausable.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | 3 | use crate::{ 4 | ownable::{OwnableMsg, OwnableQueryMsg}, 5 | pausable::{PausableMsg, PausableQueryMsg}, 6 | }; 7 | 8 | use super::{HookQueryMsg, PostDispatchMsg}; 9 | 10 | #[cw_serde] 11 | pub struct InstantiateMsg { 12 | pub owner: String, 13 | pub paused: bool, 14 | } 15 | 16 | #[cw_serde] 17 | pub enum ExecuteMsg { 18 | Ownable(OwnableMsg), 19 | Pausable(PausableMsg), 20 | PostDispatch(PostDispatchMsg), 21 | } 22 | 23 | #[cw_serde] 24 | #[derive(QueryResponses)] 25 | #[query_responses(nested)] 26 | pub enum QueryMsg { 27 | Pausable(PausableQueryMsg), 28 | Ownable(OwnableQueryMsg), 29 | Hook(HookQueryMsg), 30 | } 31 | 32 | #[cfg(test)] 33 | mod test { 34 | use cosmwasm_std::HexBinary; 35 | 36 | use super::*; 37 | use crate::{ 38 | hook::{ExpectedHookQueryMsg, PostDispatchMsg, QuoteDispatchMsg}, 39 | msg_checker, 40 | }; 41 | 42 | #[test] 43 | fn test_hook_interface() { 44 | let _checked: ExecuteMsg = msg_checker( 45 | PostDispatchMsg { 46 | metadata: HexBinary::default(), 47 | message: HexBinary::default(), 48 | } 49 | .wrap(), 50 | ); 51 | 52 | let _checked: QueryMsg = msg_checker(ExpectedHookQueryMsg::Hook(HookQueryMsg::Mailbox {})); 53 | let _checked: QueryMsg = msg_checker( 54 | QuoteDispatchMsg { 55 | metadata: HexBinary::default(), 56 | message: HexBinary::default(), 57 | } 58 | .request(), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/interface/src/hook/routing.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::Addr; 3 | 4 | use crate::{ 5 | ownable::{OwnableMsg, OwnableQueryMsg}, 6 | router::{RouterMsg, RouterQuery}, 7 | }; 8 | 9 | use super::{HookQueryMsg, PostDispatchMsg}; 10 | 11 | #[cw_serde] 12 | pub struct InstantiateMsg { 13 | pub owner: String, 14 | } 15 | 16 | #[cw_serde] 17 | pub enum ExecuteMsg { 18 | Ownable(OwnableMsg), 19 | PostDispatch(PostDispatchMsg), 20 | Router(RouterMsg), 21 | } 22 | 23 | #[cw_serde] 24 | #[derive(QueryResponses)] 25 | #[query_responses(nested)] 26 | pub enum QueryMsg { 27 | Ownable(OwnableQueryMsg), 28 | Router(RouterQuery), 29 | Hook(HookQueryMsg), 30 | } 31 | 32 | #[cfg(test)] 33 | mod test { 34 | use cosmwasm_std::HexBinary; 35 | 36 | use super::*; 37 | use crate::{ 38 | hook::{ExpectedHookQueryMsg, PostDispatchMsg, QuoteDispatchMsg}, 39 | msg_checker, 40 | }; 41 | 42 | #[test] 43 | fn test_hook_interface() { 44 | let _checked: ExecuteMsg = msg_checker( 45 | PostDispatchMsg { 46 | metadata: HexBinary::default(), 47 | message: HexBinary::default(), 48 | } 49 | .wrap(), 50 | ); 51 | 52 | let _checked: QueryMsg = msg_checker(ExpectedHookQueryMsg::Hook(HookQueryMsg::Mailbox {})); 53 | let _checked: QueryMsg = msg_checker( 54 | QuoteDispatchMsg { 55 | metadata: HexBinary::default(), 56 | message: HexBinary::default(), 57 | } 58 | .request(), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/interface/src/hook/routing_custom.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::Addr; 3 | 4 | use crate::{ 5 | ownable::{OwnableMsg, OwnableQueryMsg}, 6 | router::{RouterMsg, RouterQuery}, 7 | Order, 8 | }; 9 | 10 | use super::{HookQueryMsg, PostDispatchMsg}; 11 | 12 | #[cw_serde] 13 | pub struct InstantiateMsg { 14 | pub owner: String, 15 | } 16 | 17 | #[cw_serde] 18 | pub struct RegisterCustomHookMsg { 19 | pub dest_domain: u32, 20 | pub recipient: String, 21 | pub hook: String, 22 | } 23 | 24 | #[cw_serde] 25 | pub struct ClearCustomHookMsg { 26 | pub dest_domain: u32, 27 | pub recipient: String, 28 | } 29 | 30 | #[cw_serde] 31 | pub enum ExecuteMsg { 32 | Ownable(OwnableMsg), 33 | PostDispatch(PostDispatchMsg), 34 | Router(RouterMsg), 35 | 36 | RegisterCustomHook(RegisterCustomHookMsg), 37 | RegisterCustomHooks(Vec), 38 | 39 | ClearCustomHook(ClearCustomHookMsg), 40 | ClearCustomHooks(Vec), 41 | } 42 | 43 | #[cw_serde] 44 | #[derive(QueryResponses)] 45 | #[query_responses(nested)] 46 | pub enum QueryMsg { 47 | Ownable(OwnableQueryMsg), 48 | Router(RouterQuery), 49 | Hook(HookQueryMsg), 50 | CustomRoutingHook(CustomRoutingHookQueryMsg), 51 | } 52 | 53 | #[cw_serde] 54 | #[derive(QueryResponses)] 55 | pub enum CustomRoutingHookQueryMsg { 56 | #[returns(CustomHookResponse)] 57 | CustomHook { dest_domain: u32, recipient: String }, 58 | 59 | #[returns(CustomHooksResponse)] 60 | CustomHooks { 61 | dest_domain: u32, 62 | offset: Option, 63 | limit: Option, 64 | order: Option, 65 | }, 66 | } 67 | 68 | #[cw_serde] 69 | pub struct CustomHookResponse { 70 | pub dest_domain: u32, 71 | pub recipient: String, 72 | pub hook: String, 73 | } 74 | 75 | #[cw_serde] 76 | pub struct CustomHooksResponse { 77 | pub custom_hooks: Vec, 78 | } 79 | 80 | #[cfg(test)] 81 | mod test { 82 | use cosmwasm_std::HexBinary; 83 | 84 | use super::*; 85 | use crate::{ 86 | hook::{ExpectedHookQueryMsg, PostDispatchMsg, QuoteDispatchMsg}, 87 | msg_checker, 88 | }; 89 | 90 | #[test] 91 | fn test_hook_interface() { 92 | let _checked: ExecuteMsg = msg_checker( 93 | PostDispatchMsg { 94 | metadata: HexBinary::default(), 95 | message: HexBinary::default(), 96 | } 97 | .wrap(), 98 | ); 99 | 100 | let _checked: QueryMsg = msg_checker(ExpectedHookQueryMsg::Hook(HookQueryMsg::Mailbox {})); 101 | let _checked: QueryMsg = msg_checker( 102 | QuoteDispatchMsg { 103 | metadata: HexBinary::default(), 104 | message: HexBinary::default(), 105 | } 106 | .request(), 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /packages/interface/src/hook/routing_fallback.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::Addr; 3 | 4 | use crate::{ 5 | ownable::{OwnableMsg, OwnableQueryMsg}, 6 | router::{RouterMsg, RouterQuery}, 7 | }; 8 | 9 | use super::{HookQueryMsg, PostDispatchMsg}; 10 | 11 | #[cw_serde] 12 | pub struct InstantiateMsg { 13 | pub owner: String, 14 | } 15 | 16 | #[cw_serde] 17 | pub enum ExecuteMsg { 18 | Ownable(OwnableMsg), 19 | PostDispatch(PostDispatchMsg), 20 | Router(RouterMsg), 21 | 22 | SetFallbackHook { hook: String }, 23 | } 24 | 25 | #[cw_serde] 26 | #[derive(QueryResponses)] 27 | #[query_responses(nested)] 28 | pub enum QueryMsg { 29 | Ownable(OwnableQueryMsg), 30 | Router(RouterQuery), 31 | Hook(HookQueryMsg), 32 | } 33 | 34 | #[cfg(test)] 35 | mod test { 36 | use cosmwasm_std::HexBinary; 37 | 38 | use super::*; 39 | use crate::{ 40 | hook::{ExpectedHookQueryMsg, PostDispatchMsg, QuoteDispatchMsg}, 41 | msg_checker, 42 | }; 43 | 44 | #[test] 45 | fn test_hook_interface() { 46 | let _checked: ExecuteMsg = msg_checker( 47 | PostDispatchMsg { 48 | metadata: HexBinary::default(), 49 | message: HexBinary::default(), 50 | } 51 | .wrap(), 52 | ); 53 | 54 | let _checked: QueryMsg = msg_checker(ExpectedHookQueryMsg::Hook(HookQueryMsg::Mailbox {})); 55 | let _checked: QueryMsg = msg_checker( 56 | QuoteDispatchMsg { 57 | metadata: HexBinary::default(), 58 | message: HexBinary::default(), 59 | } 60 | .request(), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/interface/src/igp/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod core; 2 | pub mod oracle; 3 | -------------------------------------------------------------------------------- /packages/interface/src/igp/oracle.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::Uint128; 3 | 4 | use crate::ownable::{OwnableMsg, OwnableQueryMsg}; 5 | 6 | #[cw_serde] 7 | pub struct InstantiateMsg { 8 | pub owner: String, 9 | } 10 | 11 | #[cw_serde] 12 | pub struct RemoteGasDataConfig { 13 | pub remote_domain: u32, 14 | pub token_exchange_rate: Uint128, 15 | pub gas_price: Uint128, 16 | } 17 | 18 | #[cw_serde] 19 | pub enum ExecuteMsg { 20 | // ownership 21 | Ownership(OwnableMsg), 22 | 23 | // gas data 24 | SetRemoteGasDataConfigs { configs: Vec }, 25 | SetRemoteGasData { config: RemoteGasDataConfig }, 26 | } 27 | 28 | #[cw_serde] 29 | #[derive(QueryResponses)] 30 | #[query_responses(nested)] 31 | pub enum QueryMsg { 32 | // overrides 33 | Ownable(OwnableQueryMsg), 34 | 35 | // base 36 | Oracle(IgpGasOracleQueryMsg), 37 | } 38 | 39 | #[cw_serde] 40 | #[derive(QueryResponses)] 41 | pub enum IgpGasOracleQueryMsg { 42 | #[returns(GetExchangeRateAndGasPriceResponse)] 43 | GetExchangeRateAndGasPrice { dest_domain: u32 }, 44 | } 45 | 46 | impl IgpGasOracleQueryMsg { 47 | pub fn wrap(self) -> QueryMsg { 48 | QueryMsg::Oracle(self) 49 | } 50 | } 51 | 52 | #[cw_serde] 53 | pub struct GetExchangeRateAndGasPriceResponse { 54 | pub gas_price: Uint128, 55 | pub exchange_rate: Uint128, 56 | } 57 | -------------------------------------------------------------------------------- /packages/interface/src/ism/aggregate.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | 3 | use crate::ownable::{OwnableMsg, OwnableQueryMsg}; 4 | 5 | use super::IsmQueryMsg; 6 | 7 | #[cw_serde] 8 | pub struct InstantiateMsg { 9 | pub owner: String, 10 | pub isms: Vec, 11 | pub threshold: u8, 12 | } 13 | 14 | #[cw_serde] 15 | pub enum ExecuteMsg { 16 | Ownable(OwnableMsg), 17 | 18 | SetIsms { isms: Vec, threshold: u8 }, 19 | } 20 | 21 | #[cw_serde] 22 | #[derive(QueryResponses)] 23 | #[query_responses(nested)] 24 | pub enum QueryMsg { 25 | Ownable(OwnableQueryMsg), 26 | 27 | Ism(IsmQueryMsg), 28 | 29 | AggregateIsm(AggregateIsmQueryMsg), 30 | } 31 | 32 | #[cw_serde] 33 | #[derive(QueryResponses)] 34 | pub enum AggregateIsmQueryMsg { 35 | #[returns(IsmsResponse)] 36 | Isms {}, 37 | } 38 | 39 | #[cw_serde] 40 | pub struct IsmsResponse { 41 | pub isms: Vec, 42 | pub threshold: u8, 43 | } 44 | 45 | #[cfg(test)] 46 | mod test { 47 | use cosmwasm_std::HexBinary; 48 | 49 | use super::*; 50 | use crate::{ism::IsmQueryMsg, msg_checker}; 51 | 52 | #[test] 53 | fn test_ism_interface() { 54 | let _checked: QueryMsg = msg_checker(IsmQueryMsg::ModuleType {}.wrap()); 55 | let _checked: QueryMsg = msg_checker( 56 | IsmQueryMsg::Verify { 57 | metadata: HexBinary::default(), 58 | message: HexBinary::default(), 59 | } 60 | .wrap(), 61 | ); 62 | let _checked: QueryMsg = msg_checker( 63 | IsmQueryMsg::VerifyInfo { 64 | message: HexBinary::default(), 65 | } 66 | .wrap(), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/interface/src/ism/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod aggregate; 2 | pub mod multisig; 3 | pub mod routing; 4 | pub mod pausable; 5 | 6 | use cosmwasm_schema::{cw_serde, QueryResponses}; 7 | use cosmwasm_std::{Addr, CustomQuery, HexBinary, QuerierWrapper, StdResult}; 8 | 9 | #[cw_serde] 10 | #[repr(u32)] 11 | pub enum IsmType { 12 | Unused = 0, 13 | Routing = 1, 14 | Aggregation = 2, 15 | LegacyMultisig = 3, 16 | MerkleRootMultisig = 4, 17 | MessageIdMultisig = 5, 18 | Null = 6, // used with relayer carrying no metadata 19 | CcipRead = 7, 20 | } 21 | 22 | #[cw_serde] 23 | #[derive(QueryResponses)] 24 | pub enum IsmQueryMsg { 25 | #[returns(ModuleTypeResponse)] 26 | ModuleType {}, 27 | 28 | #[returns(VerifyResponse)] 29 | Verify { 30 | metadata: HexBinary, 31 | message: HexBinary, 32 | }, 33 | 34 | #[returns(VerifyInfoResponse)] 35 | VerifyInfo { message: HexBinary }, 36 | } 37 | 38 | impl IsmQueryMsg { 39 | pub fn wrap(self) -> ExpectedIsmQueryMsg { 40 | ExpectedIsmQueryMsg::Ism(self) 41 | } 42 | } 43 | 44 | #[cw_serde] 45 | #[derive(QueryResponses)] 46 | #[query_responses(nested)] 47 | pub enum ExpectedIsmQueryMsg { 48 | Ism(IsmQueryMsg), 49 | } 50 | 51 | #[cw_serde] 52 | #[derive(QueryResponses)] 53 | #[query_responses(nested)] 54 | pub enum ExpectedIsmSpecifierQueryMsg { 55 | IsmSpecifier(IsmSpecifierQueryMsg), 56 | } 57 | 58 | #[cw_serde] 59 | #[derive(QueryResponses)] 60 | pub enum IsmSpecifierQueryMsg { 61 | #[returns(InterchainSecurityModuleResponse)] 62 | InterchainSecurityModule(), 63 | } 64 | 65 | impl IsmSpecifierQueryMsg { 66 | pub fn wrap(self) -> ExpectedIsmSpecifierQueryMsg { 67 | ExpectedIsmSpecifierQueryMsg::IsmSpecifier(self) 68 | } 69 | } 70 | 71 | #[cw_serde] 72 | pub struct ModuleTypeResponse { 73 | #[serde(rename = "type")] 74 | pub typ: IsmType, 75 | } 76 | 77 | #[cw_serde] 78 | pub struct VerifyResponse { 79 | pub verified: bool, 80 | } 81 | 82 | #[cw_serde] 83 | pub struct VerifyInfoResponse { 84 | pub threshold: u8, 85 | pub validators: Vec, 86 | } 87 | 88 | #[cw_serde] 89 | pub struct InterchainSecurityModuleResponse { 90 | pub ism: Option, 91 | } 92 | 93 | pub fn recipient( 94 | querier: &QuerierWrapper, 95 | recipient: impl Into, 96 | ) -> StdResult> { 97 | let res = querier.query_wasm_smart::( 98 | recipient, 99 | &IsmSpecifierQueryMsg::InterchainSecurityModule().wrap(), 100 | )?; 101 | 102 | Ok(res.ism) 103 | } 104 | 105 | pub fn verify( 106 | querier: &QuerierWrapper, 107 | ism: impl Into, 108 | metadata: HexBinary, 109 | message: HexBinary, 110 | ) -> StdResult { 111 | let verify_resp = querier.query_wasm_smart::( 112 | ism, 113 | &IsmQueryMsg::Verify { metadata, message }.wrap(), 114 | )?; 115 | 116 | Ok(verify_resp.verified) 117 | } 118 | -------------------------------------------------------------------------------- /packages/interface/src/ism/multisig.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::HexBinary; 3 | 4 | use crate::ownable::{OwnableMsg, OwnableQueryMsg}; 5 | 6 | use super::IsmQueryMsg; 7 | #[allow(unused_imports)] 8 | use super::{ModuleTypeResponse, VerifyInfoResponse, VerifyResponse}; 9 | 10 | #[cw_serde] 11 | pub struct InstantiateMsg { 12 | pub owner: String, 13 | } 14 | 15 | #[cw_serde] 16 | pub struct ValidatorSet { 17 | pub domain: u32, 18 | pub validator: HexBinary, 19 | } 20 | 21 | #[cw_serde] 22 | pub enum ExecuteMsg { 23 | Ownable(OwnableMsg), 24 | 25 | SetValidators { 26 | domain: u32, 27 | threshold: u8, 28 | validators: Vec, // should be 20 lenghted 29 | }, 30 | UnsetDomain { 31 | domain: u32, 32 | }, 33 | } 34 | 35 | #[cw_serde] 36 | #[derive(QueryResponses)] 37 | #[query_responses(nested)] 38 | pub enum QueryMsg { 39 | Ownable(OwnableQueryMsg), 40 | Ism(IsmQueryMsg), 41 | MultisigIsm(MultisigIsmQueryMsg), 42 | } 43 | 44 | #[cw_serde] 45 | #[derive(QueryResponses)] 46 | pub enum MultisigIsmQueryMsg { 47 | #[returns(EnrolledValidatorsResponse)] 48 | EnrolledValidators { domain: u32 }, 49 | } 50 | 51 | #[cw_serde] 52 | pub struct EnrolledValidatorsResponse { 53 | pub validators: Vec, 54 | pub threshold: u8, 55 | } 56 | 57 | #[cfg(test)] 58 | mod test { 59 | use super::*; 60 | use crate::msg_checker; 61 | 62 | #[test] 63 | fn test_ism_interface() { 64 | let _checked: QueryMsg = msg_checker(IsmQueryMsg::ModuleType {}.wrap()); 65 | let _checked: QueryMsg = msg_checker( 66 | IsmQueryMsg::Verify { 67 | metadata: HexBinary::default(), 68 | message: HexBinary::default(), 69 | } 70 | .wrap(), 71 | ); 72 | let _checked: QueryMsg = msg_checker( 73 | IsmQueryMsg::VerifyInfo { 74 | message: HexBinary::default(), 75 | } 76 | .wrap(), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/interface/src/ism/pausable.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | 3 | use crate::{ownable::{OwnableMsg, OwnableQueryMsg}, pausable::{PausableMsg, PausableQueryMsg}}; 4 | 5 | use super::IsmQueryMsg; 6 | 7 | #[cw_serde] 8 | pub struct InstantiateMsg { 9 | pub owner: String, 10 | pub paused: bool 11 | } 12 | 13 | #[cw_serde] 14 | pub enum ExecuteMsg { 15 | Ownable(OwnableMsg), 16 | Pausable(PausableMsg) 17 | } 18 | 19 | #[cw_serde] 20 | #[derive(QueryResponses)] 21 | #[query_responses(nested)] 22 | pub enum QueryMsg { 23 | Ownable(OwnableQueryMsg), 24 | Ism(IsmQueryMsg), 25 | Pausable(PausableQueryMsg) 26 | } 27 | -------------------------------------------------------------------------------- /packages/interface/src/ism/routing.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::HexBinary; 3 | 4 | use crate::ownable::{OwnableMsg, OwnableQueryMsg}; 5 | 6 | use super::IsmQueryMsg; 7 | #[allow(unused_imports)] 8 | use super::{ModuleTypeResponse, VerifyResponse}; 9 | 10 | #[cw_serde] 11 | pub struct IsmSet { 12 | pub domain: u32, 13 | pub address: String, 14 | } 15 | 16 | #[cw_serde] 17 | pub struct InstantiateMsg { 18 | pub owner: String, 19 | pub isms: Vec, 20 | } 21 | 22 | #[cw_serde] 23 | pub enum ExecuteMsg { 24 | Ownable(OwnableMsg), 25 | 26 | Set { ism: IsmSet }, 27 | Unset { domains: Vec }, 28 | } 29 | 30 | #[cw_serde] 31 | #[derive(QueryResponses)] 32 | #[query_responses(nested)] 33 | pub enum QueryMsg { 34 | Ownable(OwnableQueryMsg), 35 | Ism(IsmQueryMsg), 36 | RoutingIsm(RoutingIsmQueryMsg), 37 | } 38 | 39 | #[cw_serde] 40 | #[derive(QueryResponses)] 41 | pub enum RoutingIsmQueryMsg { 42 | #[returns(RouteResponse)] 43 | Route { message: HexBinary }, 44 | } 45 | 46 | #[cw_serde] 47 | pub struct RouteResponse { 48 | pub ism: String, 49 | } 50 | 51 | #[cfg(test)] 52 | mod test { 53 | use cosmwasm_std::HexBinary; 54 | 55 | use super::*; 56 | use crate::{ism::IsmQueryMsg, msg_checker}; 57 | 58 | #[test] 59 | fn test_ism_interface() { 60 | let _checked: QueryMsg = msg_checker(IsmQueryMsg::ModuleType {}.wrap()); 61 | let _checked: QueryMsg = msg_checker( 62 | IsmQueryMsg::Verify { 63 | metadata: HexBinary::default(), 64 | message: HexBinary::default(), 65 | } 66 | .wrap(), 67 | ); 68 | let _checked: QueryMsg = msg_checker( 69 | IsmQueryMsg::VerifyInfo { 70 | message: HexBinary::default(), 71 | } 72 | .wrap(), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use cosmwasm_schema::cw_serde; 4 | use cosmwasm_std::{to_json_binary, QueryResponse, StdError, StdResult}; 5 | use cw_storage_plus::Bound; 6 | 7 | pub mod connection; 8 | pub mod core; 9 | pub mod hook; 10 | pub mod igp; 11 | pub mod ism; 12 | mod macros; 13 | pub mod ownable; 14 | pub mod pausable; 15 | pub mod router; 16 | pub mod types; 17 | pub mod warp; 18 | 19 | pub use macros::*; 20 | 21 | #[cw_serde] 22 | pub enum Order { 23 | Asc, 24 | Desc, 25 | } 26 | 27 | impl Default for Order { 28 | fn default() -> Self { 29 | Self::Asc 30 | } 31 | } 32 | 33 | impl From for cosmwasm_std::Order { 34 | fn from(v: Order) -> Self { 35 | match v { 36 | Order::Asc => cosmwasm_std::Order::Ascending, 37 | Order::Desc => cosmwasm_std::Order::Descending, 38 | } 39 | } 40 | } 41 | 42 | // Settings for pagination 43 | pub const MAX_LIMIT: u32 = 30; 44 | pub const DEFAULT_LIMIT: u32 = 10; 45 | 46 | pub fn get_and_check_limit(limit: Option, max: u32, default: u32) -> StdResult { 47 | match limit { 48 | Some(l) => { 49 | if l <= max { 50 | Ok(l) 51 | } else { 52 | Err(StdError::generic_err(format!( 53 | "oversized request. size: {:?}, max: {:?}", 54 | l as u64, max as u64, 55 | ))) 56 | } 57 | } 58 | None => Ok(default), 59 | } 60 | } 61 | 62 | type RangeOptionRespBound<'a, T> = Option>; 63 | type RangeOptionResp<'a, T> = ( 64 | (RangeOptionRespBound<'a, T>, RangeOptionRespBound<'a, T>), 65 | usize, 66 | Order, 67 | ); 68 | 69 | pub fn range_option<'a, T: cw_storage_plus::PrimaryKey<'a>>( 70 | start: Option, 71 | limit: Option, 72 | order: Option, 73 | ) -> StdResult> { 74 | let limit = get_and_check_limit(limit, MAX_LIMIT, DEFAULT_LIMIT)? as usize; 75 | let order = order.unwrap_or(Order::Asc); 76 | let (min, max) = match order { 77 | Order::Asc => (start.map(Bound::exclusive), None), 78 | Order::Desc => (None, start.map(Bound::exclusive)), 79 | }; 80 | 81 | Ok(((min, max), limit, order)) 82 | } 83 | 84 | pub fn to_binary + From>( 85 | data: Result, 86 | ) -> Result { 87 | data.map(|v| to_json_binary(&v))?.map_err(|err| err.into()) 88 | } 89 | 90 | #[cfg(test)] 91 | pub use test::msg_checker; 92 | 93 | #[cfg(test)] 94 | mod test { 95 | use cosmwasm_std::{from_json, to_json_binary}; 96 | use serde::{de::DeserializeOwned, Serialize}; 97 | 98 | pub fn msg_checker(input: Input) -> Output { 99 | from_json::(to_json_binary(&input).unwrap()).unwrap() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /packages/interface/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! build_test_querier { 3 | ($handler:expr) => { 4 | fn test_query< 5 | S: cosmwasm_schema::serde::Serialize, 6 | T: cosmwasm_schema::serde::de::DeserializeOwned, 7 | >( 8 | deps: cosmwasm_std::Deps, 9 | msg: S, 10 | ) -> T { 11 | let res = $handler( 12 | deps, 13 | cosmwasm_std::testing::mock_env(), 14 | cosmwasm_std::from_json(cosmwasm_std::to_json_binary(&msg).unwrap()).unwrap(), 15 | ) 16 | .map_err(|e| e.to_string()) 17 | .unwrap(); 18 | cosmwasm_std::from_json(&res).unwrap() 19 | } 20 | }; 21 | } 22 | 23 | #[macro_export] 24 | macro_rules! build_test_executor { 25 | ($handler:expr) => { 26 | fn test_execute( 27 | deps: cosmwasm_std::DepsMut, 28 | sender: &cosmwasm_std::Addr, 29 | msg: S, 30 | funds: Vec, 31 | ) -> cosmwasm_std::Response { 32 | $handler( 33 | deps, 34 | cosmwasm_std::testing::mock_env(), 35 | cosmwasm_std::testing::mock_info(sender.as_str(), &funds), 36 | cosmwasm_std::from_json(cosmwasm_std::to_json_binary(&msg).unwrap()).unwrap(), 37 | ) 38 | .map_err(|e| e.to_string()) 39 | .unwrap() 40 | } 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /packages/interface/src/ownable.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::Addr; 3 | 4 | #[cw_serde] 5 | pub enum OwnableMsg { 6 | InitOwnershipTransfer { next_owner: String }, 7 | RevokeOwnershipTransfer {}, 8 | ClaimOwnership {}, 9 | } 10 | 11 | #[cw_serde] 12 | #[derive(QueryResponses)] 13 | pub enum OwnableQueryMsg { 14 | #[returns(OwnerResponse)] 15 | GetOwner {}, 16 | 17 | #[returns(PendingOwnerResponse)] 18 | GetPendingOwner {}, 19 | } 20 | 21 | #[cw_serde] 22 | pub struct OwnerResponse { 23 | pub owner: Addr, 24 | } 25 | 26 | #[cw_serde] 27 | pub struct PendingOwnerResponse { 28 | pub pending_owner: Option, 29 | } 30 | -------------------------------------------------------------------------------- /packages/interface/src/pausable.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | 3 | #[cw_serde] 4 | pub enum PausableMsg { 5 | Pause {}, 6 | Release {}, 7 | } 8 | 9 | #[cw_serde] 10 | #[derive(QueryResponses)] 11 | pub enum PausableQueryMsg { 12 | #[returns(PauseInfoResponse)] 13 | PauseInfo {}, 14 | } 15 | 16 | #[cw_serde] 17 | pub struct PauseInfoResponse { 18 | pub paused: bool, 19 | } 20 | -------------------------------------------------------------------------------- /packages/interface/src/router.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use cosmwasm_schema::{cw_serde, QueryResponses}; 4 | 5 | use crate::Order; 6 | 7 | #[cw_serde] 8 | pub struct DomainRouteSet { 9 | pub domain: u32, 10 | pub route: Option, 11 | } 12 | 13 | #[cw_serde] 14 | pub enum RouterMsg { 15 | SetRoute { set: DomainRouteSet }, 16 | SetRoutes { set: Vec> }, 17 | } 18 | 19 | #[cw_serde] 20 | #[derive(QueryResponses)] 21 | pub enum RouterQuery { 22 | #[returns(DomainsResponse)] 23 | Domains {}, 24 | 25 | #[returns(RouteResponse)] 26 | GetRoute { domain: u32 }, 27 | 28 | #[returns(RoutesResponse)] 29 | ListRoutes { 30 | offset: Option, 31 | limit: Option, 32 | order: Option, 33 | }, 34 | 35 | #[serde(skip)] 36 | #[returns(cosmwasm_std::Empty)] 37 | Placeholder(PhantomData), 38 | } 39 | 40 | #[cw_serde] 41 | pub struct DomainsResponse { 42 | pub domains: Vec, 43 | } 44 | 45 | #[cw_serde] 46 | pub struct RouteResponse { 47 | pub route: DomainRouteSet, 48 | } 49 | 50 | #[cw_serde] 51 | pub struct RoutesResponse { 52 | pub routes: Vec>, 53 | } 54 | -------------------------------------------------------------------------------- /packages/interface/src/types/crypto.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{HexBinary, StdError, StdResult}; 2 | 3 | const PREFIX: &str = "\x19Ethereum Signed Message:\n"; 4 | 5 | pub fn keccak256_hash(bz: &[u8]) -> HexBinary { 6 | use sha3::{Digest, Keccak256}; 7 | 8 | let mut hasher = Keccak256::new(); 9 | hasher.update(bz); 10 | let hash = hasher.finalize().to_vec(); 11 | 12 | hash.into() 13 | } 14 | 15 | pub fn eth_hash(message: HexBinary) -> StdResult { 16 | let mut eth_message = format!("{PREFIX}{}", message.len()).into_bytes(); 17 | eth_message.extend_from_slice(&message); 18 | let message_hash = keccak256_hash(ð_message); 19 | 20 | Ok(message_hash) 21 | } 22 | 23 | pub fn eth_addr(pubkey: HexBinary) -> StdResult { 24 | let hash = keccak256_hash(&pubkey.as_slice()[1..]); 25 | 26 | let mut bytes = [0u8; 20]; 27 | bytes.copy_from_slice(&hash.as_slice()[12..]); 28 | let addr = HexBinary::from(bytes.to_vec()); 29 | 30 | Ok(addr.to_vec().into()) 31 | } 32 | 33 | pub fn sha256_digest(bz: impl AsRef<[u8]>) -> StdResult<[u8; 32]> { 34 | use sha2::{Digest, Sha256}; 35 | 36 | let mut hasher = Sha256::new(); 37 | 38 | hasher.update(bz); 39 | 40 | hasher 41 | .finalize() 42 | .as_slice() 43 | .try_into() 44 | .map_err(|_| StdError::generic_err("wrong length")) 45 | } 46 | 47 | pub fn ripemd160_digest(bz: impl AsRef<[u8]>) -> StdResult<[u8; 20]> { 48 | use ripemd::{Digest, Ripemd160}; 49 | 50 | let mut hasher = Ripemd160::new(); 51 | 52 | hasher.update(bz); 53 | 54 | hasher 55 | .finalize() 56 | .as_slice() 57 | .try_into() 58 | .map_err(|_| StdError::generic_err("wrong length")) 59 | } 60 | 61 | pub fn pub_to_addr(pub_key: HexBinary) -> StdResult { 62 | let sha_hash = sha256_digest(pub_key)?; 63 | let rip_hash = ripemd160_digest(sha_hash)?; 64 | 65 | Ok(rip_hash.to_vec().into()) 66 | } 67 | -------------------------------------------------------------------------------- /packages/interface/src/types/message.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::cw_serde; 2 | use cosmwasm_std::{Addr, HexBinary, StdResult}; 3 | 4 | use super::bech32_encode; 5 | 6 | #[cw_serde] 7 | pub struct Message { 8 | pub version: u8, 9 | pub nonce: u32, 10 | pub origin_domain: u32, 11 | pub sender: HexBinary, 12 | pub dest_domain: u32, 13 | pub recipient: HexBinary, 14 | pub body: HexBinary, 15 | } 16 | 17 | impl Message { 18 | pub fn id(&self) -> HexBinary { 19 | super::keccak256_hash(&HexBinary::from(self.clone())) 20 | } 21 | 22 | pub fn sender_addr(&self, hrp: &str) -> StdResult { 23 | bech32_encode(hrp, &self.sender) 24 | } 25 | pub fn recipient_addr(&self, hrp: &str) -> StdResult { 26 | bech32_encode(hrp, &self.recipient) 27 | } 28 | } 29 | 30 | impl From for HexBinary { 31 | fn from(v: Message) -> Self { 32 | v.version 33 | .to_be_bytes() 34 | .iter() 35 | .chain(v.nonce.to_be_bytes().iter()) 36 | .chain(v.origin_domain.to_be_bytes().iter()) 37 | .chain(v.sender.to_vec().iter()) 38 | .chain(v.dest_domain.to_be_bytes().iter()) 39 | .chain(v.recipient.to_vec().iter()) 40 | .chain(v.body.to_vec().iter()) 41 | .cloned() 42 | .collect::>() 43 | .into() 44 | } 45 | } 46 | 47 | impl From for Message { 48 | fn from(v: HexBinary) -> Self { 49 | Self { 50 | version: v[0], 51 | nonce: u32::from_be_bytes(v[1..5].try_into().unwrap()), 52 | origin_domain: u32::from_be_bytes(v[5..9].try_into().unwrap()), 53 | sender: v[9..41].to_vec().into(), 54 | dest_domain: u32::from_be_bytes(v[41..45].try_into().unwrap()), 55 | recipient: v[45..77].to_vec().into(), 56 | body: v[77..].to_vec().into(), 57 | } 58 | } 59 | } 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | use cosmwasm_std::HexBinary; 64 | 65 | use super::Message; 66 | 67 | #[test] 68 | fn test_encode_decode() { 69 | let encode_expected = HexBinary::from_hex("00000021500000aef3000000000000000000000000477d860f8f41bc69ddd32821f2bf2c2af0243f1600aa36a70000000000000000000000005d56b8a669f50193b54319442c6eee5edd66238148656c6c6f21").unwrap(); 70 | 71 | let hex = |v: &str| -> HexBinary { HexBinary::from_hex(v).unwrap() }; 72 | 73 | let decode_actual: Message = encode_expected.clone().into(); 74 | let decode_expected = Message { 75 | version: 0, 76 | nonce: 8528, 77 | origin_domain: 44787, 78 | sender: hex("000000000000000000000000477d860f8f41bc69ddd32821f2bf2c2af0243f16"), 79 | dest_domain: 11155111, 80 | recipient: hex("0000000000000000000000005d56b8a669f50193b54319442c6eee5edd662381"), 81 | body: hex("48656c6c6f21"), 82 | }; 83 | let encode_actual: HexBinary = decode_expected.clone().into(); 84 | 85 | assert_eq!(decode_expected, decode_actual); 86 | assert_eq!(encode_expected, encode_actual); 87 | } 88 | 89 | #[test] 90 | #[should_panic(expected = "range end index 77 out of range for slice of length 67")] 91 | fn test_overflow() { 92 | let no = HexBinary::from_hex("00000021500000aef3000000000000000000000000477d860f8f41bc69ddd32821f2bf2c2af0243f1600aa36a70000000000000000000000005d56b8a669f50193b543").unwrap(); 93 | 94 | let _msg: Message = no.into(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /packages/interface/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | mod bech32; 2 | mod crypto; 3 | mod merkle; 4 | mod message; 5 | mod metadata; 6 | 7 | pub use crate::types::bech32::{bech32_decode, bech32_encode, bech32_to_h256}; 8 | pub use crate::types::crypto::*; 9 | pub use crate::types::merkle::MerkleTree; 10 | pub use crate::types::message::Message; 11 | pub use crate::types::metadata::*; 12 | -------------------------------------------------------------------------------- /packages/interface/src/warp/cw20.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::{HexBinary, Uint128}; 3 | 4 | use crate::{ 5 | connection::{ConnectionMsg, ConnectionQueryMsg}, 6 | core, 7 | ism::IsmSpecifierQueryMsg, 8 | ownable::{OwnableMsg, OwnableQueryMsg}, 9 | router::{self, RouterQuery}, 10 | }; 11 | 12 | use super::{TokenModeMsg, TokenWarpDefaultQueryMsg}; 13 | 14 | pub use cw20_base::msg::InstantiateMsg as Cw20InitMsg; 15 | 16 | #[cw_serde] 17 | pub enum TokenOption { 18 | Create { 19 | code_id: u64, 20 | init_msg: Box, 21 | }, 22 | Reuse { 23 | contract: String, 24 | }, 25 | } 26 | 27 | #[cw_serde] 28 | pub struct Cw20ModeBridged { 29 | pub code_id: u64, 30 | pub init_msg: Box, 31 | } 32 | 33 | #[cw_serde] 34 | pub struct Cw20ModeCollateral { 35 | pub address: String, 36 | } 37 | 38 | #[cw_serde] 39 | pub struct InstantiateMsg { 40 | pub token: TokenModeMsg, 41 | 42 | pub hrp: String, 43 | pub owner: String, 44 | pub mailbox: String, 45 | } 46 | 47 | #[cw_serde] 48 | pub enum ExecuteMsg { 49 | Ownable(OwnableMsg), 50 | Router(router::RouterMsg), 51 | Connection(ConnectionMsg), 52 | 53 | // handle transfer remote 54 | Handle(core::HandleMsg), 55 | 56 | // transfer to remote 57 | TransferRemote { 58 | dest_domain: u32, 59 | recipient: HexBinary, 60 | amount: Uint128, 61 | hook: Option, 62 | metadata: Option, 63 | }, 64 | } 65 | 66 | #[cw_serde] 67 | #[derive(QueryResponses)] 68 | #[query_responses(nested)] 69 | pub enum QueryMsg { 70 | Ownable(OwnableQueryMsg), 71 | 72 | Router(RouterQuery), 73 | 74 | Connection(ConnectionQueryMsg), 75 | 76 | TokenDefault(TokenWarpDefaultQueryMsg), 77 | 78 | IsmSpecifier(IsmSpecifierQueryMsg), 79 | } 80 | -------------------------------------------------------------------------------- /packages/interface/src/warp/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cw20; 2 | pub mod native; 3 | 4 | use std::fmt; 5 | 6 | use cosmwasm_schema::{cw_serde, QueryResponses}; 7 | use cosmwasm_std::{HexBinary, Uint256}; 8 | 9 | #[cw_serde] 10 | pub enum TokenTypeNative { 11 | Fungible { denom: String }, 12 | NonFungible { class: String }, 13 | } 14 | 15 | #[cw_serde] 16 | pub enum TokenType { 17 | Native(TokenTypeNative), 18 | CW20 { contract: String }, 19 | CW721 { contract: String }, 20 | } 21 | 22 | #[cw_serde] 23 | pub enum TokenModeMsg { 24 | Bridged(Bridged), 25 | Collateral(Collateral), 26 | } 27 | 28 | impl From> for TokenMode { 29 | fn from(v: TokenModeMsg) -> Self { 30 | match v { 31 | TokenModeMsg::Bridged(_) => Self::Bridged, 32 | TokenModeMsg::Collateral(_) => Self::Collateral, 33 | } 34 | } 35 | } 36 | 37 | #[cw_serde] 38 | pub enum TokenMode { 39 | Bridged, 40 | Collateral, 41 | } 42 | 43 | impl fmt::Display for TokenMode { 44 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 45 | write!( 46 | f, 47 | "{}", 48 | match self { 49 | Self::Bridged => "bridged", 50 | Self::Collateral => "collateral", 51 | } 52 | ) 53 | } 54 | } 55 | 56 | #[cw_serde] 57 | pub struct Message { 58 | pub recipient: HexBinary, 59 | pub amount: Uint256, 60 | pub metadata: HexBinary, 61 | } 62 | 63 | impl From for HexBinary { 64 | fn from(v: Message) -> Self { 65 | v.recipient 66 | .iter() 67 | .chain(v.amount.to_be_bytes().iter()) 68 | .chain(v.metadata.iter()) 69 | .cloned() 70 | .collect::>() 71 | .into() 72 | } 73 | } 74 | 75 | impl From for Message { 76 | fn from(v: HexBinary) -> Self { 77 | Self { 78 | recipient: v[0..32].to_vec().into(), 79 | amount: Uint256::from_be_bytes(v[32..64].try_into().unwrap()), 80 | metadata: v[64..].to_vec().into(), 81 | } 82 | } 83 | } 84 | 85 | #[cw_serde] 86 | #[derive(QueryResponses)] 87 | pub enum TokenWarpDefaultQueryMsg { 88 | #[returns(TokenTypeResponse)] 89 | TokenType {}, 90 | 91 | #[returns(TokenModeResponse)] 92 | TokenMode {}, 93 | } 94 | 95 | #[cw_serde] 96 | pub struct TokenTypeResponse { 97 | #[serde(rename = "type")] 98 | pub typ: TokenType, 99 | } 100 | 101 | #[cw_serde] 102 | pub struct TokenModeResponse { 103 | pub mode: TokenMode, 104 | } 105 | -------------------------------------------------------------------------------- /packages/interface/src/warp/native.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | use cosmwasm_std::{HexBinary, Uint128}; 3 | 4 | use crate::{ 5 | connection::{ConnectionMsg, ConnectionQueryMsg}, 6 | core, 7 | ism::IsmSpecifierQueryMsg, 8 | ownable::{OwnableMsg, OwnableQueryMsg}, 9 | router::{RouterMsg, RouterQuery}, 10 | }; 11 | 12 | use super::{TokenModeMsg, TokenWarpDefaultQueryMsg}; 13 | 14 | #[cw_serde] 15 | pub struct DenomUnit { 16 | pub denom: String, 17 | #[serde( 18 | serialize_with = "as_str::serialize", 19 | deserialize_with = "as_str::deserialize" 20 | )] 21 | pub exponent: u32, 22 | pub aliases: Vec, 23 | } 24 | 25 | #[cw_serde] 26 | pub struct Metadata { 27 | pub description: String, 28 | pub denom_units: Vec, 29 | pub base: String, 30 | pub display: String, 31 | pub name: String, 32 | pub symbol: String, 33 | } 34 | 35 | #[cw_serde] 36 | pub struct NativeModeBriged { 37 | pub denom: String, 38 | pub metadata: Option, 39 | } 40 | 41 | #[cw_serde] 42 | pub struct NativeModeCollateral { 43 | pub denom: String, 44 | } 45 | 46 | #[cw_serde] 47 | pub struct InstantiateMsg { 48 | pub token: TokenModeMsg, 49 | 50 | pub hrp: String, 51 | pub owner: String, 52 | pub mailbox: String, 53 | } 54 | 55 | #[cw_serde] 56 | pub enum ExecuteMsg { 57 | Ownable(OwnableMsg), 58 | Router(RouterMsg), 59 | Connection(ConnectionMsg), 60 | 61 | // handle transfer remote 62 | Handle(core::HandleMsg), 63 | 64 | // transfer to remote 65 | TransferRemote { 66 | dest_domain: u32, 67 | recipient: HexBinary, 68 | amount: Uint128, 69 | hook: Option, 70 | metadata: Option, 71 | }, 72 | } 73 | 74 | #[cw_serde] 75 | #[derive(QueryResponses)] 76 | #[query_responses(nested)] 77 | pub enum QueryMsg { 78 | Ownable(OwnableQueryMsg), 79 | 80 | Router(RouterQuery), 81 | 82 | Connection(ConnectionQueryMsg), 83 | 84 | TokenDefault(TokenWarpDefaultQueryMsg), 85 | 86 | IsmSpecifier(IsmSpecifierQueryMsg), 87 | } 88 | 89 | mod as_str { 90 | use serde::{de, Deserialize, Deserializer, Serializer}; 91 | use std::{fmt::Display, str::FromStr}; 92 | 93 | pub fn deserialize<'de, T, D>(deserializer: D) -> Result 94 | where 95 | T: FromStr, 96 | T::Err: Display, 97 | D: Deserializer<'de>, 98 | { 99 | let s = String::deserialize(deserializer)?; 100 | T::from_str(&s).map_err(de::Error::custom) 101 | } 102 | 103 | pub fn serialize(value: &T, serializer: S) -> Result 104 | where 105 | S: Serializer, 106 | T: Display, 107 | { 108 | serializer.serialize_str(&value.to_string()) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /packages/ownable/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-ownable" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cw-storage-plus.workspace = true 25 | cw2.workspace = true 26 | bech32.workspace = true 27 | sha3.workspace = true 28 | schemars.workspace = true 29 | serde.workspace = true 30 | thiserror.workspace = true 31 | cosmwasm-schema.workspace = true 32 | 33 | hpl-interface.workspace = true 34 | 35 | [dev-dependencies] 36 | rstest.workspace = true 37 | 38 | anyhow.workspace = true 39 | -------------------------------------------------------------------------------- /packages/pausable/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-pausable" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cw-storage-plus.workspace = true 25 | cw2.workspace = true 26 | bech32.workspace = true 27 | sha3.workspace = true 28 | schemars.workspace = true 29 | serde.workspace = true 30 | thiserror.workspace = true 31 | cosmwasm-schema.workspace = true 32 | 33 | hpl-ownable.workspace = true 34 | hpl-interface.workspace = true 35 | 36 | [dev-dependencies] 37 | rstest.workspace = true 38 | 39 | anyhow.workspace = true 40 | -------------------------------------------------------------------------------- /packages/pausable/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test; 3 | 4 | use cosmwasm_std::{ 5 | ensure, ensure_eq, to_json_binary, Addr, CustomQuery, Deps, DepsMut, Env, Event, MessageInfo, 6 | QueryResponse, Response, StdError, StdResult, Storage, 7 | }; 8 | use cw_storage_plus::Item; 9 | use hpl_interface::pausable::{PausableMsg, PausableQueryMsg, PauseInfoResponse}; 10 | 11 | const PAUSE_KEY: &str = "pause"; 12 | const PAUSE: Item = Item::new(PAUSE_KEY); 13 | 14 | fn event_to_resp(event: Event) -> Response { 15 | Response::new().add_event(event) 16 | } 17 | 18 | fn new_event(name: &str) -> Event { 19 | Event::new(format!("hpl_pausable::{}", name)) 20 | } 21 | 22 | pub fn initialize(storage: &mut dyn Storage, paused: &bool) -> StdResult<()> { 23 | PAUSE.save(storage, paused)?; 24 | 25 | Ok(()) 26 | } 27 | 28 | pub fn handle( 29 | deps: DepsMut<'_, C>, 30 | _env: Env, 31 | info: MessageInfo, 32 | msg: PausableMsg, 33 | ) -> StdResult { 34 | ensure_eq!( 35 | hpl_ownable::get_owner(deps.storage)?, 36 | info.sender, 37 | StdError::generic_err("unauthorized") 38 | ); 39 | 40 | match msg { 41 | PausableMsg::Pause {} => Ok(event_to_resp(pause(deps.storage, &info.sender)?)), 42 | PausableMsg::Release {} => Ok(event_to_resp(release(deps.storage, &info.sender)?)), 43 | } 44 | } 45 | 46 | pub fn pause(storage: &mut dyn Storage, sender: &Addr) -> StdResult { 47 | ensure!( 48 | !PAUSE.load(storage)?, 49 | StdError::generic_err("already paused") 50 | ); 51 | 52 | PAUSE.save(storage, &true)?; 53 | Ok(new_event("pause").add_attribute("sender", sender)) 54 | } 55 | 56 | pub fn release(storage: &mut dyn Storage, sender: &Addr) -> StdResult { 57 | ensure!( 58 | PAUSE.load(storage)?, 59 | StdError::generic_err("already released") 60 | ); 61 | 62 | PAUSE.save(storage, &false)?; 63 | Ok(new_event("release").add_attribute("sender", sender)) 64 | } 65 | 66 | pub fn handle_query( 67 | deps: Deps<'_, C>, 68 | _env: Env, 69 | msg: PausableQueryMsg, 70 | ) -> StdResult { 71 | match msg { 72 | PausableQueryMsg::PauseInfo {} => to_json_binary(&PauseInfoResponse { 73 | paused: get_pause_info(deps.storage)?, 74 | }), 75 | } 76 | } 77 | 78 | pub fn get_pause_info(storage: &dyn Storage) -> StdResult { 79 | PAUSE.load(storage) 80 | } 81 | -------------------------------------------------------------------------------- /packages/router/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-router" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | cosmwasm-std.workspace = true 23 | cosmwasm-storage.workspace = true 24 | cw-storage-plus.workspace = true 25 | cw2.workspace = true 26 | bech32.workspace = true 27 | sha3.workspace = true 28 | schemars.workspace = true 29 | serde.workspace = true 30 | serde-json-wasm.workspace = true 31 | thiserror.workspace = true 32 | cosmwasm-schema.workspace = true 33 | 34 | hpl-ownable.workspace = true 35 | hpl-interface.workspace = true 36 | 37 | [dev-dependencies] 38 | anyhow.workspace = true 39 | -------------------------------------------------------------------------------- /packages/schema/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-schema" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [dependencies] 13 | cosmwasm-std.workspace = true 14 | cosmwasm-schema.workspace = true 15 | 16 | hpl-interface.workspace = true 17 | -------------------------------------------------------------------------------- /packages/utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hpl-utils" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | documentation.workspace = true 10 | keywords.workspace = true 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | # for more explicit tests, cargo test --features=backtraces 17 | backtraces = ["cosmwasm-std/backtraces"] 18 | # use library feature to disable all instantiate/execute/query exports 19 | library = [] 20 | 21 | [dependencies] 22 | semver.workspace = true 23 | cosmwasm-std.workspace = true 24 | cw2.workspace = true 25 | thiserror.workspace = true 26 | -------------------------------------------------------------------------------- /packages/utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::{ensure_eq, StdError, Storage}; 2 | use semver::Version; 3 | use thiserror::Error; 4 | 5 | #[derive(Error, Debug, PartialEq)] 6 | pub enum MigrationError { 7 | #[error("{0}")] 8 | StdError(#[from] StdError), 9 | 10 | #[error("Semver parsing error: {0}")] 11 | SemVer(String), 12 | } 13 | 14 | impl From for MigrationError { 15 | fn from(err: semver::Error) -> Self { 16 | Self::SemVer(err.to_string()) 17 | } 18 | } 19 | 20 | pub fn migrate( 21 | storage: &mut dyn Storage, 22 | contract_name: &str, 23 | contract_version: &str, 24 | ) -> Result<(), MigrationError> { 25 | let stored = cw2::get_contract_version(storage)?; 26 | 27 | ensure_eq!( 28 | stored.contract, 29 | contract_name, 30 | StdError::generic_err("contract name mismatch") 31 | ); 32 | 33 | let version: Version = contract_version.parse()?; 34 | let stored_version: Version = stored.version.parse()?; 35 | 36 | if stored_version < version { 37 | Ok(cw2::set_contract_version( 38 | storage, 39 | contract_name, 40 | contract_version, 41 | )?) 42 | } else { 43 | Err(StdError::generic_err("invalid version").into()) 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod test { 49 | use cosmwasm_std::{testing::mock_dependencies, StdError, Storage}; 50 | 51 | use crate::migrate; 52 | 53 | fn assert_version(storage: &dyn Storage, contract: &str, version: &str) { 54 | let stored = cw2::get_contract_version(storage).unwrap(); 55 | 56 | assert_eq!(stored.contract, contract); 57 | assert_eq!(stored.version, version); 58 | } 59 | 60 | #[test] 61 | fn test_migrate() { 62 | let mut deps = mock_dependencies(); 63 | cw2::set_contract_version(&mut deps.storage, "hello", "0.0.6-rc8").unwrap(); 64 | 65 | migrate(&mut deps.storage, "nono", "0.0.6-rc8") 66 | .expect_err(&StdError::generic_err("contract name mismatch").to_string()); 67 | 68 | migrate(&mut deps.storage, "hello", "0.0.5") 69 | .expect_err(&StdError::generic_err("invalid version").to_string()); 70 | 71 | migrate(&mut deps.storage, "hello", "0.0.6").unwrap(); 72 | assert_version(&deps.storage, "hello", "0.0.6"); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /script/commands/context.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | 3 | import { saveAgentConfig } from '../shared/agent'; 4 | import { CONTAINER, Dependencies } from '../shared/ioc'; 5 | 6 | const contextCmd = new Command('context'); 7 | 8 | contextCmd 9 | .command('make-agent-config') 10 | .description('Make an agent config') 11 | .option('-o --output ', 'The output directory') 12 | .action(async (_, cmd) => { 13 | const opts = cmd.optsWithGlobals(); 14 | const { ctx, network } = CONTAINER.get(Dependencies); 15 | 16 | await saveAgentConfig( 17 | network, 18 | ctx, 19 | opts.output && { contextPath: opts.output }, 20 | ); 21 | }); 22 | 23 | export { contextCmd }; 24 | -------------------------------------------------------------------------------- /script/commands/contract.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | 3 | import { contractNames } from '../shared/constants'; 4 | import { executeContract } from '../shared/contract'; 5 | import { CONTAINER, Dependencies } from '../shared/ioc'; 6 | import { addPad } from '../shared/utils'; 7 | 8 | export const contractCmd = new Command('contract').configureHelp({ 9 | showGlobalOptions: true, 10 | }); 11 | 12 | contractCmd.command('list').action(() => { 13 | console.log('Available contracts:'.green); 14 | contractNames.forEach((v) => console.log('-', v)); 15 | }); 16 | 17 | contractCmd 18 | .command('test-dispatch') 19 | .argument('dest-domian') 20 | .argument('recipient-addr') 21 | .argument('msg-body') 22 | .action(async (destDomain, recipientAddr, msgBody) => { 23 | const { ctx, client, network } = CONTAINER.get(Dependencies); 24 | 25 | if (!ctx.deployments.core?.mailbox) 26 | throw new Error('Mailbox contract is not deployed. Run `deploy` first.'); 27 | 28 | const mailbox = ctx.deployments.core.mailbox; 29 | 30 | const res = await executeContract( 31 | client, 32 | mailbox, 33 | { 34 | dispatch: { 35 | dest_domain: parseInt(destDomain), 36 | recipient_addr: addPad(recipientAddr), 37 | msg_body: Buffer.from(msgBody, 'utf-8').toString('hex'), 38 | }, 39 | }, 40 | [{ amount: '500', denom: network.gas.denom }], 41 | ); 42 | 43 | console.log(res.hash); 44 | }); 45 | -------------------------------------------------------------------------------- /script/commands/index.ts: -------------------------------------------------------------------------------- 1 | export { contextCmd } from './context'; 2 | export { contractCmd } from './contract'; 3 | export { deployCmd } from './deploy'; 4 | export { migrateCmd } from './migrate'; 5 | export { uploadCmd } from './upload'; 6 | export { walletCmd } from './wallet'; 7 | export { warpCmd } from './warp'; 8 | -------------------------------------------------------------------------------- /script/commands/wallet.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DirectSecp256k1HdWallet, 3 | DirectSecp256k1Wallet, 4 | makeCosmoshubPath, 5 | } from '@cosmjs/proto-signing'; 6 | import { Command } from 'commander'; 7 | 8 | import { getKeyPair } from '../shared/crypto'; 9 | import { CONTAINER, Dependencies } from '../shared/ioc'; 10 | 11 | const walletCmd = new Command('wallet') 12 | .description('Wallet commands') 13 | .configureHelp({ showGlobalOptions: true }); 14 | 15 | walletCmd 16 | .command('new') 17 | .description('Create a new wallet') 18 | .action(async () => { 19 | const deps = CONTAINER.get(Dependencies); 20 | 21 | const prefix = { prefix: deps.network.hrp }; 22 | const wallet = await DirectSecp256k1HdWallet.generate(12, prefix); 23 | 24 | const [account] = await wallet.getAccounts(); 25 | const { privkey } = await getKeyPair(wallet.mnemonic, makeCosmoshubPath(0)); 26 | 27 | console.log({ 28 | address: account.address, 29 | mnemonic: wallet.mnemonic, 30 | privateKey: Buffer.from(privkey).toString('hex'), 31 | }); 32 | }); 33 | 34 | walletCmd 35 | .command('address') 36 | .description('Get the address of the wallet') 37 | .option('--private-key ', 'The private key of the wallet') 38 | .option('--mnemonic ', 'The mnemonic of the wallet') 39 | .action(async (_, cmd) => { 40 | const opts = cmd.optsWithGlobals(); 41 | const deps = CONTAINER.get(Dependencies); 42 | 43 | if ( 44 | (opts.privateKey && opts.mnemonic) || 45 | (!opts.privateKey && !opts.mnemonic) 46 | ) { 47 | throw new Error( 48 | 'Only one of --private-key and --mnemonic can be specified', 49 | ); 50 | } 51 | 52 | const wallet = opts.privateKey 53 | ? await DirectSecp256k1Wallet.fromKey( 54 | Buffer.from( 55 | opts.privateKey.startsWith('0x') 56 | ? opts.privateKey.slice(2) 57 | : opts.privateKey, 58 | 'hex', 59 | ), 60 | deps.network.hrp, 61 | ) 62 | : await DirectSecp256k1HdWallet.fromMnemonic(opts.mnemonic, { 63 | prefix: deps.network.hrp, 64 | }); 65 | 66 | const [account] = await wallet.getAccounts(); 67 | 68 | console.log(account.address); 69 | }); 70 | 71 | export { walletCmd }; 72 | -------------------------------------------------------------------------------- /script/deploy/igp.ts: -------------------------------------------------------------------------------- 1 | import { Client, IgpHookType, getNetwork } from '../shared/config'; 2 | import { Context, ContextHook } from '../shared/context'; 3 | import { deployContract, executeMultiMsg } from '../shared/contract'; 4 | 5 | export const deployIgp = async ( 6 | networkId: string, 7 | ctx: Context, 8 | client: Client, 9 | igpType: IgpHookType, 10 | ): Promise => { 11 | const { hrp, gas } = getNetwork(networkId); 12 | 13 | // init igp 14 | const igp = await deployContract(ctx, client, 'hpl_igp', { 15 | hrp, 16 | owner: igpType.owner === '' ? client.signer : igpType.owner, 17 | gas_token: igpType.token || gas.denom, 18 | beneficiary: client.signer, 19 | default_gas_usage: igpType.default_gas_usage.toString(), 20 | }); 21 | 22 | // init igp oracle 23 | 24 | const igpOracle = await deployContract(ctx, client, 'hpl_igp_oracle', { 25 | owner: client.signer, 26 | }); 27 | 28 | await executeMultiMsg(client, [ 29 | { 30 | contract: igpOracle, 31 | msg: { 32 | set_remote_gas_data_configs: { 33 | configs: Object.entries(igpType.configs).map(([domain, v]) => ({ 34 | remote_domain: Number(domain), 35 | token_exchange_rate: v.exchange_rate.toString(), 36 | gas_price: v.gas_price.toString(), 37 | })), 38 | }, 39 | }, 40 | }, 41 | { 42 | contract: igp, 43 | msg: { 44 | router: { 45 | set_routes: { 46 | set: Object.keys(igpType.configs).map((domain) => ({ 47 | domain: Number(domain), 48 | route: igpOracle.address, 49 | })), 50 | }, 51 | }, 52 | }, 53 | }, 54 | ]); 55 | 56 | return { ...igp, oracle: igpOracle }; 57 | }; 58 | -------------------------------------------------------------------------------- /script/deploy/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hook'; 2 | export * from './igp'; 3 | export * from './ism'; 4 | export * from './warp'; 5 | -------------------------------------------------------------------------------- /script/deploy/ism.ts: -------------------------------------------------------------------------------- 1 | import { Client, IsmType } from '../shared/config'; 2 | import { Context, ContextIsm } from '../shared/context'; 3 | import { deployContract, executeMultiMsg } from '../shared/contract'; 4 | 5 | const deployRoutingIsm = async ( 6 | ctx: Context, 7 | client: Client, 8 | ism: Extract, 9 | ) => { 10 | const routes = []; 11 | for (const [domain, v] of Object.entries(ism.isms)) { 12 | routes.push({ 13 | domain: parseInt(domain), 14 | route: await deployIsm(ctx, client, v), 15 | }); 16 | } 17 | 18 | const routing = await deployContract(ctx, client, 'hpl_ism_routing', { 19 | owner: ism.owner === '' ? client.signer : ism.owner, 20 | isms: routes.map(({ domain, route }) => ({ 21 | domain, 22 | address: route.address, 23 | })), 24 | }); 25 | 26 | return { 27 | ...routing, 28 | isms: routes.reduce((acc, v) => ({ [v.domain]: v.route, ...acc })), 29 | }; 30 | }; 31 | 32 | export async function deployIsm( 33 | ctx: Context, 34 | client: Client, 35 | ism: Exclude, 36 | ): Promise { 37 | switch (ism.type) { 38 | // deploy multisig ism 39 | case 'multisig': { 40 | const multisig = await deployContract(ctx, client, 'hpl_ism_multisig', { 41 | owner: ism.owner === '' ? client.signer : ism.owner, 42 | }); 43 | 44 | await executeMultiMsg( 45 | client, 46 | Object.entries(ism.validators).map( 47 | ([domain, { addrs, threshold }]) => ({ 48 | contract: multisig, 49 | msg: { 50 | set_validators: { 51 | domain: Number(domain), 52 | threshold, 53 | validators: addrs.map((v) => 54 | v === '' ? client.signer_addr : v, 55 | ), 56 | }, 57 | }, 58 | }), 59 | ), 60 | ); 61 | 62 | return multisig; 63 | } 64 | 65 | // deploy aggregate ism 66 | case 'aggregate': { 67 | const aggr = []; 68 | for (const v of ism.isms) { 69 | aggr.push(await deployIsm(ctx, client, v)); 70 | } 71 | 72 | const aggregate = await deployContract(ctx, client, 'hpl_ism_aggregate', { 73 | owner: ism.owner === '' ? client.signer : ism.owner, 74 | isms: aggr.map((v) => v.address), 75 | }); 76 | 77 | return { ...aggregate, isms: aggr }; 78 | } 79 | 80 | // deploy routing ism 81 | case 'routing': { 82 | return deployRoutingIsm(ctx, client, ism); 83 | } 84 | 85 | default: { 86 | throw new Error('invalid ism type'); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /script/deploy/warp.ts: -------------------------------------------------------------------------------- 1 | import { deployContract } from '../shared/contract'; 2 | import { Dependencies } from '../shared/ioc'; 3 | import { orSigner } from '../shared/utils'; 4 | 5 | type NativeTokenBridged = { 6 | denom: string; 7 | metadata?: { 8 | description: string; 9 | denom_units: { 10 | denom: string; 11 | exponent: string; 12 | aliases: string[]; 13 | }[]; 14 | base: string; 15 | display: string; 16 | name: string; 17 | symbol: string; 18 | }; 19 | }; 20 | 21 | type NativeTokenCollateral = { 22 | denom: string; 23 | }; 24 | 25 | type Cw20TokenBridged = { 26 | code_id: number; 27 | init_msg: { 28 | name: string; 29 | symbol: string; 30 | decimals: number; 31 | initial_balances: { address: string; amount: string }[]; 32 | mint?: { minter: string; cap?: string }; 33 | marketing?: { 34 | project?: string; 35 | description?: string; 36 | marketing?: string; 37 | logo?: { url: string } & { 38 | embedded: { svg: string } & { png: string }; 39 | }; 40 | }; 41 | }; 42 | }; 43 | 44 | type Cw20TokenCollateral = { 45 | address: string; 46 | }; 47 | 48 | type WarpTokenConfigMap = { 49 | native: { 50 | bridged: { bridged: NativeTokenBridged }; 51 | collateral: { collateral: NativeTokenCollateral }; 52 | }; 53 | cw20: { 54 | bridged: { bridged: Cw20TokenBridged }; 55 | collateral: { collateral: Cw20TokenCollateral }; 56 | }; 57 | }; 58 | 59 | export type WarpTokenConfig< 60 | TokenType extends 'native' | 'cw20' = 'native' | 'cw20', 61 | TokenMode extends 'bridged' | 'collateral' = 'bridged' | 'collateral', 62 | OwnerType extends string | '' = '', 63 | > = { 64 | type: TokenType; 65 | mode: TokenMode; 66 | 67 | id: string; 68 | owner: OwnerType; 69 | config: WarpTokenConfigMap[TokenType][TokenMode]; 70 | }; 71 | 72 | export async function deployNativeTokenWarp( 73 | { ctx, client, network }: Dependencies, 74 | mailbox: string, 75 | config: WarpTokenConfig<'native'>, 76 | ): Promise< 77 | { type: 'hpl_warp_native'; address: string; hexed: string } | undefined 78 | > { 79 | const deployments = ctx.deployments; 80 | 81 | const preload = deployments?.warp?.native?.find((v) => v.id === config.id); 82 | if (preload) { 83 | console.error( 84 | '[error]'.red, 85 | `warp route ${preload.id} already exists.`, 86 | `type: ${preload.type},`, 87 | `addr: ${preload.address}`, 88 | ); 89 | return; 90 | } 91 | 92 | const nativeWarp = await deployContract(ctx, client, 'hpl_warp_native', { 93 | token: config.config, 94 | 95 | hrp: network.hrp, 96 | owner: orSigner(client, config.owner), 97 | mailbox, 98 | }); 99 | 100 | return nativeWarp; 101 | } 102 | 103 | export async function deployCw20TokenWarp( 104 | { ctx, client, network }: Dependencies, 105 | mailbox: string, 106 | config: WarpTokenConfig<'cw20'>, 107 | ): Promise< 108 | { type: 'hpl_warp_cw20'; address: string; hexed: string } | undefined 109 | > { 110 | const deployments = ctx.deployments; 111 | 112 | const preload = deployments?.warp?.cw20?.find((v) => v.id === config.id); 113 | if (preload) { 114 | console.error( 115 | '[error]'.red, 116 | `warp route ${preload.id} already exists.`, 117 | `type: ${preload.type},`, 118 | `addr: ${preload.address}`, 119 | ); 120 | return; 121 | } 122 | 123 | const cw20Warp = await deployContract(ctx, client, 'hpl_warp_cw20', { 124 | token: config.config, 125 | 126 | hrp: network.hrp, 127 | owner: orSigner(client, config.owner), 128 | mailbox, 129 | }); 130 | 131 | return cw20Warp; 132 | } 133 | -------------------------------------------------------------------------------- /script/index.ts: -------------------------------------------------------------------------------- 1 | import colors from 'colors'; 2 | import { Command, Option } from 'commander'; 3 | import 'reflect-metadata'; 4 | 5 | import { version } from '../package.json'; 6 | import { 7 | contextCmd, 8 | contractCmd, 9 | deployCmd, 10 | migrateCmd, 11 | uploadCmd, 12 | walletCmd, 13 | warpCmd, 14 | } from './commands'; 15 | import { config, getNetwork, getSigningClient } from './shared/config'; 16 | import { loadContext } from './shared/context'; 17 | import { CONTAINER, Dependencies } from './shared/ioc'; 18 | 19 | colors.enable(); 20 | 21 | const optNetworkId = new Option( 22 | '-n, --network-id ', 23 | 'specify network id', 24 | ) 25 | .choices(config.networks.map((v) => v.id)) 26 | .makeOptionMandatory(); 27 | 28 | const cli = new Command(); 29 | 30 | cli 31 | .name('cw-hpl') 32 | .version(version) 33 | .description('CLI toolkit for CosmWasm Hyperlane') 34 | .addOption(optNetworkId) 35 | .hook('preAction', injectDependencies); 36 | 37 | cli.addCommand(contextCmd); 38 | cli.addCommand(contractCmd); 39 | cli.addCommand(deployCmd); 40 | cli.addCommand(migrateCmd); 41 | cli.addCommand(uploadCmd); 42 | cli.addCommand(walletCmd); 43 | cli.addCommand(warpCmd); 44 | 45 | cli.parseAsync(process.argv).catch(console.error); 46 | 47 | async function injectDependencies(cmd: Command): Promise { 48 | const { networkId } = cmd.optsWithGlobals(); 49 | 50 | const client = await getSigningClient(networkId, config); 51 | const ctx = loadContext(networkId); 52 | const network = getNetwork(networkId); 53 | 54 | const deps = { ctx, client, network }; 55 | 56 | CONTAINER.bind(Dependencies).toConstantValue(deps); 57 | } 58 | -------------------------------------------------------------------------------- /script/shared/constants.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | export const defaultTmpDir = path.join(process.cwd(), './tmp'); 4 | export const defaultContextPath = path.join(process.cwd(), './context'); 5 | export const defaultArtifactPath = path.join(process.cwd(), './artifacts'); 6 | 7 | export const contractNames = [ 8 | 'hpl_mailbox', 9 | 'hpl_validator_announce', 10 | 'hpl_ism_aggregate', 11 | 'hpl_ism_multisig', 12 | 'hpl_ism_pausable', 13 | 'hpl_ism_routing', 14 | 'hpl_igp', 15 | 'hpl_hook_aggregate', 16 | 'hpl_hook_fee', 17 | 'hpl_hook_merkle', 18 | 'hpl_hook_pausable', 19 | 'hpl_hook_routing', 20 | 'hpl_hook_routing_custom', 21 | 'hpl_hook_routing_fallback', 22 | 'hpl_test_mock_hook', 23 | 'hpl_test_mock_ism', 24 | 'hpl_test_mock_msg_receiver', 25 | 'hpl_igp_oracle', 26 | 'hpl_warp_cw20', 27 | 'hpl_warp_native', 28 | ]; 29 | 30 | export const REMOTE_REPO_NAME = 'many-things/cw-hyperlane'; 31 | export const REMOTE_MIN_VERSION = 'v0.0.6-rc8'; 32 | 33 | export const RELEASE_API_URL = `https://api.github.com/repos/${REMOTE_REPO_NAME}/releases`; 34 | export const RELEASE_ARTIFACT_URL = (tag: string) => 35 | `https://github.com/${REMOTE_REPO_NAME}/releases/download/${tag}/cw-hyperlane-${tag}.zip`; 36 | -------------------------------------------------------------------------------- /script/shared/context.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | import { defaultContextPath } from './constants'; 5 | import { ContractNames } from './contract'; 6 | 7 | type typed = { 8 | type: T; 9 | address: string; 10 | hexed: string; 11 | }; 12 | 13 | export type ContextIsm = 14 | | (typed<'hpl_ism_aggregate'> & { 15 | isms: ContextIsm[]; 16 | }) 17 | | typed<'hpl_ism_multisig'> 18 | | typed<'hpl_ism_pausable'> 19 | | (typed<'hpl_ism_routing'> & { 20 | isms: Record; 21 | }) 22 | | typed<'hpl_test_mock_ism'>; 23 | 24 | export type ContextHook = 25 | | (typed<'hpl_igp'> & { oracle: typed<'hpl_igp_oracle'> }) 26 | | (typed<'hpl_hook_aggregate'> & { 27 | hooks: ContextHook[]; 28 | }) 29 | | typed<'hpl_hook_fee'> 30 | | typed<'hpl_hook_merkle'> 31 | | typed<'hpl_hook_pausable'> 32 | | (typed<'hpl_hook_routing'> & { 33 | hooks: Record; 34 | }) 35 | | (typed<'hpl_hook_routing_custom'> & { 36 | hooks: Record>; 37 | }) 38 | | (typed<'hpl_hook_routing_fallback'> & { 39 | hooks: Record; 40 | }) 41 | | typed<'hpl_test_mock_hook'>; 42 | 43 | export type ContextDeployments = { 44 | core?: { 45 | mailbox?: typed<'hpl_mailbox'>; 46 | validator_announce?: typed<'hpl_validator_announce'>; 47 | }; 48 | 49 | isms?: ContextIsm; 50 | 51 | hooks?: { 52 | default?: ContextHook; 53 | required?: ContextHook; 54 | }; 55 | 56 | warp?: { 57 | cw20?: ({ id: string } & typed<'hpl_warp_cw20'>)[]; 58 | native?: ({ id: string } & typed<'hpl_warp_native'>)[]; 59 | }; 60 | 61 | test?: { 62 | msg_receiver?: typed<'hpl_test_mock_msg_receiver'>; 63 | }; 64 | }; 65 | 66 | export class Context { 67 | artifacts: Record; 68 | deployments: ContextDeployments; 69 | 70 | latestMigration?: string; 71 | } 72 | 73 | export function loadContext( 74 | network: string, 75 | { contextPath }: { contextPath: string } = { 76 | contextPath: defaultContextPath, 77 | }, 78 | ): Context { 79 | try { 80 | const fileName = path.join(contextPath, `${network}.json`); 81 | const result = fs.readFileSync(fileName, 'utf-8'); 82 | return JSON.parse(result.toString()) as Context; 83 | } catch (err) { 84 | console.error('Failed to load context. Returning an empty context object.'); 85 | } 86 | 87 | return { 88 | artifacts: {}, 89 | deployments: {}, 90 | }; 91 | } 92 | 93 | export function saveContext( 94 | network: string, 95 | context: Context, 96 | { contextPath }: { contextPath: string } = { 97 | contextPath: defaultContextPath, 98 | }, 99 | ) { 100 | fs.mkdirSync(contextPath, { recursive: true }); 101 | const fileName = path.join(contextPath, `${network}.json`); 102 | fs.writeFileSync(fileName, JSON.stringify(context, null, 2)); 103 | } 104 | -------------------------------------------------------------------------------- /script/shared/crypto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Bip39, 3 | EnglishMnemonic, 4 | HdPath, 5 | Secp256k1, 6 | Slip10, 7 | Slip10Curve, 8 | } from '@cosmjs/crypto'; 9 | 10 | export const getKeyPair = async ( 11 | mnemonic: string, 12 | hdPath: HdPath, 13 | password?: string, 14 | ) => { 15 | const { privkey } = Slip10.derivePath( 16 | Slip10Curve.Secp256k1, 17 | await Bip39.mnemonicToSeed(new EnglishMnemonic(mnemonic), password), 18 | hdPath, 19 | ); 20 | const { pubkey } = await Secp256k1.makeKeypair(privkey); 21 | return { privkey, pubkey: Secp256k1.compressPubkey(pubkey) }; 22 | }; 23 | -------------------------------------------------------------------------------- /script/shared/github.ts: -------------------------------------------------------------------------------- 1 | import decompress from 'decompress'; 2 | import * as fs from 'fs'; 3 | import path from 'path'; 4 | 5 | import { 6 | RELEASE_API_URL, 7 | RELEASE_ARTIFACT_URL, 8 | REMOTE_MIN_VERSION, 9 | } from './constants'; 10 | import { downloadFile } from './utils'; 11 | 12 | interface ReleaseApiResp { 13 | tag_name: string; 14 | } 15 | 16 | export const getReleases = async (): Promise> => { 17 | const apiResp = await fetch(RELEASE_API_URL); 18 | const releases = (await apiResp.json()) as ReleaseApiResp[]; 19 | 20 | return Object.fromEntries( 21 | releases 22 | .map((v) => v.tag_name) 23 | .filter((v) => v >= REMOTE_MIN_VERSION) 24 | .map((v) => [v, RELEASE_ARTIFACT_URL(v)]), 25 | ); 26 | }; 27 | 28 | export const downloadReleases = async ( 29 | codeUrl: string, 30 | outPath: string, 31 | ): Promise => { 32 | const codeFile = path.join(outPath, 'codes.zip'); 33 | const unzipPath = path.join(outPath, 'codes'); 34 | 35 | if (fs.existsSync(codeFile)) fs.rmSync(codeFile); 36 | if (fs.existsSync(unzipPath)) fs.rmSync(unzipPath, { recursive: true }); 37 | 38 | await downloadFile(codeUrl, codeFile); 39 | await decompress(codeFile, unzipPath); 40 | 41 | return unzipPath; 42 | }; 43 | -------------------------------------------------------------------------------- /script/shared/ioc.ts: -------------------------------------------------------------------------------- 1 | import { Container } from 'inversify'; 2 | 3 | import { Client, Config } from './config'; 4 | import { Context } from './context'; 5 | 6 | export const CONTAINER = new Container({ 7 | autoBindInjectable: true, 8 | defaultScope: 'Singleton', 9 | }); 10 | 11 | // referenced by tsoa 12 | export const iocContainer = CONTAINER; 13 | 14 | export class Dependencies { 15 | ctx: Context; 16 | client: Client; 17 | network: Config['networks'][number]; 18 | } 19 | -------------------------------------------------------------------------------- /script/shared/logger.ts: -------------------------------------------------------------------------------- 1 | export class Logger { 2 | constructor(public name: string) {} 3 | 4 | log(...args: unknown[]) { 5 | console.log(`[${this.name}]`, ...args); 6 | } 7 | 8 | debug = (...args: unknown[]) => 9 | console.log('DEBUG]'.grey, `[${this.name}]`, ...args); 10 | 11 | info = (...args: unknown[]) => 12 | console.log(' INFO]'.cyan, `[${this.name}]`, ...args); 13 | 14 | error = (...args: unknown[]) => 15 | console.error('ERROR]'.red, `[${this.name}]`, ...args); 16 | } 17 | -------------------------------------------------------------------------------- /script/shared/utils.ts: -------------------------------------------------------------------------------- 1 | import { fromBech32 } from '@cosmjs/encoding'; 2 | import { IndexedTx, StargateClient } from '@cosmjs/stargate'; 3 | import { createHash } from 'crypto'; 4 | import * as fs from 'fs'; 5 | import * as readline from 'readline'; 6 | import { Readable } from 'stream'; 7 | import { finished } from 'stream/promises'; 8 | 9 | import { Client } from './config'; 10 | 11 | export const sleep = (ms: number) => 12 | new Promise((resolve) => setTimeout(resolve, ms)); 13 | 14 | export function askQuestion(query: string) { 15 | const rl = readline.createInterface({ 16 | input: process.stdin, 17 | output: process.stdout, 18 | }); 19 | 20 | return new Promise((resolve) => 21 | rl.question(`${query} [Y/n] `, (ans) => { 22 | rl.close(); 23 | resolve(ans.toLowerCase() == 'y' ? true : false); 24 | }), 25 | ); 26 | } 27 | 28 | export const addPad = (v: string): string => { 29 | const s = v.startsWith('0x') ? v.slice(2) : v; 30 | return s.padStart(64, '0'); 31 | }; 32 | 33 | export const withLink = (text: string, url: string) => 34 | `${text} (\u200B${url}\u200B)`; 35 | 36 | export const extractByte32AddrFromBech32 = (addr: string): string => { 37 | const { data } = fromBech32(addr); 38 | const hexed = Buffer.from(data).toString('hex'); 39 | return `0x${hexed.length === 64 ? hexed : addPad(hexed)}`; 40 | }; 41 | 42 | export const downloadFile = async (url: string, dest: string) => { 43 | const res = await fetch(url); 44 | const fileStream = fs.createWriteStream(dest, { flags: 'wx' }); 45 | await finished(Readable.fromWeb(res.body!).pipe(fileStream)); 46 | }; 47 | 48 | export const generateSha256 = (file: string): Promise<[string, string]> => 49 | new Promise((resolve, reject) => { 50 | const stream = fs.createReadStream(file); 51 | const hash = createHash('sha256'); 52 | 53 | stream.on('error', (err) => reject(err)); 54 | stream.on('data', (chunk) => hash.update(chunk)); 55 | stream.on('end', () => resolve([file, hash.digest('hex')])); 56 | }); 57 | 58 | export const waitTx = async ( 59 | txHash: string, 60 | client: StargateClient, 61 | { waitMs, tryCount }: { waitMs: number; tryCount: number } = { 62 | waitMs: 1000, 63 | tryCount: 30, 64 | }, 65 | ): Promise => { 66 | let found: IndexedTx | null = null; 67 | let count = 0; 68 | while (!found) { 69 | found = await client.getTx(txHash); 70 | count++; 71 | await sleep(waitMs); // default to 1s 72 | 73 | if (count > tryCount) { 74 | throw new Error( 75 | `max try count exceeded. count: ${tryCount}, waitMs: ${waitMs}`, 76 | ); 77 | } 78 | } 79 | return found; 80 | }; 81 | 82 | export function orSigner' = ''>( 83 | client: Client, 84 | v: SignerType, 85 | ): string { 86 | return v === '' ? client.signer : v; 87 | } 88 | -------------------------------------------------------------------------------- /script/shared/wasm.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | import { Config } from './config'; 5 | import { defaultArtifactPath } from './constants'; 6 | import { generateSha256 } from './utils'; 7 | 8 | function getWasmFilesPath( 9 | { artifactPath }: { artifactPath: string } = { 10 | artifactPath: defaultArtifactPath, 11 | }, 12 | ): string[] { 13 | try { 14 | const files = fs.readdirSync(artifactPath); 15 | return files 16 | .filter((file) => file.endsWith('.wasm')) 17 | .map((file) => path.join(artifactPath, file)); 18 | } catch (err) { 19 | console.error( 20 | '[error]'.red, 21 | 'cannot find wasm folder.', 22 | 'did you compiled the wasm projects?', 23 | ); 24 | process.exit(1); 25 | } 26 | } 27 | 28 | export async function loadWasmFileDigest( 29 | { artifactPath }: { artifactPath: string } = { 30 | artifactPath: defaultArtifactPath, 31 | }, 32 | ): Promise> { 33 | return Object.fromEntries( 34 | await Promise.all(getWasmFilesPath({ artifactPath }).map(generateSha256)), 35 | ); 36 | } 37 | 38 | export function getWasmPath( 39 | contractName: string, 40 | { artifactPath }: { artifactPath: string } = { 41 | artifactPath: defaultArtifactPath, 42 | }, 43 | ): string { 44 | return path.join(artifactPath, `${contractName}.wasm`); 45 | } 46 | 47 | export type ContractInfoResp = { 48 | address: string; 49 | contract_info: { 50 | code_id: string; 51 | creator: string; 52 | admin?: string; 53 | label: string; 54 | created: { 55 | block_height: string; 56 | tx_index: string; 57 | }; 58 | ibc_por_id?: string; 59 | extension?: object; 60 | }; 61 | }; 62 | 63 | export async function getContractInfo( 64 | network: Config['networks'][number], 65 | addr: string, 66 | ): Promise { 67 | try { 68 | const res = await fetch( 69 | path.join(network.endpoint.rest, '/cosmwasm/wasm/v1/contract/', addr), 70 | ); 71 | const body = await res.json(); 72 | 73 | return body as ContractInfoResp; 74 | } catch (err) { 75 | console.error('Error fetching contract info', err); 76 | return undefined; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "types": ["reflect-metadata", "node"], 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "strict": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "resolveJsonModule": true, 15 | "strictPropertyInitialization": false, 16 | "outDir": "dist", 17 | "declaration": true /* Skip type checking all .d.ts files. */ 18 | }, 19 | "include": ["script/**/*.ts", "example/**/*.ts"] 20 | } 21 | --------------------------------------------------------------------------------