├── .drs-version ├── .githook └── pre-push ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── build.yml │ ├── build_and_push_images.yml │ ├── build_image_with_dymint.yml │ ├── changelog.yml │ ├── codeql.yml │ ├── e2e-tests.yml │ ├── e2e_test_upgrade.yml │ ├── generate_genesis_template.yml │ ├── golangci_lint.yml │ ├── markdown_lint.yml │ ├── new-pr-notification.yaml │ ├── release_binary.yaml │ ├── stale-pr-notification.yaml │ ├── stalebot.yml │ └── test.yml ├── .gitignore ├── .goreleaser.yml ├── .markdownlint.yaml ├── .markdownlintignore ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── app ├── ante.go ├── app.go ├── app_test.go ├── apptesting │ └── apptesting.go ├── create_account_decorator.go ├── encoding.go ├── export.go ├── genesis.go ├── params │ ├── encoding.go │ └── proto.go ├── sigcheck_decorator.go ├── test_app.go ├── test_helpers.go ├── upgrades │ ├── drs-2 │ │ ├── constants.go │ │ └── upgrade.go │ ├── drs-3 │ │ ├── constants.go │ │ └── upgrade.go │ ├── drs-4 │ │ ├── constants.go │ │ └── upgrade.go │ ├── drs-5 │ │ ├── constants.go │ │ └── upgrade.go │ ├── drs-6 │ │ ├── constants.go │ │ └── upgrade.go │ ├── drs-7 │ │ ├── constants.go │ │ └── upgrade.go │ ├── drs-8 │ │ ├── constants.go │ │ └── upgrade.go │ ├── drs-9 │ │ ├── constants.go │ │ ├── doc.go │ │ ├── upgrade.go │ │ └── upgrade_test.go │ └── types.go └── wasm.go ├── contract.wasm ├── contracts └── callback-test │ ├── .editorconfig │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── artifacts │ ├── callback_test.wasm │ └── checksums.txt │ └── src │ ├── bin │ └── schema.rs │ ├── contract.rs │ ├── error.rs │ ├── lib.rs │ ├── msg.rs │ └── state.rs ├── e2e └── testing │ ├── chain.go │ ├── chain_ops.go │ ├── chain_ops_contract.go │ ├── chain_ops_ibc.go │ ├── chain_options.go │ ├── client.go │ ├── common.go │ ├── ibc_path.go │ └── types.go ├── genesis-templates └── DRS │ ├── 1 │ ├── genesis-mainnet.json │ └── genesis-testnet.json │ ├── 2 │ ├── genesis-mainnet.json │ └── genesis-testnet.json │ ├── 3 │ ├── genesis-mainnet.json │ └── genesis-testnet.json │ ├── 4 │ ├── genesis-mainnet.json │ └── genesis-testnet.json │ ├── 5 │ ├── genesis-mainnet.json │ └── genesis-testnet.json │ ├── 6 │ ├── genesis-mainnet.json │ └── genesis-testnet.json │ ├── 7 │ ├── genesis-mainnet.json │ └── genesis-testnet.json │ ├── 8 │ ├── genesis-mainnet.json │ └── genesis-testnet.json │ └── 9 │ ├── genesis-mainnet.json │ └── genesis-testnet.json ├── go.mod ├── go.sum ├── internal └── collcompat │ └── collcompat.go ├── pkg ├── basic_types.go ├── cli_args.go ├── cli_flags.go ├── coin.go ├── coins.go ├── coins_test.go ├── dec.go ├── openapiconsole │ ├── console.go │ └── index.tpl ├── testutils │ ├── ante.go │ ├── msg.go │ ├── tx.go │ └── wasmd.go └── utils.go ├── proto ├── buf.gen.gogo.yaml ├── buf.lock ├── buf.yaml └── rollapp │ ├── callback │ └── v1 │ │ ├── callback.proto │ │ ├── errors.proto │ │ ├── events.proto │ │ ├── genesis.proto │ │ ├── query.proto │ │ └── tx.proto │ ├── cwerrors │ └── v1 │ │ ├── cwerrors.proto │ │ ├── events.proto │ │ ├── genesis.proto │ │ ├── params.proto │ │ ├── query.proto │ │ └── tx.proto │ └── wasm │ └── authz.proto ├── ra_wasm_be_rpc ├── backend │ ├── backend.go │ ├── rollapp_wasm_interceptor.go │ └── sequencers.go ├── namespaces │ └── raw │ │ └── api.go └── types │ └── query_client.go ├── rollappd ├── cmd │ ├── genaccounts.go │ ├── root.go │ └── start.go └── main.go ├── roller.Dockerfile ├── scripts ├── add_vesting_accounts_to_genesis_file.sh ├── bytecode │ ├── cw20_base.wasm │ └── cw20_ics20.wasm ├── config.sh ├── download_release.sh ├── generate-genesis-template.sh ├── ibc │ ├── hub.json │ ├── rollapp.json │ └── setup_ibc.sh ├── init.sh ├── protogen.sh ├── settlement │ ├── add_genesis_accounts.sh │ ├── generate_denom_metadata.sh │ ├── register_rollapp_to_hub.sh │ └── register_sequencer_to_hub.sh ├── setup-git-hooks.sh ├── update_genesis_file.sh ├── version.txt └── wasm │ ├── deploy_contract.sh │ └── ibc_transfer.sh └── x ├── README.md ├── callback ├── abci.go ├── abci_test.go ├── client │ └── cli │ │ ├── query.go │ │ └── tx.go ├── genesis.go ├── genesis_test.go ├── keeper │ ├── callback.go │ ├── callback_test.go │ ├── fees.go │ ├── grpc_query.go │ ├── grpc_query_test.go │ ├── keeper.go │ ├── keeper_test.go │ ├── msg_server.go │ ├── msg_server_test.go │ └── params.go ├── module.go ├── spec │ ├── 01_state.md │ ├── 02_messages.md │ ├── 03_end_block.md │ ├── 04_events.md │ ├── 05_client.md │ ├── 06_wasm_bindings.md │ ├── 07_errors.md │ └── README.md └── types │ ├── callback.go │ ├── callback.pb.go │ ├── callback_test.go │ ├── codec.go │ ├── errors.go │ ├── errors.pb.go │ ├── events.go │ ├── events.pb.go │ ├── expected_keepers.go │ ├── genesis.go │ ├── genesis.pb.go │ ├── genesis_test.go │ ├── keys.go │ ├── msg.go │ ├── params.go │ ├── params_test.go │ ├── query.pb.go │ ├── query.pb.gw.go │ ├── sudo_msg.go │ ├── sudo_msg_test.go │ ├── tx.pb.go │ └── types.go ├── cwerrors ├── abci.go ├── abci_test.go ├── client │ └── cli │ │ ├── query.go │ │ └── tx.go ├── genesis.go ├── genesis_test.go ├── keeper │ ├── grpc_query.go │ ├── grpc_query_test.go │ ├── keeper.go │ ├── keeper_test.go │ ├── msg_server.go │ ├── msg_server_test.go │ ├── params.go │ ├── subscriptions.go │ ├── subscriptions_test.go │ ├── sudo_errors.go │ └── sudo_errors_test.go ├── module.go ├── spec │ ├── 01_state.md │ ├── 02_messages.md │ ├── 03_end_block.md │ ├── 04_events.md │ ├── 05_client.md │ ├── 06_errors.md │ └── README.md └── types │ ├── codec.go │ ├── cwerrors.pb.go │ ├── errors.go │ ├── events.go │ ├── events.pb.go │ ├── expected_keepers.go │ ├── genesis.go │ ├── genesis.pb.go │ ├── genesis_test.go │ ├── keys.go │ ├── msg.go │ ├── params.go │ ├── params.pb.go │ ├── params_test.go │ ├── query.pb.go │ ├── query.pb.gw.go │ ├── sudo_errors_test.go │ ├── sudo_msg.go │ ├── tx.pb.go │ ├── types.go │ └── types_test.go └── wasm ├── authz.go ├── authz.pb.go └── authz_test.go /.drs-version: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /.githook/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # pre-push hook script 3 | 4 | # Ensure golangci-lint is installed 5 | if ! command -v golangci-lint >/dev/null 2>&1; then 6 | echo "golangci-lint is not installed. Please install it with 'go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.58.0'" 7 | exit 1 8 | fi 9 | 10 | # Ensure markdownlint is installed 11 | if ! command -v markdownlint >/dev/null 2>&1; then 12 | echo "markdownlint-cli is not installed. Please install it with 'npm install -g markdownlint-cli'" 13 | exit 1 14 | fi 15 | 16 | # Run golangci-lint 17 | golangci-lint run 18 | 19 | # Capture the exit status of golangci-lint 20 | RESULT=$? 21 | if [ $RESULT -ne 0 ]; then 22 | echo "golangci-lint checks failed. Aborting push." 23 | exit 1 24 | fi 25 | 26 | # Run markdownlint 27 | markdownlint . --config .markdownlint.yaml 28 | 29 | # Capture the exit status of markdownlint 30 | RESULT=$? 31 | if [ $RESULT -ne 0 ]; then 32 | echo "markdownlint checks failed. Aborting push." 33 | exit 1 34 | fi 35 | 36 | # If all tests pass, allow the push to proceed 37 | exit 0 38 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS: https://help.github.com/articles/about-codeowners/ 2 | 3 | # Everything goes through the following "global owners" by default. 4 | # Unless a later match takes precedence, these three will be 5 | # requested for review when someone opens a PR. 6 | # Note that the last matching pattern takes precedence, so 7 | # global owners are only requested if there isn't a more specific 8 | # codeowner specified below. For this reason, the global codeowners 9 | # are often repeated in package-level definitions. 10 | * @dymensionxyz/core-dev 11 | * @dymensionxyz/decentrio 12 | 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 6 | 7 | ---- 8 | 9 | Closes #XXX 10 | 11 | **All** items are required. Please add a note to the item if the item is not applicable and 12 | please add links to any relevant follow-up issues. 13 | 14 | PR review checkboxes: 15 | 16 | I have... 17 | 18 | - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` 19 | - [ ] Targeted PR against the correct branch 20 | - [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title 21 | - [ ] Linked to the GitHub issue with discussion and accepted design 22 | - [ ] Targets only one GitHub issue 23 | - [ ] Wrote unit and integration tests 24 | - [ ] Wrote relevant migration scripts if necessary 25 | - [ ] All CI checks have passed 26 | - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code) 27 | - [ ] Updated the scripts for local run, e.g genesis_config_commands.sh if the PR changes parameters 28 | - [ ] Add an issue in the [e2e-tests repo](https://github.com/dymensionxyz/e2e-tests) if necessary 29 | 30 | SDK Checklist 31 | - [ ] Import/Export Genesis 32 | - [ ] Registered Invariants 33 | - [ ] Registered Events 34 | - [ ] Updated openapi.yaml 35 | - [ ] No usage of go `map` 36 | - [ ] No usage of `time.Now()` 37 | - [ ] Used fixed point arithmetic and not float arithmetic 38 | - [ ] Avoid panicking in Begin/End block as much as possible 39 | - [ ] No unexpected math Overflow 40 | - [ ] Used `sendCoin` and not `SendCoins` 41 | - [ ] Out-of-block compute is bounded 42 | - [ ] No serialized ID at the end of store keys 43 | - [ ] UInt to byte conversion should use BigEndian 44 | 45 | Full security checklist [here](https://www.faulttolerant.xyz/2024-01-16-cosmos-security-1/) 46 | 47 | 48 | ----; 49 | 50 | For Reviewer: 51 | 52 | - [ ] Confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title 53 | - [ ] Reviewers assigned 54 | - [ ] Confirmed all author checklist items have been addressed 55 | 56 | ---; 57 | 58 | After reviewer approval: 59 | 60 | - [ ] In case the PR targets the main branch, PR should not be squash merge in order to keep meaningful git history. 61 | - [ ] In case the PR targets a release branch, PR must be rebased. 62 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Rollapp-wasm 2 | on: ["push"] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | arch: [amd64] 9 | targetos: [darwin, linux] 10 | go-version: ['1.21.x'] 11 | include: 12 | - targetos: darwin 13 | arch: arm64 14 | name: rollapp ${{ matrix.arch }} for ${{ matrix.targetos }} 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-go@v4.1.0 18 | with: 19 | go-version: ${{ matrix.go-version }} 20 | env: 21 | GOOS: ${{ matrix.targetos }} 22 | GOARCH: ${{ matrix.arch }} 23 | 24 | - name: Compile rollapp-wasm 25 | run: | 26 | go mod download 27 | cd rollappd 28 | go build . 29 | 30 | - uses: actions/upload-artifact@v4 31 | with: 32 | name: rollappd ${{ matrix.targetos }} ${{ matrix.arch }} 33 | path: rollappd/rollappd 34 | -------------------------------------------------------------------------------- /.github/workflows/build_and_push_images.yml: -------------------------------------------------------------------------------- 1 | # This workflow pushes new rollapp-wasm images on every new commit on main. 2 | # 3 | # ghcr.io/dymensionxyz/rollapp-wasm:latest 4 | # ghcr.io/dymensionxyz/rollapp-wasm:{SHORT_SHA} 5 | 6 | name: Push docker images 7 | on: 8 | push: 9 | branches: 10 | - main 11 | 12 | permissions: 13 | contents: read 14 | packages: write 15 | 16 | jobs: 17 | build-and-push: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - 21 | name: Check out the repo 22 | uses: actions/checkout@v3 23 | - 24 | name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v3 26 | - name: Login to GitHub Container Registry 27 | uses: docker/login-action@v3 28 | with: 29 | registry: ghcr.io 30 | username: ${{ github.repository_owner }} 31 | password: ${{ secrets.GITHUB_TOKEN }} 32 | - name: Create Docker Image Tag for vN.x branch 33 | run: | 34 | SHORT_SHA=$(echo ${GITHUB_SHA} | cut -c1-8) 35 | echo "DOCKER_IMAGE_TAG=${SHORT_SHA}" >> $GITHUB_ENV 36 | - 37 | name: Build and push 38 | id: build_push_image 39 | uses: docker/build-push-action@v3 40 | with: 41 | file: Dockerfile 42 | context: . 43 | push: true 44 | platforms: linux/amd64 45 | tags: | 46 | ghcr.io/dymensionxyz/rollapp-wasm:latest 47 | ghcr.io/dymensionxyz/rollapp-wasm:${{ env.DOCKER_IMAGE_TAG }} -------------------------------------------------------------------------------- /.github/workflows/build_image_with_dymint.yml: -------------------------------------------------------------------------------- 1 | name: Build RollApp with Dymint 2 | 3 | on: 4 | workflow_call: # Makes this workflow reusable 5 | inputs: 6 | commit_hash: 7 | description: "Commit hash for updating dymint version" 8 | required: true 9 | type: string 10 | 11 | jobs: 12 | build-wasm-image: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | repository: dymensionxyz/rollapp-wasm 19 | ref: main 20 | 21 | - name: Update dymint module version 22 | run: | 23 | echo "Updating dymint to version ${{ inputs.commit_hash }}" 24 | go mod edit -require github.com/dymensionxyz/dymint@${{ inputs.commit_hash }} 25 | go mod tidy 26 | 27 | - name: Set up Docker Buildx 28 | uses: docker/setup-buildx-action@v3 29 | 30 | - name: Build RollApp WASM Image 31 | run: | 32 | docker buildx build \ 33 | --file Dockerfile \ 34 | --platform linux/amd64 \ 35 | --output type=docker,dest=/tmp/rollapp-wasm.tar \ 36 | --tag ghcr.io/dymensionxyz/rollapp-wasm:e2e . 37 | 38 | - name: Upload WASM artifact 39 | uses: actions/upload-artifact@v4 40 | with: 41 | name: rollapp-wasm 42 | path: /tmp/rollapp-wasm.tar -------------------------------------------------------------------------------- /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | name: Auto Changelog Update 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | update_changelog: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout the repository 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 1 16 | 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: '18' 21 | 22 | - name: Configure Git 23 | run: | 24 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 25 | git config --global user.name "github-actions[bot]" 26 | 27 | - name: Check if "auto-changelog-update-do-not-create-manually" branch exists 28 | id: check_branch 29 | run: | 30 | if [ -n "$(git ls-remote --heads origin auto-changelog-update-do-not-create-manually)" ]; then 31 | git fetch origin auto-changelog-update-do-not-create-manually 32 | echo "branch_exists=true" >> $GITHUB_ENV 33 | else 34 | echo "branch_exists=false" >> $GITHUB_ENV 35 | fi 36 | 37 | - name: Generate Changelog Update and update if branch exists 38 | run: | 39 | npm install -g conventional-changelog-cli 40 | if [ "$branch_exists" == "true" ]; then 41 | git checkout auto-changelog-update-do-not-create-manually 42 | git merge main --strategy-option theirs --allow-unrelated-histories --no-edit 43 | conventional-changelog -p angular -i CHANGELOG.md -s 44 | git add CHANGELOG.md 45 | git commit -m "Update CHANGELOG.md [skip ci]" 46 | git push origin auto-changelog-update-do-not-create-manually 47 | exit 0 48 | else 49 | git checkout main 50 | git checkout -b auto-changelog-update-do-not-create-manually 51 | git push origin auto-changelog-update-do-not-create-manually 52 | conventional-changelog -p angular -i CHANGELOG.md -s 53 | fi 54 | 55 | - name: Create Pull Request 56 | if: env.branch_exists == 'false' 57 | id: cpr 58 | uses: peter-evans/create-pull-request@v6 59 | with: 60 | token: ${{ secrets.GITHUB_TOKEN }} 61 | commit-message: "Update CHANGELOG.md [skip ci]" 62 | title: "Chore(changelog): Automated Changelog Update [skip ci]" 63 | body: "Update the CHANGELOG.md with recent pushes to branch main." 64 | base: "main" 65 | branch: "auto-changelog-update-do-not-create-manually" 66 | delete-branch: true 67 | 68 | - name: Check outputs 69 | if: env.branch_exists == 'false' 70 | run: | 71 | echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" 72 | echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" 73 | 74 | - name: Log if PR updated 75 | if: steps.cpr.outputs.pull-request-operation == 'updated' 76 | run: | 77 | echo "Changelog PR updated due to new commit to main." -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "**.go" 7 | push: 8 | # The branches below must be a subset of the branches above 9 | branches: 10 | - main 11 | - release/** 12 | paths: 13 | - "**.go" 14 | 15 | jobs: 16 | analyze: 17 | name: Analyze 18 | runs-on: ubuntu-latest 19 | permissions: 20 | actions: read 21 | contents: read 22 | security-events: write 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v3 27 | - uses: actions/setup-go@v3 28 | with: 29 | go-version: "1.22" 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v2 33 | with: 34 | languages: "go" 35 | queries: crypto-com/cosmos-sdk-codeql@main,security-and-quality 36 | 37 | - name: Build 38 | run: make build BECH32_PREFIX=rol 39 | 40 | - name: Perform CodeQL Analysis 41 | uses: github/codeql-action/analyze@v2 42 | -------------------------------------------------------------------------------- /.github/workflows/e2e-tests.yml: -------------------------------------------------------------------------------- 1 | name: E2E Tests 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: read 8 | packages: write 9 | 10 | jobs: 11 | build-image: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up Docker Buildx 18 | uses: docker/setup-buildx-action@v3 19 | 20 | - name: Build and export 21 | uses: docker/build-push-action@v5 22 | with: 23 | file: Dockerfile 24 | context: . 25 | outputs: type=docker,dest=/tmp/e2e.tar 26 | platforms: linux/amd64 27 | tags: | 28 | ghcr.io/dymensionxyz/rollapp-wasm:e2e 29 | - name: Upload artifact 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: e2e 33 | path: /tmp/e2e.tar 34 | e2e-tests: 35 | needs: build-image 36 | uses: dymensionxyz/e2e-tests/.github/workflows/e2e-test-wasm-workflow-call.yaml@main 37 | with: 38 | rollapp_wasm_ci: "e2e" 39 | -------------------------------------------------------------------------------- /.github/workflows/e2e_test_upgrade.yml: -------------------------------------------------------------------------------- 1 | name: E2E Tests Upgrade 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: read 8 | packages: write 9 | 10 | jobs: 11 | build-image: 12 | runs-on: ubuntu-latest 13 | outputs: 14 | UPGRADE_NAME: ${{ steps.set-output.outputs.upgrade }} 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Set environment variable 20 | id: set-output 21 | run: | 22 | upgradeName=$(ls -d -- ./app/upgrades/v* | sort -Vr | head -n 1 | xargs basename) 23 | echo "upgrade=$upgradeName" >> $GITHUB_OUTPUT 24 | - name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v3 26 | 27 | - name: Build and export 28 | uses: docker/build-push-action@v5 29 | with: 30 | file: Dockerfile 31 | context: . 32 | outputs: type=docker,dest=/tmp/e2e.tar 33 | platforms: linux/amd64 34 | tags: | 35 | ghcr.io/dymensionxyz/dymension:e2e 36 | 37 | - name: Upload artifact 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: e2e 41 | path: /tmp/e2e.tar 42 | 43 | e2e-tests-upgrade: 44 | needs: build-image 45 | uses: dymensionxyz/e2e-tests/.github/workflows/e2e-test-rollapp-wasm-upgrade-workflow-call.yml@main 46 | with: 47 | dymension_ci: "e2e" 48 | upgrade_name: "${{ needs.build-image.outputs.UPGRADE_NAME }}" 49 | -------------------------------------------------------------------------------- /.github/workflows/generate_genesis_template.yml: -------------------------------------------------------------------------------- 1 | name: Generate Genesis Template 2 | on: ["push"] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | name: Generate Genesis Template 7 | steps: 8 | - uses: actions/checkout@v4 9 | - uses: actions/setup-go@v5 10 | with: 11 | go-version: "1.23.1" 12 | 13 | - name: Install Dasel 14 | run: | 15 | curl -sSLf https://github.com/TomWright/dasel/releases/latest/download/dasel_linux_amd64 -o /usr/local/bin/dasel 16 | chmod +x /usr/local/bin/dasel 17 | 18 | - name: Generate genesis template mainnet 19 | run: make generate-genesis env=mainnet 20 | env: 21 | BECH32_PREFIX: ethm 22 | 23 | - name: Generate genesis template testnet 24 | run: make generate-genesis env=testnet 25 | env: 26 | BECH32_PREFIX: ethm -------------------------------------------------------------------------------- /.github/workflows/golangci_lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | golangci: 12 | name: golangci-lint 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version: "1.23.1" 19 | - name: golangci-lint 20 | uses: golangci/golangci-lint-action@v6 21 | with: 22 | version: v1.62.2 23 | skip-cache: true 24 | args: --timeout 5m 25 | -------------------------------------------------------------------------------- /.github/workflows/markdown_lint.yml: -------------------------------------------------------------------------------- 1 | name: markdown-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | markdownlint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: markdownlint-cli 16 | uses: nosborn/github-action-markdown-cli@v3.2.0 17 | with: 18 | files: ./ 19 | config_file: .markdownlint.yaml -------------------------------------------------------------------------------- /.github/workflows/new-pr-notification.yaml: -------------------------------------------------------------------------------- 1 | name: new-pr-notification-to-slack 2 | on: 3 | pull_request: 4 | types: [opened] 5 | 6 | jobs: 7 | new-pr-notification: 8 | uses: dymensionxyz/common-workflows/.github/workflows/new-pr-notification.yaml@main 9 | secrets: 10 | WEBHOOK_URL: ${{ secrets.SLACK_NEW_PR_CHANNEL_WEBHOOK }} 11 | -------------------------------------------------------------------------------- /.github/workflows/release_binary.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release Binary 3 | 4 | on: 5 | release: 6 | types: [created] 7 | 8 | permissions: write-all 9 | 10 | # This workflow creates a release using goreleaser 11 | # via the 'make release' command. 12 | 13 | jobs: 14 | release: 15 | runs-on: ubuntu-latest 16 | environment: release 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | submodules: true 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@v5 24 | with: 25 | check-latest: true 26 | go-version-file: go.mod 27 | 28 | - name: Setup release environment 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | run: |- 32 | echo 'GITHUB_TOKEN=${{secrets.GITHUB_TOKEN}}' > .release-env 33 | 34 | - name: Extract TM_VERSION 35 | run: echo "TM_VERSION=$(go list -m github.com/tendermint/tendermint | sed 's:.* ::')" >> $GITHUB_ENV 36 | 37 | - name: Extract BECH32_PREFIX 38 | run: echo BECH32_PREFIX=mock >> $GITHUB_ENV 39 | 40 | - name: Release publish 41 | run: make release -------------------------------------------------------------------------------- /.github/workflows/stale-pr-notification.yaml: -------------------------------------------------------------------------------- 1 | name: stale-pr-notification-to-slack 2 | on: 3 | pull_request: 4 | types: [labeled] 5 | 6 | jobs: 7 | stale-pr-notification: 8 | if: github.event.label.name == 'Stale' 9 | uses: dymensionxyz/common-workflows/.github/workflows/stale-pr-notification.yaml@main 10 | secrets: 11 | WEBHOOK_URL: ${{ secrets.SLACK_STALE_PR_CHANNEL_WEBHOOK }} 12 | -------------------------------------------------------------------------------- /.github/workflows/stalebot.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale pull requests" 2 | on: 3 | schedule: 4 | - cron: "0 0 * * *" 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | stale: 11 | permissions: 12 | issues: write # for actions/stale to close stale issues 13 | pull-requests: write # for actions/stale to close stale PRs 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/stale@v9 17 | with: 18 | repo-token: ${{ secrets.GITHUB_TOKEN }} 19 | stale-pr-message: > 20 | This pull request has been automatically marked as stale because it 21 | has not had any recent activity. It will be closed if no further 22 | activity occurs. Thank you! 23 | days-before-stale: -1 24 | days-before-close: -1 25 | days-before-pr-stale: 8 26 | days-before-pr-close: 3 27 | exempt-pr-labels: "security, proposal, blocked" 28 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Unit Tests 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | env: 13 | GOPROXY: https://proxy.golang.org 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up Go 17 | uses: actions/setup-go@v5 18 | with: 19 | go-version: "1.23" 20 | - name: Test & Coverage 21 | run: | 22 | go install github.com/ory/go-acc@v0.2.6 23 | go-acc -o coverage.txt ./... -- -v --race 24 | - uses: codecov/codecov-action@v3 25 | with: 26 | file: ./coverage.txt 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | rollappd/rollappd 2 | scripts/ibc/*.json 3 | build/ 4 | release/ 5 | .idea/ 6 | .vscode/ 7 | .DS_Store 8 | .go-version 9 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | default: true 2 | MD010: 3 | code_blocks: false 4 | MD013: false 5 | MD024: true 6 | MD033: 7 | allowed_elements: ["img"] -------------------------------------------------------------------------------- /.markdownlintignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # (2024-05-20) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **app:** Fixed bech32 on account keeper to not be hardcoded ([#54](https://github.com/dymensionxyz/rollapp-wasm/issues/54)) ([883653a](https://github.com/dymensionxyz/rollapp-wasm/commit/883653af7053450af80719e1cfd93e8309ba7a7d)) 7 | * merge conflict ([#13](https://github.com/dymensionxyz/rollapp-wasm/issues/13)) ([2cc8431](https://github.com/dymensionxyz/rollapp-wasm/commit/2cc8431a3dc57a60efece2a485c7298c08d22ecb)) 8 | * updated IBC Keeper to use the sequencer keeper. ([#63](https://github.com/dymensionxyz/rollapp-wasm/issues/63)) ([6c4a2b6](https://github.com/dymensionxyz/rollapp-wasm/commit/6c4a2b674527476ad08e790dfd4b41ef18f086e3)) 9 | 10 | 11 | ### Features 12 | 13 | * add ethsecp256k1 as the default signing algo ([#80](https://github.com/dymensionxyz/rollapp-wasm/issues/80)) ([7362c6f](https://github.com/dymensionxyz/rollapp-wasm/commit/7362c6f89ba701d3103a5c25bbe45f01de0321f6)) 14 | * add hub genesis module ([#43](https://github.com/dymensionxyz/rollapp-wasm/issues/43)) ([73b3ceb](https://github.com/dymensionxyz/rollapp-wasm/commit/73b3cebef6c159494f0a4074ef5edb804b82bf0c)) 15 | * Add wasm module for rollapp-wasm ([#10](https://github.com/dymensionxyz/rollapp-wasm/issues/10)) ([9829d4a](https://github.com/dymensionxyz/rollapp-wasm/commit/9829d4a10b9f7928c98151b7295b20f0d54a8ad0)) 16 | * **app:** Add modules authz and feegrant ([#60](https://github.com/dymensionxyz/rollapp-wasm/issues/60)) ([a4451ea](https://github.com/dymensionxyz/rollapp-wasm/commit/a4451eaebd11eb49c89a40c239f6dd8593f201d1)) 17 | * **be:** integrate block explorer Json-RPC server ([#41](https://github.com/dymensionxyz/rollapp-wasm/issues/41)) ([51fd3e3](https://github.com/dymensionxyz/rollapp-wasm/commit/51fd3e36a0404d68325c64f79f65a15afc3be82a)) 18 | * **ci:** add auto update changelog workflow ([5bc7247](https://github.com/dymensionxyz/rollapp-wasm/commit/5bc7247f4ecd073f9410024a7ce0944c126b1aaa)) 19 | * **ci:** Add auto update changelog workflow ([#61](https://github.com/dymensionxyz/rollapp-wasm/issues/61)) ([ed9c6da](https://github.com/dymensionxyz/rollapp-wasm/commit/ed9c6da98f33a9842ae83007b46bc074f67d2152)) 20 | * **ci:** Add setup script and push hook ([#86](https://github.com/dymensionxyz/rollapp-wasm/issues/86)) ([d4dc3e4](https://github.com/dymensionxyz/rollapp-wasm/commit/d4dc3e4d73a72ab0e99cefc79c82eb0dcd79b187)) 21 | * set bech32 prefix without changing source code ([#68](https://github.com/dymensionxyz/rollapp-wasm/issues/68)) ([82c81a2](https://github.com/dymensionxyz/rollapp-wasm/commit/82c81a2e521669e2f0f48f34c9c8d56ed46d4196)) 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use Ubuntu as the base image 2 | FROM ubuntu:latest as go-builder 3 | 4 | # Install necessary dependencies 5 | RUN apt-get update && apt-get install -y \ 6 | wget make git \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | # Download and install Go 1.21 10 | RUN wget https://golang.org/dl/go1.21.4.linux-amd64.tar.gz && \ 11 | tar -xvf go1.21.4.linux-amd64.tar.gz && \ 12 | mv go /usr/local && \ 13 | rm go1.21.4.linux-amd64.tar.gz 14 | 15 | # Set Go environment variables 16 | ENV GOROOT=/usr/local/go 17 | ENV GOPATH=$HOME/go 18 | ENV PATH=$GOPATH/bin:$GOROOT/bin:$PATH 19 | 20 | RUN apt-get update -y 21 | RUN apt-get install build-essential -y 22 | # Set the working directory 23 | WORKDIR /app 24 | 25 | # Download go dependencies 26 | COPY go.mod go.sum ./ 27 | RUN --mount=type=cache,target=/root/.cache/go-build \ 28 | --mount=type=cache,target=/root/go/pkg/mod \ 29 | go mod download 30 | 31 | # Cosmwasm - Download correct libwasmvm version 32 | RUN ARCH=$(uname -m) && WASMVM_VERSION=$(go list -m github.com/CosmWasm/wasmvm | sed 's/.* //') && \ 33 | wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/libwasmvm_muslc.$ARCH.a \ 34 | -O /lib/libwasmvm_muslc.a && \ 35 | # verify checksum 36 | wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/checksums.txt -O /tmp/checksums.txt && \ 37 | sha256sum /lib/libwasmvm_muslc.a | grep $(cat /tmp/checksums.txt | grep libwasmvm_muslc.$ARCH | cut -d ' ' -f 1) && \ 38 | wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/libwasmvm.x86_64.so \ 39 | -O /lib/libwasmvm.x86_64.so && \ 40 | wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/libwasmvm.aarch64.so \ 41 | -O /lib/libwasmvm.aarch64.so 42 | 43 | # Copy the remaining files 44 | COPY . . 45 | 46 | RUN make build BECH32_PREFIX=rol 47 | 48 | FROM ubuntu:latest 49 | 50 | RUN apt-get update -y 51 | RUN apt-get install -y curl 52 | 53 | COPY --from=go-builder /app/build/rollapp-wasm /usr/local/bin/rollappd 54 | COPY --from=go-builder /lib/libwasmvm.x86_64.so /lib/libwasmvm.x86_64.so 55 | COPY --from=go-builder /lib/libwasmvm.aarch64.so /lib/libwasmvm.aarch64.so 56 | 57 | WORKDIR /app 58 | 59 | EXPOSE 26657 1317 -------------------------------------------------------------------------------- /app/app_test.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "testing" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" 8 | "github.com/dymensionxyz/dymension-rdk/server/consensus" 9 | "github.com/gogo/protobuf/proto" 10 | prototypes "github.com/gogo/protobuf/types" 11 | "github.com/stretchr/testify/require" 12 | abci "github.com/tendermint/tendermint/abci/types" 13 | "github.com/tendermint/tendermint/proto/tendermint/types" 14 | ) 15 | 16 | func TestBeginBlocker(t *testing.T) { 17 | app, valAccount := SetupWithOneValidator(t) 18 | ctx := app.NewUncachedContext(true, types.Header{ 19 | Height: 1, 20 | ChainID: "testchain_9000-1", 21 | }) 22 | 23 | app.setAdmissionHandler(consensus.AllowedMessagesHandler([]string{ 24 | proto.MessageName(&banktypes.MsgSend{}), 25 | })) 26 | 27 | bankSend := &banktypes.MsgSend{ 28 | FromAddress: valAccount.GetAddress().String(), 29 | ToAddress: valAccount.GetAddress().String(), 30 | Amount: sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(100))), 31 | } 32 | msgBz, err := proto.Marshal(bankSend) 33 | require.NoError(t, err) 34 | 35 | goodMessage := &prototypes.Any{ 36 | TypeUrl: proto.MessageName(&banktypes.MsgSend{}), 37 | Value: msgBz, 38 | } 39 | 40 | testCases := []struct { 41 | name string 42 | consensusMsgs []*prototypes.Any 43 | expectError bool 44 | }{ 45 | { 46 | name: "ValidConsensusMessage", 47 | consensusMsgs: []*prototypes.Any{ 48 | goodMessage, 49 | }, 50 | expectError: false, 51 | }, 52 | { 53 | name: "InvalidUnpackMessage", 54 | consensusMsgs: []*prototypes.Any{ 55 | { 56 | TypeUrl: "/path.to.InvalidMsg", 57 | Value: []byte("invalid unpack data"), 58 | }, 59 | }, 60 | expectError: true, 61 | }, 62 | { 63 | name: "InvalidExecutionMessage", 64 | consensusMsgs: []*prototypes.Any{ 65 | { 66 | TypeUrl: "/path.to.ExecErrorMsg", 67 | Value: []byte("execution error data"), 68 | }, 69 | }, 70 | expectError: true, 71 | }, 72 | } 73 | 74 | for _, tc := range testCases { 75 | t.Run(tc.name, func(t *testing.T) { 76 | req := abci.RequestBeginBlock{ 77 | Header: types.Header{ 78 | Height: 1, 79 | Time: ctx.BlockTime(), 80 | ChainID: "testchain_9000-1", 81 | }, 82 | LastCommitInfo: abci.LastCommitInfo{}, 83 | ByzantineValidators: []abci.Evidence{}, 84 | ConsensusMessages: tc.consensusMsgs, 85 | } 86 | 87 | res := app.BeginBlocker(ctx, req) 88 | require.NotNil(t, res) 89 | 90 | if tc.expectError { 91 | require.NotEmpty(t, res.ConsensusMessagesResponses) 92 | for _, response := range res.ConsensusMessagesResponses { 93 | _, isError := response.Response.(*abci.ConsensusMessageResponse_Error) 94 | require.True(t, isError, "Expected an error response but got a success") 95 | } 96 | } else { 97 | require.NotEmpty(t, res.ConsensusMessagesResponses) 98 | for _, response := range res.ConsensusMessagesResponses { 99 | _, isOk := response.Response.(*abci.ConsensusMessageResponse_Ok) 100 | require.True(t, isOk, "Expected a success response but got an error") 101 | } 102 | } 103 | }) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/create_account_decorator.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 7 | authante "github.com/cosmos/cosmos-sdk/x/auth/ante" 8 | authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" 9 | "github.com/cosmos/cosmos-sdk/x/auth/types" 10 | rdkante "github.com/dymensionxyz/dymension-rdk/server/ante" 11 | ) 12 | 13 | type createAccountDecorator struct { 14 | ak accountKeeper 15 | } 16 | 17 | type accountKeeper interface { 18 | authante.AccountKeeper 19 | NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) types.AccountI 20 | } 21 | 22 | func NewCreateAccountDecorator(ak accountKeeper) createAccountDecorator { 23 | return createAccountDecorator{ak: ak} 24 | } 25 | 26 | const newAccountCtxKeyPrefix = "new-account/" 27 | 28 | func CtxKeyNewAccount(acc string) string { 29 | return newAccountCtxKeyPrefix + acc 30 | } 31 | 32 | func (cad createAccountDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { 33 | sigTx, ok := tx.(authsigning.SigVerifiableTx) 34 | if !ok { 35 | return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") 36 | } 37 | 38 | pubkeys, err := sigTx.GetPubKeys() 39 | if err != nil { 40 | return ctx, err 41 | } 42 | 43 | ibcRelayerMsg := rdkante.IbcOnly(tx.GetMsgs()...) 44 | 45 | for i, pk := range pubkeys { 46 | if pk == nil { 47 | continue 48 | } 49 | 50 | _, err := authante.GetSignerAcc(ctx, cad.ak, sigTx.GetSigners()[i]) 51 | if err != nil { 52 | // ======= HACK ========================= 53 | // for IBC relayer messages, create an account if it doesn't exist 54 | if ibcRelayerMsg { 55 | address := sdk.AccAddress(pk.Address()) 56 | acc := cad.ak.NewAccountWithAddress(ctx, address) 57 | // inject the new account flag into the context, in order to signal 58 | // the account creation to the subsequent decorators (sigchecker) 59 | ctx = ctx.WithValue(CtxKeyNewAccount(address.String()), struct{}{}) 60 | cad.ak.SetAccount(ctx, acc) 61 | } else { 62 | return ctx, err 63 | } 64 | // ====================================== 65 | } 66 | } 67 | 68 | return next(ctx, tx, simulate) 69 | } 70 | -------------------------------------------------------------------------------- /app/encoding.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 6 | "github.com/cosmos/cosmos-sdk/std" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | cryptocodec "github.com/evmos/evmos/v12/crypto/codec" 9 | 10 | "github.com/dymensionxyz/rollapp-wasm/app/params" 11 | ) 12 | 13 | // MakeEncodingConfig creates an EncodingConfig for testing 14 | func MakeEncodingConfig() params.EncodingConfig { 15 | encodingConfig := params.MakeEncodingConfig() 16 | RegisterLegacyAminoCodec(encodingConfig.Amino) 17 | RegisterInterfaces(encodingConfig.InterfaceRegistry) 18 | ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) 19 | ModuleBasics.RegisterInterfaces(encodingConfig.InterfaceRegistry) 20 | return encodingConfig 21 | } 22 | 23 | // RegisterLegacyAminoCodec registers Interfaces from types, crypto, and SDK std. 24 | func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { 25 | sdk.RegisterLegacyAminoCodec(cdc) 26 | cryptocodec.RegisterCrypto(cdc) 27 | codec.RegisterEvidences(cdc) 28 | } 29 | 30 | // RegisterInterfaces registers Interfaces from types, crypto, and SDK std. 31 | func RegisterInterfaces(interfaceRegistry codectypes.InterfaceRegistry) { 32 | std.RegisterInterfaces(interfaceRegistry) 33 | cryptocodec.RegisterInterfaces(interfaceRegistry) 34 | } 35 | -------------------------------------------------------------------------------- /app/genesis.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/cosmos/cosmos-sdk/codec" 7 | ) 8 | 9 | // GenesisState of the blockchain is represented here as a map of raw json 10 | // messages key'd by a identifier string. 11 | // The identifier is used to determine which module genesis information belongs 12 | // to so it may be appropriately routed during init chain. 13 | // Within this application default genesis information is retrieved from 14 | // the ModuleBasicManager which populates json from each BasicModule 15 | // object provided to it during init. 16 | type GenesisState map[string]json.RawMessage 17 | 18 | // NewDefaultGenesisState generates the default state for the application. 19 | func NewDefaultGenesisState(cdc codec.JSONCodec) GenesisState { 20 | return ModuleBasics.DefaultGenesis(cdc) 21 | } 22 | -------------------------------------------------------------------------------- /app/params/encoding.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/client" 5 | "github.com/cosmos/cosmos-sdk/codec" 6 | "github.com/cosmos/cosmos-sdk/codec/types" 7 | 8 | simappparams "github.com/cosmos/cosmos-sdk/simapp/params" 9 | ) 10 | 11 | // EncodingConfig specifies the concrete encoding types to use for a given app. 12 | // This is provided for compatibility between protobuf and amino implementations. 13 | type EncodingConfig struct { 14 | InterfaceRegistry types.InterfaceRegistry 15 | Codec codec.Codec 16 | TxConfig client.TxConfig 17 | Amino *codec.LegacyAmino 18 | } 19 | 20 | // EncodingAsSimapp takes an EncodingConfig and returns a simappparams.EncodingConfig. 21 | // The only difference between the two is the type, which is useful for compatibility 22 | // between amino and protobuf implementations. 23 | func EncodingAsSimapp(encCfg EncodingConfig) simappparams.EncodingConfig { 24 | return simappparams.EncodingConfig{ 25 | InterfaceRegistry: encCfg.InterfaceRegistry, 26 | Codec: encCfg.Codec, 27 | TxConfig: encCfg.TxConfig, 28 | Amino: encCfg.Amino, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/params/proto.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | "github.com/cosmos/cosmos-sdk/codec/types" 6 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 7 | ) 8 | 9 | // MakeEncodingConfig creates an EncodingConfig for an amino based test configuration. 10 | func MakeEncodingConfig() EncodingConfig { 11 | amino := codec.NewLegacyAmino() 12 | interfaceRegistry := types.NewInterfaceRegistry() 13 | codec := codec.NewProtoCodec(interfaceRegistry) 14 | txCfg := tx.NewTxConfig(codec, tx.DefaultSignModes) 15 | 16 | return EncodingConfig{ 17 | InterfaceRegistry: interfaceRegistry, 18 | Codec: codec, 19 | TxConfig: txCfg, 20 | Amino: amino, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/test_app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | abci "github.com/tendermint/tendermint/abci/types" 5 | tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 6 | tmtypes "github.com/tendermint/tendermint/types" 7 | "time" 8 | 9 | // unnamed import of statik for swagger UI support 10 | _ "github.com/cosmos/cosmos-sdk/client/docs/statik" 11 | ) 12 | 13 | var DefaultConsensusParams = &abci.ConsensusParams{ 14 | Block: &abci.BlockParams{ 15 | MaxBytes: 200000, 16 | MaxGas: -1, 17 | }, 18 | Evidence: &tmproto.EvidenceParams{ 19 | MaxAgeNumBlocks: 302400, 20 | MaxAgeDuration: 504 * time.Hour, // 3 weeks is the max duration 21 | MaxBytes: 10000, 22 | }, 23 | Validator: &tmproto.ValidatorParams{ 24 | PubKeyTypes: []string{ 25 | tmtypes.ABCIPubKeyTypeEd25519, 26 | }, 27 | }, 28 | } 29 | 30 | // EmptyAppOptions is a stub implementing AppOptions 31 | type EmptyAppOptions struct{} 32 | 33 | // Get implements AppOptions 34 | func (ao EmptyAppOptions) Get(o string) interface{} { 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /app/upgrades/drs-2/constants.go: -------------------------------------------------------------------------------- 1 | package drs2 2 | 3 | import ( 4 | storetypes "github.com/cosmos/cosmos-sdk/store/types" 5 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 6 | ) 7 | 8 | const ( 9 | UpgradeName = "drs-2" 10 | ) 11 | 12 | var Upgrade = upgrades.Upgrade{ 13 | Name: UpgradeName, 14 | CreateHandler: CreateUpgradeHandler, 15 | StoreUpgrades: storetypes.StoreUpgrades{}, 16 | } 17 | -------------------------------------------------------------------------------- /app/upgrades/drs-2/upgrade.go: -------------------------------------------------------------------------------- 1 | package drs2 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/cosmos/cosmos-sdk/types/module" 6 | upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" 7 | 8 | rollappparamskeeper "github.com/dymensionxyz/dymension-rdk/x/rollappparams/keeper" 9 | rollappparamstypes "github.com/dymensionxyz/dymension-rdk/x/rollappparams/types" 10 | 11 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 12 | ) 13 | 14 | func CreateUpgradeHandler( 15 | kk upgrades.UpgradeKeepers, 16 | mm *module.Manager, 17 | configurator module.Configurator, 18 | ) upgradetypes.UpgradeHandler { 19 | return func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { 20 | if err := HandleUpgrade(ctx, kk.RpKeeper); err != nil { 21 | return nil, err 22 | } 23 | return mm.RunMigrations(ctx, configurator, fromVM) 24 | } 25 | } 26 | 27 | func HandleUpgrade(ctx sdk.Context, rpKeeper rollappparamskeeper.Keeper) error { 28 | // upgrade drs to 2 29 | if err := rpKeeper.SetVersion(ctx, uint32(2)); err != nil { 30 | return err 31 | } 32 | // migrate rollapp params with missing min-gas-prices 33 | return rpKeeper.SetMinGasPrices(ctx, rollappparamstypes.DefaultParams().MinGasPrices) 34 | } 35 | -------------------------------------------------------------------------------- /app/upgrades/drs-3/constants.go: -------------------------------------------------------------------------------- 1 | package drs3 2 | 3 | import ( 4 | storetypes "github.com/cosmos/cosmos-sdk/store/types" 5 | 6 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 7 | ) 8 | 9 | const ( 10 | UpgradeName = "drs-3" 11 | ) 12 | 13 | var Upgrade = upgrades.Upgrade{ 14 | Name: UpgradeName, 15 | CreateHandler: CreateUpgradeHandler, 16 | StoreUpgrades: storetypes.StoreUpgrades{}, 17 | } 18 | -------------------------------------------------------------------------------- /app/upgrades/drs-3/upgrade.go: -------------------------------------------------------------------------------- 1 | package drs3 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/cosmos/cosmos-sdk/types/module" 6 | upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" 7 | rollappparamskeeper "github.com/dymensionxyz/dymension-rdk/x/rollappparams/keeper" 8 | 9 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 10 | drs2 "github.com/dymensionxyz/rollapp-wasm/app/upgrades/drs-2" 11 | ) 12 | 13 | func CreateUpgradeHandler( 14 | kk upgrades.UpgradeKeepers, 15 | mm *module.Manager, 16 | configurator module.Configurator, 17 | ) upgradetypes.UpgradeHandler { 18 | return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { 19 | if err := HandleUpgrade(ctx, kk.RpKeeper); err != nil { 20 | return nil, err 21 | } 22 | return mm.RunMigrations(ctx, configurator, fromVM) 23 | } 24 | } 25 | 26 | func HandleUpgrade(ctx sdk.Context, rpKeeper rollappparamskeeper.Keeper) error { 27 | if rpKeeper.Version(ctx) < 2 { 28 | // first run drs-2 migration 29 | if err := drs2.HandleUpgrade(ctx, rpKeeper); err != nil { 30 | return err 31 | } 32 | } 33 | // upgrade drs to 3 34 | if err := rpKeeper.SetVersion(ctx, uint32(3)); err != nil { 35 | return err 36 | } 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /app/upgrades/drs-4/constants.go: -------------------------------------------------------------------------------- 1 | package drs4 2 | 3 | import ( 4 | storetypes "github.com/cosmos/cosmos-sdk/store/types" 5 | 6 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 7 | ) 8 | 9 | const ( 10 | UpgradeName = "drs-4" 11 | ) 12 | 13 | var Upgrade = upgrades.Upgrade{ 14 | Name: UpgradeName, 15 | CreateHandler: CreateUpgradeHandler, 16 | StoreUpgrades: storetypes.StoreUpgrades{}, 17 | } 18 | -------------------------------------------------------------------------------- /app/upgrades/drs-4/upgrade.go: -------------------------------------------------------------------------------- 1 | package drs4 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/cosmos/cosmos-sdk/types/module" 6 | upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" 7 | rollappparamskeeper "github.com/dymensionxyz/dymension-rdk/x/rollappparams/keeper" 8 | 9 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 10 | drs3 "github.com/dymensionxyz/rollapp-wasm/app/upgrades/drs-3" 11 | ) 12 | 13 | func CreateUpgradeHandler( 14 | kk upgrades.UpgradeKeepers, 15 | mm *module.Manager, 16 | configurator module.Configurator, 17 | ) upgradetypes.UpgradeHandler { 18 | return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { 19 | if err := HandleUpgrade(ctx, kk.RpKeeper); err != nil { 20 | return nil, err 21 | } 22 | return mm.RunMigrations(ctx, configurator, fromVM) 23 | } 24 | } 25 | 26 | func HandleUpgrade(ctx sdk.Context, rpKeeper rollappparamskeeper.Keeper) error { 27 | if rpKeeper.Version(ctx) < 3 { 28 | // first run drs-3 migration 29 | if err := drs3.HandleUpgrade(ctx, rpKeeper); err != nil { 30 | return err 31 | } 32 | } 33 | // upgrade drs to 4 34 | if err := rpKeeper.SetVersion(ctx, uint32(4)); err != nil { 35 | return err 36 | } 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /app/upgrades/drs-5/constants.go: -------------------------------------------------------------------------------- 1 | package drs5 2 | 3 | import ( 4 | storetypes "github.com/cosmos/cosmos-sdk/store/types" 5 | 6 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 7 | ) 8 | 9 | const ( 10 | UpgradeName = "drs-5" 11 | ) 12 | 13 | var Upgrade = upgrades.Upgrade{ 14 | Name: UpgradeName, 15 | CreateHandler: CreateUpgradeHandler, 16 | StoreUpgrades: storetypes.StoreUpgrades{}, 17 | } 18 | -------------------------------------------------------------------------------- /app/upgrades/drs-5/upgrade.go: -------------------------------------------------------------------------------- 1 | package drs5 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/cosmos/cosmos-sdk/types/module" 6 | upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" 7 | rollappparamskeeper "github.com/dymensionxyz/dymension-rdk/x/rollappparams/keeper" 8 | 9 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 10 | drs4 "github.com/dymensionxyz/rollapp-wasm/app/upgrades/drs-4" 11 | ) 12 | 13 | func CreateUpgradeHandler( 14 | kk upgrades.UpgradeKeepers, 15 | mm *module.Manager, 16 | configurator module.Configurator, 17 | ) upgradetypes.UpgradeHandler { 18 | return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { 19 | if err := HandleUpgrade(ctx, kk.RpKeeper); err != nil { 20 | return nil, err 21 | } 22 | return mm.RunMigrations(ctx, configurator, fromVM) 23 | } 24 | } 25 | 26 | func HandleUpgrade(ctx sdk.Context, rpKeeper rollappparamskeeper.Keeper) error { 27 | if rpKeeper.Version(ctx) < 4 { 28 | // first run drs-4 migration 29 | if err := drs4.HandleUpgrade(ctx, rpKeeper); err != nil { 30 | return err 31 | } 32 | } 33 | // upgrade drs to 5 34 | if err := rpKeeper.SetVersion(ctx, uint32(5)); err != nil { 35 | return err 36 | } 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /app/upgrades/drs-6/constants.go: -------------------------------------------------------------------------------- 1 | package drs6 2 | 3 | import ( 4 | storetypes "github.com/cosmos/cosmos-sdk/store/types" 5 | 6 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 7 | ) 8 | 9 | const ( 10 | UpgradeName = "drs-6" 11 | ) 12 | 13 | var Upgrade = upgrades.Upgrade{ 14 | Name: UpgradeName, 15 | CreateHandler: CreateUpgradeHandler, 16 | StoreUpgrades: storetypes.StoreUpgrades{}, 17 | } 18 | -------------------------------------------------------------------------------- /app/upgrades/drs-6/upgrade.go: -------------------------------------------------------------------------------- 1 | package drs6 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/cosmos/cosmos-sdk/types/module" 6 | upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" 7 | rollappparamskeeper "github.com/dymensionxyz/dymension-rdk/x/rollappparams/keeper" 8 | 9 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 10 | drs5 "github.com/dymensionxyz/rollapp-wasm/app/upgrades/drs-5" 11 | ) 12 | 13 | func CreateUpgradeHandler( 14 | kk upgrades.UpgradeKeepers, 15 | mm *module.Manager, 16 | configurator module.Configurator, 17 | ) upgradetypes.UpgradeHandler { 18 | return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { 19 | if err := HandleUpgrade(ctx, kk.RpKeeper); err != nil { 20 | return nil, err 21 | } 22 | return mm.RunMigrations(ctx, configurator, fromVM) 23 | } 24 | } 25 | 26 | func HandleUpgrade(ctx sdk.Context, rpKeeper rollappparamskeeper.Keeper) error { 27 | if rpKeeper.Version(ctx) < 5 { 28 | // first run drs-5 migration 29 | if err := drs5.HandleUpgrade(ctx, rpKeeper); err != nil { 30 | return err 31 | } 32 | } 33 | // upgrade drs to 6 34 | if err := rpKeeper.SetVersion(ctx, uint32(6)); err != nil { 35 | return err 36 | } 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /app/upgrades/drs-7/constants.go: -------------------------------------------------------------------------------- 1 | package drs7 2 | 3 | import ( 4 | storetypes "github.com/cosmos/cosmos-sdk/store/types" 5 | 6 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 7 | ) 8 | 9 | const ( 10 | UpgradeName = "drs-7" 11 | ) 12 | 13 | var Upgrade = upgrades.Upgrade{ 14 | Name: UpgradeName, 15 | CreateHandler: CreateUpgradeHandler, 16 | StoreUpgrades: storetypes.StoreUpgrades{}, 17 | } 18 | -------------------------------------------------------------------------------- /app/upgrades/drs-7/upgrade.go: -------------------------------------------------------------------------------- 1 | package drs7 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/cosmos/cosmos-sdk/types/module" 6 | upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" 7 | rollappparamskeeper "github.com/dymensionxyz/dymension-rdk/x/rollappparams/keeper" 8 | 9 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 10 | drs6 "github.com/dymensionxyz/rollapp-wasm/app/upgrades/drs-6" 11 | ) 12 | 13 | func CreateUpgradeHandler( 14 | kk upgrades.UpgradeKeepers, 15 | mm *module.Manager, 16 | configurator module.Configurator, 17 | ) upgradetypes.UpgradeHandler { 18 | return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { 19 | if err := HandleUpgrade(ctx, kk.RpKeeper); err != nil { 20 | return nil, err 21 | } 22 | return mm.RunMigrations(ctx, configurator, fromVM) 23 | } 24 | } 25 | 26 | func HandleUpgrade(ctx sdk.Context, rpKeeper rollappparamskeeper.Keeper) error { 27 | if rpKeeper.Version(ctx) < 6 { 28 | // first run drs-6 migration 29 | if err := drs6.HandleUpgrade(ctx, rpKeeper); err != nil { 30 | return err 31 | } 32 | } 33 | // upgrade drs to 7 34 | if err := rpKeeper.SetVersion(ctx, uint32(7)); err != nil { 35 | return err 36 | } 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /app/upgrades/drs-8/constants.go: -------------------------------------------------------------------------------- 1 | package drs8 2 | 3 | import ( 4 | storetypes "github.com/cosmos/cosmos-sdk/store/types" 5 | 6 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 7 | ) 8 | 9 | const ( 10 | UpgradeName = "drs-8" 11 | ) 12 | 13 | var Upgrade = upgrades.Upgrade{ 14 | Name: UpgradeName, 15 | CreateHandler: CreateUpgradeHandler, 16 | StoreUpgrades: storetypes.StoreUpgrades{}, 17 | } 18 | -------------------------------------------------------------------------------- /app/upgrades/drs-8/upgrade.go: -------------------------------------------------------------------------------- 1 | package drs8 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/cosmos/cosmos-sdk/types/module" 6 | upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" 7 | rollappparamskeeper "github.com/dymensionxyz/dymension-rdk/x/rollappparams/keeper" 8 | 9 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 10 | drs7 "github.com/dymensionxyz/rollapp-wasm/app/upgrades/drs-7" 11 | ) 12 | 13 | func CreateUpgradeHandler( 14 | kk upgrades.UpgradeKeepers, 15 | mm *module.Manager, 16 | configurator module.Configurator, 17 | ) upgradetypes.UpgradeHandler { 18 | return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { 19 | if err := HandleUpgrade(ctx, kk.RpKeeper); err != nil { 20 | return nil, err 21 | } 22 | return mm.RunMigrations(ctx, configurator, fromVM) 23 | } 24 | } 25 | 26 | func HandleUpgrade(ctx sdk.Context, rpKeeper rollappparamskeeper.Keeper) error { 27 | if rpKeeper.Version(ctx) < 7 { 28 | // first run drs-7 migration 29 | if err := drs7.HandleUpgrade(ctx, rpKeeper); err != nil { 30 | return err 31 | } 32 | } 33 | // upgrade drs to 8 34 | if err := rpKeeper.SetVersion(ctx, uint32(8)); err != nil { 35 | return err 36 | } 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /app/upgrades/drs-9/constants.go: -------------------------------------------------------------------------------- 1 | package drs9 2 | 3 | import ( 4 | storetypes "github.com/cosmos/cosmos-sdk/store/types" 5 | 6 | "github.com/dymensionxyz/rollapp-wasm/app/upgrades" 7 | ) 8 | 9 | const ( 10 | UpgradeName = "drs-9" 11 | DRS uint32 = 9 12 | ) 13 | 14 | var Upgrade = upgrades.Upgrade{ 15 | Name: UpgradeName, 16 | CreateHandler: CreateUpgradeHandler, 17 | StoreUpgrades: storetypes.StoreUpgrades{}, 18 | } 19 | -------------------------------------------------------------------------------- /app/upgrades/drs-9/doc.go: -------------------------------------------------------------------------------- 1 | // Package drs9 (3D) provides an upgrade for broken vesting accounts where the start and end timestamps are erroneously expressed in milliseconds 2 | package drs9 3 | -------------------------------------------------------------------------------- /app/upgrades/types.go: -------------------------------------------------------------------------------- 1 | package upgrades 2 | 3 | import ( 4 | storetypes "github.com/cosmos/cosmos-sdk/store/types" 5 | "github.com/cosmos/cosmos-sdk/types/module" 6 | authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" 7 | upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" 8 | rollappparamskeeper "github.com/dymensionxyz/dymension-rdk/x/rollappparams/keeper" 9 | ) 10 | 11 | type UpgradeKeepers struct { 12 | RpKeeper rollappparamskeeper.Keeper 13 | AccountKeeper authkeeper.AccountKeeper 14 | } 15 | 16 | // Upgrade defines a struct containing necessary fields that a SoftwareUpgradeProposal 17 | // must have written, in order for the state migration to go smoothly. 18 | // An upgrade must implement this struct, and then set it in the app.go. 19 | // The app.go will then define the handler. 20 | type Upgrade struct { 21 | // Upgrade version name, for the upgrade handler, e.g. `v4` 22 | Name string 23 | 24 | // CreateHandler defines the function that creates an upgrade handler 25 | CreateHandler func( 26 | keepers UpgradeKeepers, 27 | mm *module.Manager, 28 | configurator module.Configurator, 29 | ) upgradetypes.UpgradeHandler 30 | 31 | // Store upgrades, should be used for any new modules introduced, new modules deleted, or store names renamed. 32 | StoreUpgrades storetypes.StoreUpgrades 33 | } 34 | -------------------------------------------------------------------------------- /app/wasm.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "strings" 5 | 6 | wasm "github.com/CosmWasm/wasmd/x/wasm" 7 | ) 8 | 9 | var ( 10 | // If EnabledSpecificProposals is "", and this is "true", then enable all x/wasm proposals. 11 | // If EnabledSpecificProposals is "", and this is not "true", then disable all x/wasm proposals. 12 | ProposalsEnabled = "true" 13 | // If set to non-empty string it must be comma-separated list of values that are all a subset 14 | // of "EnableAllProposals" (takes precedence over ProposalsEnabled) 15 | // https://github.com/CosmWasm/wasmd/blob/02a54d33ff2c064f3539ae12d75d027d9c665f05/x/wasm/internal/types/proposal.go#L28-L34 16 | EnableSpecificProposals = "" 17 | ) 18 | 19 | // GetEnabledProposals parses the ProposalsEnabled / EnableSpecificProposals values to 20 | // produce a list of enabled proposals to pass into wasmd app. 21 | func GetEnabledProposals() []wasm.ProposalType { 22 | if EnableSpecificProposals == "" { 23 | if ProposalsEnabled == "true" { 24 | return wasm.EnableAllProposals 25 | } 26 | return wasm.DisableAllProposals 27 | } 28 | chunks := strings.Split(EnableSpecificProposals, ",") 29 | proposals, err := wasm.ConvertToProposals(chunks) 30 | if err != nil { 31 | panic(err) 32 | } 33 | return proposals 34 | } 35 | 36 | // AllCapabilities returns all capabilities available with the current wasmvm 37 | // See https://github.com/CosmWasm/cosmwasm/blob/main/docs/CAPABILITIES-BUILT-IN.md 38 | // This functionality is going to be moved upstream: https://github.com/CosmWasm/wasmvm/issues/425 39 | func AllCapabilities() []string { 40 | return []string{ 41 | "iterator", 42 | "staking", 43 | "stargate", 44 | "cosmwasm_1_1", 45 | "cosmwasm_1_2", 46 | "cosmwasm_1_3", 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contract.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymensionxyz/rollapp-wasm/b135a5543c1963a77e4402b4c1d6613d49596754/contract.wasm -------------------------------------------------------------------------------- /contracts/callback-test/.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 | -------------------------------------------------------------------------------- /contracts/callback-test/.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | /target 3 | /schema 4 | 5 | # Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) 6 | .cargo-ok 7 | 8 | # Text file backups 9 | **/*.rs.bk 10 | 11 | # macOS 12 | .DS_Store 13 | 14 | # IDEs 15 | *.iml 16 | .idea 17 | -------------------------------------------------------------------------------- /contracts/callback-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "callback-test" 3 | version = "0.1.0" 4 | authors = ["Spoorthi "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | crate-type = ["cdylib", "rlib"] 11 | 12 | [profile.release] 13 | opt-level = 3 14 | debug = false 15 | rpath = false 16 | lto = true 17 | debug-assertions = false 18 | codegen-units = 1 19 | panic = 'abort' 20 | incremental = false 21 | overflow-checks = true 22 | 23 | [features] 24 | # for more explicit tests, cargo test --features=backtraces 25 | backtraces = ["cosmwasm-std/backtraces"] 26 | # use library feature to disable all instantiate/execute/query exports 27 | library = [] 28 | 29 | [package.metadata.scripts] 30 | optimize = """docker run --rm -v "$(pwd)":/code \ 31 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ 32 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 33 | cosmwasm/optimizer:0.15.0 34 | """ 35 | 36 | [dependencies] 37 | cosmwasm-schema = "1.5.0" 38 | cosmwasm-std = { version = "1.5.0", features = [ 39 | "cosmwasm_1_3", 40 | # Enable this if you only deploy to chains that have CosmWasm 1.4 or higher 41 | # "cosmwasm_1_4", 42 | ] } 43 | cw-storage-plus = "1.1.0" 44 | cw2 = "1.1.1" 45 | schemars = "0.8.15" 46 | serde = { version = "1.0.189", default-features = false, features = ["derive"] } 47 | thiserror = { version = "1.0.49" } 48 | 49 | [dev-dependencies] 50 | cw-multi-test = "0.17.0" 51 | -------------------------------------------------------------------------------- /contracts/callback-test/README.md: -------------------------------------------------------------------------------- 1 | # callback-test 2 | 3 | This contract is a sample contract which is used to test the x/callback module functionalty. 4 | 5 | The implementation is based on the sample counter contract and has been modified to have callback msg in the Sudo entrypoint. 6 | 7 | The following changes have been made 8 | 9 | ```rust 10 | // msg.rs 11 | 12 | #[cw_serde] 13 | pub enum SudoMsg { 14 | Callback { 15 | job_id: u64 16 | }, 17 | Error { 18 | module_name: String, 19 | error_code: u32, 20 | contract_address: String, 21 | input_payload: String, 22 | error_message: String, 23 | }, 24 | } 25 | ``` 26 | 27 | ```rust 28 | //contract.rs 29 | 30 | pub mod sudo { 31 | use super::*; 32 | use std::u64; 33 | 34 | pub fn handle_callback(deps: DepsMut, job_id: u64) -> Result { 35 | STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { 36 | if job_id == 0 { 37 | state.count -= 1; // Decrement the count 38 | }; 39 | if job_id == 1 { 40 | state.count += 1; // Increment the count 41 | }; 42 | if job_id == 2 { 43 | return Err(ContractError::SomeError {}); // Throw an error 44 | } 45 | // else do nothing 46 | Ok(state) 47 | })?; 48 | 49 | Ok(Response::new().add_attribute("action", "handle_callback")) 50 | } 51 | 52 | pub fn handle_error(deps: DepsMut, module_name: String, error_code: u32, _contract_address: String, _input_payload: String, _error_message: String) -> Result { 53 | STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { 54 | if module_name == "callback" && error_code == 2 { 55 | state.count = 0; // reset the counter 56 | } 57 | Ok(state) 58 | })?; 59 | 60 | Ok(Response::new().add_attribute("action", "handle_error")) 61 | } 62 | } 63 | ``` 64 | 65 | Relevant test has been added as well in contract.rs and the default counter init/execute tests removed 66 | -------------------------------------------------------------------------------- /contracts/callback-test/artifacts/callback_test.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymensionxyz/rollapp-wasm/b135a5543c1963a77e4402b4c1d6613d49596754/contracts/callback-test/artifacts/callback_test.wasm -------------------------------------------------------------------------------- /contracts/callback-test/artifacts/checksums.txt: -------------------------------------------------------------------------------- 1 | 136f06a7a6d9192218dde561353c6c76e2f54fa96355cc4e1038ddd161e53df7 callback_test.wasm 2 | -------------------------------------------------------------------------------- /contracts/callback-test/src/bin/schema.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::write_api; 2 | 3 | use callback_test::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, SudoMsg}; 4 | 5 | fn main() { 6 | write_api! { 7 | instantiate: InstantiateMsg, 8 | execute: ExecuteMsg, 9 | query: QueryMsg, 10 | sudo: SudoMsg, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/callback-test/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("Unauthorized")] 10 | Unauthorized {}, 11 | 12 | #[error("SomeError")] 13 | SomeError {}, 14 | } 15 | -------------------------------------------------------------------------------- /contracts/callback-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | mod error; 3 | pub mod msg; 4 | pub mod state; 5 | 6 | pub use crate::error::ContractError; 7 | -------------------------------------------------------------------------------- /contracts/callback-test/src/msg.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | 3 | #[cw_serde] 4 | pub struct InstantiateMsg { 5 | pub count: i32, 6 | } 7 | 8 | #[cw_serde] 9 | pub enum ExecuteMsg { 10 | Increment {}, 11 | Reset { count: i32 }, 12 | } 13 | 14 | #[cw_serde] 15 | pub enum SudoMsg { 16 | Callback { 17 | job_id: u64, 18 | }, 19 | Error { 20 | module_name: String, 21 | error_code: u32, 22 | contract_address: String, 23 | input_payload: String, 24 | error_message: String, 25 | }, 26 | } 27 | 28 | #[cw_serde] 29 | #[derive(QueryResponses)] 30 | pub enum QueryMsg { 31 | // GetCount returns the current count as a json-encoded number 32 | #[returns(GetCountResponse)] 33 | GetCount {}, 34 | } 35 | 36 | #[cw_serde] 37 | pub struct GetCountResponse { 38 | pub count: i32, 39 | } 40 | -------------------------------------------------------------------------------- /contracts/callback-test/src/state.rs: -------------------------------------------------------------------------------- 1 | use schemars::JsonSchema; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use cosmwasm_std::Addr; 5 | use cw_storage_plus::Item; 6 | 7 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] 8 | pub struct State { 9 | pub count: i32, 10 | pub owner: Addr, 11 | } 12 | 13 | pub const STATE: Item = Item::new("state"); 14 | -------------------------------------------------------------------------------- /e2e/testing/chain_ops.go: -------------------------------------------------------------------------------- 1 | package e2eTesting 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | govTypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1" 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | // ExecuteGovProposal submits a new proposal and votes for it. 10 | func (chain *TestChain) ExecuteGovProposal(proposerAcc Account, expPass bool, proposals []sdk.Msg, title string, summary string, metadata string) { 11 | t := chain.t 12 | 13 | // Get params 14 | k := chain.app.GovKeeper 15 | govDepositParams := k.GetDepositParams(chain.GetContext()) 16 | govVotingParams := k.GetVotingParams(chain.GetContext()) 17 | depositCoin := govDepositParams.MinDeposit 18 | votingDur := govVotingParams.VotingPeriod 19 | 20 | // Submit proposal with min deposit to start the voting 21 | msg, err := govTypes.NewMsgSubmitProposal(proposals, depositCoin, proposerAcc.Address.String(), metadata) 22 | require.NoError(t, err) 23 | 24 | _, res, _, err := chain.SendMsgs(proposerAcc, true, []sdk.Msg{msg}) 25 | require.NoError(t, err) 26 | txRes := chain.ParseSDKResultData(res) 27 | require.Len(t, txRes.MsgResponses, 1) 28 | 29 | var resp govTypes.MsgSubmitProposalResponse 30 | require.NoError(t, resp.Unmarshal(txRes.MsgResponses[0].Value)) 31 | proposalID := resp.ProposalId 32 | 33 | // Vote with all validators (delegators) 34 | for i := 0; i < len(chain.valSet.Validators); i++ { 35 | delegatorAcc := chain.GetAccount(i) 36 | 37 | msg := govTypes.NewMsgVote(delegatorAcc.Address, proposalID, govTypes.OptionYes, "metadata") 38 | _, _, _, err = chain.SendMsgs(proposerAcc, true, []sdk.Msg{msg}) 39 | require.NoError(t, err) 40 | } 41 | 42 | // Wait for voting to end 43 | chain.NextBlock(*votingDur) 44 | chain.NextBlock(0) // for the Gov EndBlocker to work 45 | 46 | // Check if proposal was passed 47 | proposal, ok := k.GetProposal(chain.GetContext(), proposalID) 48 | require.True(t, ok) 49 | 50 | if expPass { 51 | require.Equal(t, govTypes.StatusPassed.String(), proposal.Status.String()) 52 | } else { 53 | require.NotEqual(t, govTypes.StatusPassed.String(), proposal.Status.String()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /e2e/testing/client.go: -------------------------------------------------------------------------------- 1 | package e2eTesting 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/cosmos/cosmos-sdk/codec" 8 | abci "github.com/tendermint/tendermint/abci/types" 9 | "google.golang.org/grpc" 10 | 11 | "github.com/dymensionxyz/rollapp-wasm/app" 12 | ) 13 | 14 | var _ grpc.ClientConnInterface = (*grpcClient)(nil) 15 | 16 | type grpcClient struct { 17 | app *app.App 18 | } 19 | 20 | func (c grpcClient) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error { 21 | req := args.(codec.ProtoMarshaler) 22 | resp := c.app.Query(abci.RequestQuery{ 23 | Data: c.app.AppCodec().MustMarshal(req), 24 | Path: method, 25 | Height: 0, // TODO: heightened queries 26 | Prove: false, 27 | }) 28 | 29 | if resp.Code != abci.CodeTypeOK { 30 | return fmt.Errorf("query response: %s", resp.Log) 31 | } 32 | 33 | c.app.AppCodec().MustUnmarshal(resp.Value, reply.(codec.ProtoMarshaler)) 34 | 35 | return nil 36 | } 37 | 38 | func (c grpcClient) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { 39 | panic("not supported") 40 | } 41 | 42 | func (chain *TestChain) Client() grpc.ClientConnInterface { 43 | return grpcClient{app: chain.app} 44 | } 45 | -------------------------------------------------------------------------------- /e2e/testing/common.go: -------------------------------------------------------------------------------- 1 | package e2eTesting 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | wasmKeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" 9 | abci "github.com/tendermint/tendermint/abci/types" 10 | "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" 11 | cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" 12 | sdk "github.com/cosmos/cosmos-sdk/types" 13 | ) 14 | 15 | // GetStringEventAttribute returns TX response event attribute string value by type and attribute key. 16 | func GetStringEventAttribute(events []abci.Event, eventType, attrKey string) string { 17 | for _, event := range events { 18 | if event.Type != eventType { 19 | continue 20 | } 21 | 22 | for _, attr := range event.Attributes { 23 | if string(attr.Key) != attrKey { 24 | continue 25 | } 26 | 27 | attrValue := string(attr.Value) 28 | if valueUnquoted, err := strconv.Unquote(string(attr.Value)); err == nil { 29 | attrValue = valueUnquoted 30 | } 31 | 32 | return attrValue 33 | } 34 | } 35 | 36 | return "" 37 | } 38 | 39 | // GenAccounts generates a list of accounts and private keys for them. 40 | func GenAccounts(num uint) ([]sdk.AccAddress, []cryptotypes.PrivKey) { 41 | addrs := make([]sdk.AccAddress, 0, num) 42 | privKeys := make([]cryptotypes.PrivKey, 0, num) 43 | 44 | for i := 0; i < cap(addrs); i++ { 45 | privKey := secp256k1.GenPrivKey() 46 | 47 | addrs = append(addrs, sdk.AccAddress(privKey.PubKey().Address())) 48 | privKeys = append(privKeys, privKey) 49 | } 50 | 51 | return addrs, privKeys 52 | } 53 | 54 | // GenContractAddresses generates a list of contract addresses (codeID and instanceID are sequential). 55 | func GenContractAddresses(num uint) []sdk.AccAddress { 56 | addrs := make([]sdk.AccAddress, 0, num) 57 | 58 | for i := 0; i < cap(addrs); i++ { 59 | contractAddr := wasmKeeper.BuildContractAddressClassic(uint64(i), uint64(i)) 60 | addrs = append(addrs, contractAddr) 61 | } 62 | 63 | return addrs 64 | } 65 | 66 | // HumanizeCoins returns the sdk.Coins string representation with a number of decimals specified. 67 | // 1123000stake -> 1.123stake with 6 decimals (3 numbers after the dot is hardcoded). 68 | func HumanizeCoins(decimals uint8, coins ...sdk.Coin) string { 69 | baseDec := sdk.NewDecWithPrec(1, int64(decimals)) 70 | 71 | strs := make([]string, 0, len(coins)) 72 | for _, coin := range coins { 73 | amtDec := sdk.NewDecFromInt(coin.Amount).Mul(baseDec) 74 | amtFloat, _ := amtDec.Float64() 75 | 76 | strs = append(strs, fmt.Sprintf("%.03f%s", amtFloat, coin.Denom)) 77 | } 78 | 79 | return strings.Join(strs, ",") 80 | } 81 | 82 | // HumanizeDecCoins returns the sdk.DecCoins string representation. 83 | // 1000.123456789stake -> 1.123456stake with 3 decimals (6 numbers after the dot is hardcoded). 84 | func HumanizeDecCoins(decimals uint8, coins ...sdk.DecCoin) string { 85 | baseDec := sdk.NewDecWithPrec(1, int64(decimals)) 86 | 87 | strs := make([]string, 0, len(coins)) 88 | for _, coin := range coins { 89 | amtDec := coin.Amount.Mul(baseDec) 90 | amtFloat, _ := amtDec.Float64() 91 | 92 | strs = append(strs, fmt.Sprintf("%.06f%s", amtFloat, coin.Denom)) 93 | } 94 | 95 | return strings.Join(strs, ",") 96 | } 97 | -------------------------------------------------------------------------------- /e2e/testing/types.go: -------------------------------------------------------------------------------- 1 | package e2eTesting 2 | 3 | import ( 4 | cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | // Account keeps a genesis account data. 9 | type Account struct { 10 | Address sdk.AccAddress 11 | PrivKey cryptotypes.PrivKey 12 | } 13 | -------------------------------------------------------------------------------- /pkg/basic_types.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | // Uint64Ptr returns a pointer to the given uint64 value. 4 | func Uint64Ptr(v uint64) *uint64 { 5 | return &v 6 | } 7 | -------------------------------------------------------------------------------- /pkg/cli_args.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | ) 9 | 10 | // ParseAccAddressArg is a helper function to parse an account address CLI argument. 11 | func ParseAccAddressArg(argName, argValue string) (sdk.AccAddress, error) { 12 | addr, err := sdk.AccAddressFromBech32(argValue) 13 | if err != nil { 14 | return sdk.AccAddress{}, fmt.Errorf("parsing %s argument: invalid address: %w", argName, err) 15 | } 16 | 17 | return addr, nil 18 | } 19 | 20 | // ParseUint64Arg is a helper function to parse uint64 CLI argument. 21 | func ParseUint64Arg(argName, argValue string) (uint64, error) { 22 | v, err := strconv.ParseUint(argValue, 10, 64) 23 | if err != nil { 24 | return 0, fmt.Errorf("parsing %s argument: invalid uint64 value: %w", argName, err) 25 | } 26 | 27 | return v, nil 28 | } 29 | 30 | // ParseInt64Arg is a helper function to parse int64 CLI argument. 31 | func ParseInt64Arg(argName, argValue string) (int64, error) { 32 | v, err := strconv.ParseInt(argValue, 10, 64) 33 | if err != nil { 34 | return 0, fmt.Errorf("parsing %s argument: invalid uint64 value: %w", argName, err) 35 | } 36 | 37 | return v, nil 38 | } 39 | 40 | // ParseCoinArg is a helper function to parse uint64 CLI argument. 41 | func ParseCoinArg(argName, argValue string) (sdk.Coin, error) { 42 | deposit, err := sdk.ParseCoinNormalized(argValue) 43 | if err != nil { 44 | return deposit, fmt.Errorf("parsing %s argument: invalid sdk.Coin value: %w", argName, err) 45 | } 46 | return deposit, nil 47 | } 48 | -------------------------------------------------------------------------------- /pkg/coin.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | ) 8 | 9 | // CoinIsZero checks if sdk.Coin is set (not panics in case Amount is nil). 10 | func CoinIsZero(coin sdk.Coin) bool { 11 | if coin.Amount.IsNil() { 12 | return true 13 | } 14 | 15 | return coin.IsZero() 16 | } 17 | 18 | // DecCoinIsZero checks if sdk.DecCoin is set (not panics in case Amount is nil). 19 | func DecCoinIsZero(coin sdk.DecCoin) bool { 20 | if coin.Amount.IsNil() { 21 | return true 22 | } 23 | 24 | return coin.IsZero() 25 | } 26 | 27 | // DecCoinIsNegative checks if sdk.DecCoin is negative (not panics in case Amount is nil). 28 | func DecCoinIsNegative(coin sdk.DecCoin) bool { 29 | if coin.Amount.IsNil() { 30 | return true 31 | } 32 | 33 | return coin.IsNegative() 34 | } 35 | 36 | // ValidateCoin performs a stricter validation of sdk.Coin comparing to the SDK version. 37 | func ValidateCoin(coin sdk.Coin) error { 38 | if err := sdk.ValidateDenom(coin.Denom); err != nil { 39 | return fmt.Errorf("denom: %w", err) 40 | } 41 | if coin.Amount.IsNil() { 42 | return fmt.Errorf("amount: nil") 43 | } 44 | if coin.IsNegative() { 45 | return fmt.Errorf("amount: is negative") 46 | } 47 | 48 | return nil 49 | } 50 | 51 | // ValidateDecCoin performs a stricter validation of sdk.DecCoin comparing to the SDK version. 52 | func ValidateDecCoin(coin sdk.DecCoin) error { 53 | if err := sdk.ValidateDenom(coin.Denom); err != nil { 54 | return fmt.Errorf("denom: %w", err) 55 | } 56 | if coin.Amount.IsNil() { 57 | return fmt.Errorf("amount: nil") 58 | } 59 | if coin.IsNegative() { 60 | return fmt.Errorf("amount: is negative") 61 | } 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/coins.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | ) 6 | 7 | // SplitCoins splits coins in a proportion defined by the ratio. 8 | // CONTRACT: inputs must be valid. 9 | func SplitCoins(coins sdk.Coins, ratio sdk.Dec) (stack1, stack2 sdk.Coins) { 10 | stack1 = sdk.NewCoins() 11 | stack2 = sdk.NewCoins() 12 | 13 | for _, coin := range coins { 14 | stack1Coin := sdk.Coin{ 15 | Denom: coin.Denom, 16 | Amount: sdk.NewDecFromInt(coin.Amount).Mul(ratio).TruncateInt(), 17 | } 18 | stack2Coin := coin.Sub(stack1Coin) 19 | 20 | stack1 = stack1.Add(stack1Coin) 21 | stack2 = stack2.Add(stack2Coin) 22 | } 23 | 24 | return 25 | } 26 | -------------------------------------------------------------------------------- /pkg/coins_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestSplitCoins(t *testing.T) { 13 | type testCase struct { 14 | coins string 15 | ratio string 16 | stack1Expected string 17 | stack2Expected string 18 | } 19 | 20 | testCases := []testCase{ 21 | { 22 | coins: "100uatom", 23 | ratio: "0.5", 24 | stack1Expected: "50uatom", 25 | stack2Expected: "50uatom", 26 | }, 27 | { 28 | coins: "100uatom", 29 | ratio: "0.75", 30 | stack1Expected: "75uatom", 31 | stack2Expected: "25uatom", 32 | }, 33 | { 34 | coins: "11uatom", 35 | ratio: "0.50", 36 | stack1Expected: "5uatom", 37 | stack2Expected: "6uatom", 38 | }, 39 | { 40 | coins: "13uatom,20ubtc", 41 | ratio: "0.25", 42 | stack1Expected: "3uatom,5ubtc", 43 | stack2Expected: "10uatom,15ubtc", 44 | }, 45 | { 46 | coins: "13uatom,20ubtc", 47 | ratio: "1.0", 48 | stack1Expected: "13uatom,20ubtc", 49 | stack2Expected: "", 50 | }, 51 | } 52 | 53 | for _, tc := range testCases { 54 | t.Run(fmt.Sprintf("%s -> {%s , %s} with %s", tc.coins, tc.stack1Expected, tc.stack2Expected, tc.ratio), func(t *testing.T) { 55 | coins, err := sdk.ParseCoinsNormalized(tc.coins) 56 | require.NoError(t, err) 57 | 58 | ratio, err := sdk.NewDecFromStr(tc.ratio) 59 | require.NoError(t, err) 60 | 61 | stack1Expected, err := sdk.ParseCoinsNormalized(tc.stack1Expected) 62 | require.NoError(t, err) 63 | 64 | stack2Expected, err := sdk.ParseCoinsNormalized(tc.stack2Expected) 65 | require.NoError(t, err) 66 | 67 | stack1Received, stack2Received := SplitCoins(coins, ratio) 68 | if tc.stack1Expected == "" { 69 | assert.True(t, stack1Received.Empty()) 70 | } 71 | if tc.stack2Expected == "" { 72 | assert.True(t, stack2Received.Empty()) 73 | } 74 | 75 | assert.ElementsMatch(t, stack1Expected, stack1Received) 76 | assert.ElementsMatch(t, stack2Expected, stack2Received) 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /pkg/dec.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import sdk "github.com/cosmos/cosmos-sdk/types" 4 | 5 | // NewDecFromUint64 converts a uint64 value to the sdk.Dec. 6 | func NewDecFromUint64(v uint64) sdk.Dec { 7 | return sdk.NewDecFromInt(sdk.NewIntFromUint64(v)) 8 | } 9 | -------------------------------------------------------------------------------- /pkg/openapiconsole/console.go: -------------------------------------------------------------------------------- 1 | package openapiconsole 2 | 3 | import ( 4 | "embed" 5 | "html/template" 6 | "net/http" 7 | ) 8 | 9 | //go:embed index.tpl 10 | var index embed.FS 11 | 12 | // Handler returns an http handler that servers OpenAPI console for an OpenAPI spec at specURL. 13 | func Handler(title, specURL string) http.HandlerFunc { 14 | t, _ := template.ParseFS(index, "index.tpl") 15 | 16 | return func(w http.ResponseWriter, req *http.Request) { 17 | _ = t.Execute(w, struct { 18 | Title string 19 | URL string 20 | }{ 21 | title, 22 | specURL, 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/openapiconsole/index.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ .Title }} 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 24 | 25 | -------------------------------------------------------------------------------- /pkg/testutils/ante.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | ) 6 | 7 | // NoopAnteHandler implements the no-op AnteHandler. 8 | func NoopAnteHandler(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { 9 | return ctx, nil 10 | } 11 | -------------------------------------------------------------------------------- /pkg/testutils/msg.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | ) 6 | 7 | var _ sdk.Msg = (*MockMsg)(nil) 8 | 9 | // MockMsg is a dummy sdk.Msg. 10 | type MockMsg struct{} 11 | 12 | // NewMockMsg creates a new MockMsg. 13 | func NewMockMsg() *MockMsg { 14 | return &MockMsg{} 15 | } 16 | 17 | // Reset implements the proto.Message interface. 18 | func (msg MockMsg) Reset() {} 19 | 20 | // String implements the proto.Message interface. 21 | func (msg MockMsg) String() string { 22 | return "" 23 | } 24 | 25 | // ProtoMessage implements the proto.Message interface. 26 | func (msg MockMsg) ProtoMessage() {} 27 | 28 | // ValidateBasic implements the sdk.Msg interface. 29 | func (msg MockMsg) ValidateBasic() error { 30 | return nil 31 | } 32 | 33 | // GetSigners implements the sdk.Msg interface. 34 | func (msg MockMsg) GetSigners() []sdk.AccAddress { 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /pkg/testutils/tx.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | ) 6 | 7 | var _ sdk.FeeTx = MockFeeTx{} 8 | 9 | // MockFeeTx is a mock implementation of sdk.FeeTx. 10 | type MockFeeTx struct { 11 | fees sdk.Coins 12 | gas uint64 13 | msgs []sdk.Msg 14 | feePayer sdk.AccAddress 15 | feeGranter sdk.AccAddress 16 | } 17 | 18 | type MockFeeTxOption func(tx *MockFeeTx) 19 | 20 | // WithMockFeeTxFees option sets the fees of the MockFeeTx. 21 | func WithMockFeeTxFees(fees sdk.Coins) MockFeeTxOption { 22 | return func(tx *MockFeeTx) { 23 | tx.fees = fees 24 | } 25 | } 26 | 27 | // WithMockFeeTxMsgs option sets the msgs of the MockFeeTx. 28 | func WithMockFeeTxMsgs(msgs ...sdk.Msg) MockFeeTxOption { 29 | return func(tx *MockFeeTx) { 30 | tx.msgs = msgs 31 | } 32 | } 33 | 34 | // WithMockFeeTxPayer option sets the feePayer of the MockFeeTx. 35 | func WithMockFeeTxPayer(payer sdk.AccAddress) MockFeeTxOption { 36 | return func(tx *MockFeeTx) { 37 | tx.feePayer = payer 38 | } 39 | } 40 | 41 | // WithMockFeeTxGas option sets the gas limit of the MockFeeTx. 42 | func WithMockFeeTxGas(gas uint64) MockFeeTxOption { 43 | return func(tx *MockFeeTx) { 44 | tx.gas = gas 45 | } 46 | } 47 | 48 | // NewMockFeeTx creates a new MockFeeTx instance. 49 | // CONTRACT: tx has no defaults, so it is up to a developer to set options right. 50 | func NewMockFeeTx(opts ...MockFeeTxOption) MockFeeTx { 51 | tx := MockFeeTx{} 52 | for _, opt := range opts { 53 | opt(&tx) 54 | } 55 | 56 | return tx 57 | } 58 | 59 | // GetMsgs implemets the sdk.Tx interface. 60 | func (tx MockFeeTx) GetMsgs() []sdk.Msg { 61 | return tx.msgs 62 | } 63 | 64 | // ValidateBasic implemets the sdk.Tx interface. 65 | func (tx MockFeeTx) ValidateBasic() error { 66 | return nil 67 | } 68 | 69 | // GetGas implements the sdk.FeeTx interface. 70 | func (tx MockFeeTx) GetGas() uint64 { 71 | return tx.gas 72 | } 73 | 74 | // GetFee implements the sdk.FeeTx interface. 75 | func (tx MockFeeTx) GetFee() sdk.Coins { 76 | return tx.fees 77 | } 78 | 79 | // FeePayer implements the sdk.FeeTx interface. 80 | func (tx MockFeeTx) FeePayer() sdk.AccAddress { 81 | return tx.feePayer 82 | } 83 | 84 | // FeeGranter implements the sdk.FeeTx interface. 85 | func (tx MockFeeTx) FeeGranter() sdk.AccAddress { 86 | return tx.feeGranter 87 | } 88 | -------------------------------------------------------------------------------- /pkg/testutils/wasmd.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | wasmKeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" 5 | wasmdTypes "github.com/CosmWasm/wasmd/x/wasm/types" 6 | wasmVmTypes "github.com/CosmWasm/wasmvm/types" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | ) 9 | 10 | var _ wasmKeeper.Messenger = (*MockMessenger)(nil) 11 | 12 | // MockContractViewer mocks x/wasmd module dependency. 13 | // Mock returns a contract info if admin is set. 14 | type MockContractViewer struct { 15 | contractAdminSet map[string]string // key: contractAddr, value: adminAddr 16 | returnSudoError error 17 | } 18 | 19 | // NewMockContractViewer creates a new MockContractViewer instance. 20 | func NewMockContractViewer() *MockContractViewer { 21 | return &MockContractViewer{ 22 | contractAdminSet: make(map[string]string), 23 | returnSudoError: nil, 24 | } 25 | } 26 | 27 | // AddContractAdmin adds a contract admin link. 28 | func (v *MockContractViewer) AddContractAdmin(contractAddr, adminAddr string) { 29 | v.contractAdminSet[contractAddr] = adminAddr 30 | } 31 | 32 | // GetContractInfo returns a contract info if admin is set. 33 | func (v MockContractViewer) GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmdTypes.ContractInfo { 34 | adminAddr, found := v.contractAdminSet[contractAddress.String()] 35 | if !found { 36 | return nil 37 | } 38 | 39 | return &wasmdTypes.ContractInfo{ 40 | Admin: adminAddr, 41 | } 42 | } 43 | 44 | // HasContractInfo returns true if admin is set. 45 | func (v MockContractViewer) HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool { 46 | _, found := v.contractAdminSet[contractAddress.String()] 47 | return found 48 | } 49 | 50 | // Sudo implements the wasmKeeper.ContractInfoViewer interface. 51 | func (v MockContractViewer) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) { 52 | return nil, v.returnSudoError 53 | } 54 | 55 | func (v *MockContractViewer) SetReturnSudoError(returnSudoError error) { 56 | v.returnSudoError = returnSudoError 57 | } 58 | 59 | // MockMessenger mocks x/wasmd module dependency. 60 | type MockMessenger struct{} 61 | 62 | // NewMockMessenger creates a new MockMessenger instance. 63 | func NewMockMessenger() *MockMessenger { 64 | return &MockMessenger{} 65 | } 66 | 67 | // DispatchMsg implements the wasmKeeper.Messenger interface. 68 | func (m MockMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmVmTypes.CosmosMsg) ([]sdk.Event, [][]byte, error) { 69 | return nil, nil, nil 70 | } 71 | -------------------------------------------------------------------------------- /pkg/utils.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | storetypes "github.com/cosmos/cosmos-sdk/store/types" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 10 | ) 11 | 12 | // ExecuteWithGasLimit executes a function with a gas limit. Taken from: https://github.com/cosmos/cosmos-sdk/pull/18475 13 | func ExecuteWithGasLimit(ctx sdk.Context, gasLimit uint64, f func(ctx sdk.Context) error) (gasUsed uint64, err error) { 14 | branchedCtx, commit := ctx.CacheContext() 15 | // create a new gas meter 16 | limitedGasMeter := storetypes.NewGasMeter(gasLimit) 17 | // apply gas meter with limit to branched context 18 | branchedCtx = branchedCtx.WithGasMeter(limitedGasMeter) 19 | err = catchOutOfGas(branchedCtx, f) 20 | // even before checking the error, we want to get the gas used 21 | // and apply it to the original context. 22 | gasUsed = limitedGasMeter.GasConsumed() 23 | ctx.GasMeter().ConsumeGas(gasUsed, "branch") 24 | // in case of errors, do not commit the branched context 25 | // return gas used and the error 26 | if err != nil { 27 | return gasUsed, err 28 | } 29 | // if no error, commit the branched context 30 | // and return gas used and no error 31 | commit() 32 | return gasUsed, nil 33 | } 34 | 35 | // catchOutOfGas is a helper function to catch out of gas panics and return them as errors. 36 | func catchOutOfGas(ctx sdk.Context, f func(ctx sdk.Context) error) (err error) { 37 | defer func() { 38 | if r := recover(); r != nil { 39 | // we immediately check if it's an out of error gas. 40 | // if it is not we panic again to propagate it up. 41 | if _, ok := r.(storetypes.ErrorOutOfGas); !ok { 42 | _, _ = fmt.Fprintf(os.Stderr, "recovered: %#v", r) // log to stderr 43 | panic(r) 44 | } 45 | err = sdkerrors.ErrOutOfGas 46 | } 47 | }() 48 | return f(ctx) 49 | } 50 | -------------------------------------------------------------------------------- /proto/buf.gen.gogo.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | plugins: 3 | - name: gocosmos 4 | out: . 5 | opt: plugins=grpc,Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types 6 | - name: grpc-gateway 7 | out: . 8 | opt: logtostderr=true,allow_colon_final_segments=true -------------------------------------------------------------------------------- /proto/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | deps: 4 | - remote: buf.build 5 | owner: cosmos 6 | repository: cosmos-proto 7 | commit: 1935555c206d4afb9e94615dfd0fad31 8 | digest: shake256:c74d91a3ac7ae07d579e90eee33abf9b29664047ac8816500cf22c081fec0d72d62c89ce0bebafc1f6fec7aa5315be72606717740ca95007248425102c365377 9 | - remote: buf.build 10 | owner: cosmos 11 | repository: cosmos-sdk 12 | commit: 44216b8eb0de471187e76ee6e9953354 13 | digest: shake256:61c30bbaec7580022a6d6b4832100a5f96f62ae7587605a29d8e96cfd5a84ca5c6d793b7fa9ee09b1b22b7fd08aba36c531c05cb6d1e5047c426a72f9d38a947 14 | - remote: buf.build 15 | owner: cosmos 16 | repository: gogo-proto 17 | commit: 5e5b9fdd01804356895f8f79a6f1ddc1 18 | digest: shake256:0b85da49e2e5f9ebc4806eae058e2f56096ff3b1c59d1fb7c190413dd15f45dd456f0b69ced9059341c80795d2b6c943de15b120a9e0308b499e43e4b5fc2952 19 | - remote: buf.build 20 | owner: googleapis 21 | repository: googleapis 22 | commit: cc916c31859748a68fd229a3c8d7a2e8 23 | digest: shake256:469b049d0eb04203d5272062636c078decefc96fec69739159c25d85349c50c34c7706918a8b216c5c27f76939df48452148cff8c5c3ae77fa6ba5c25c1b8bf8 24 | -------------------------------------------------------------------------------- /proto/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | name: buf.build/dymensionxyz/rollapp 3 | deps: 4 | - buf.build/cosmos/cosmos-proto 5 | - buf.build/cosmos/cosmos-sdk 6 | - buf.build/cosmos/gogo-proto 7 | - buf.build/googleapis/googleapis 8 | breaking: 9 | use: 10 | - FILE 11 | lint: 12 | use: 13 | - DEFAULT 14 | - COMMENTS 15 | - FILE_LOWER_SNAKE_CASE 16 | except: 17 | - UNARY_RPC 18 | - COMMENT_FIELD 19 | - SERVICE_SUFFIX 20 | - PACKAGE_VERSION_SUFFIX 21 | - RPC_REQUEST_STANDARD_NAME 22 | -------------------------------------------------------------------------------- /proto/rollapp/callback/v1/errors.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rollapp.callback.v1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | 6 | option go_package = "github.com/dymensionxyz/rollapp-wasm/x/callback/types"; 7 | 8 | // ModuleErrors defines the module level error codes 9 | enum ModuleErrors { 10 | // ERR_UNKNOWN is the default error code 11 | ERR_UNKNOWN = 0; 12 | // ERR_OUT_OF_GAS is the error code when the contract callback exceeds the gas limit allowed by the module 13 | ERR_OUT_OF_GAS = 1; 14 | // ERR_CONTRACT_EXECUTION_FAILED is the error code when the contract callback execution fails 15 | ERR_CONTRACT_EXECUTION_FAILED = 2; 16 | } -------------------------------------------------------------------------------- /proto/rollapp/callback/v1/events.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rollapp.callback.v1; 3 | 4 | option go_package = "github.com/dymensionxyz/rollapp-wasm/x/callback/types"; 5 | 6 | import "rollapp/callback/v1/callback.proto"; 7 | import "gogoproto/gogo.proto"; 8 | import "cosmos/base/v1beta1/coin.proto"; 9 | import "cosmos_proto/cosmos.proto"; 10 | 11 | // CallbackRegisteredEvent is emitted when a callback is registered. 12 | message CallbackRegisteredEvent { 13 | // contract_address is the address of the contract for which callback is being registered (bech32 encoded). 14 | string contract_address = 1; 15 | // job_id is an identifier of the callback. 16 | uint64 job_id = 2; 17 | // callback_height is the height at which the callback is executed. 18 | int64 callback_height = 3; 19 | // fee_split is the breakdown of the fees paid by the contract to reserve the callback 20 | CallbackFeesFeeSplit fee_split = 4; 21 | // reserved_by is the address which reserved the callback (bech32 encoded). 22 | string reserved_by = 5; 23 | } 24 | 25 | // CallbackCancelledEvent is emitted when a callback is cancelled. 26 | message CallbackCancelledEvent { 27 | // cancelled_by is the address of the contract whose callback is being cancelled (bech32 encoded) 28 | string cancelled_by = 1; 29 | // contract_address is the address of the contract (bech32 encoded) 30 | string contract_address = 2; 31 | // job_id is an identifier the callback requestor had passed during registration of the callback 32 | uint64 job_id = 3; 33 | // callback_height is the height at which the callback requestor had registered the callback 34 | int64 callback_height = 4; 35 | // refund_amount is the amount of fees which was refunded on cancellation 36 | cosmos.base.v1beta1.Coin refund_amount = 5 [ (gogoproto.nullable) = false ]; 37 | } 38 | 39 | // CallbackExecutedSuccessEvent is emitted when a callback is executed successfully. 40 | message CallbackExecutedSuccessEvent { 41 | // contract_address is the address of the contract for which callback is being executed (bech32 encoded). 42 | string contract_address = 1; 43 | // job_id is an identifier of the callback. 44 | uint64 job_id = 2; 45 | // sudo_msg is the input passed by the module to the contract 46 | string sudo_msg = 3; 47 | // gas_used is the amount of gas consumed during the callback execution 48 | uint64 gas_used = 4; 49 | } 50 | 51 | // CallbackExecutedFailedEvent is emitted when a callback execution fails. 52 | message CallbackExecutedFailedEvent { 53 | // contract_address is the address of the contract for which callback is being executed (bech32 encoded). 54 | string contract_address = 1; 55 | // job_id is an identifier of the callback. 56 | uint64 job_id = 2; 57 | // sudo_msg is the input passed by the module to the contract 58 | string sudo_msg = 3; 59 | // gas_used is the amount of gas consumed during the callback execution 60 | uint64 gas_used = 4; 61 | // error is the error returned during the callback execution 62 | string error = 5; 63 | } 64 | -------------------------------------------------------------------------------- /proto/rollapp/callback/v1/genesis.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rollapp.callback.v1; 3 | 4 | option go_package = "github.com/dymensionxyz/rollapp-wasm/x/callback/types"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | import "cosmos/base/v1beta1/coin.proto"; 8 | import "rollapp/callback/v1/callback.proto"; 9 | 10 | // GenesisState defines the initial state of the callback module. 11 | message GenesisState { 12 | // params defines all the module parameters. 13 | Params params = 1 [ (gogoproto.nullable) = false ]; 14 | // callbacks defines all the callbacks which are yet to be executed 15 | repeated Callback callbacks = 2; 16 | } 17 | -------------------------------------------------------------------------------- /proto/rollapp/callback/v1/query.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rollapp.callback.v1; 3 | 4 | option go_package = "github.com/dymensionxyz/rollapp-wasm/x/callback/types"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | import "google/api/annotations.proto"; 8 | import "cosmos/base/v1beta1/coin.proto"; 9 | import "rollapp/callback/v1/callback.proto"; 10 | 11 | // Query service for the callback module. 12 | service Query { 13 | // Params returns module parameters 14 | rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { 15 | option (google.api.http).get = "/rollapp/callback/v1/params"; 16 | } 17 | // EstimateCallbackFees returns the total amount of callback fees a contract needs to pay to register the callback 18 | rpc EstimateCallbackFees(QueryEstimateCallbackFeesRequest) returns (QueryEstimateCallbackFeesResponse) { 19 | option (google.api.http).get = "/rollapp/callback/v1/estimate_callback_fees"; 20 | } 21 | // Callbacks returns all the callbacks registered at a given height 22 | rpc Callbacks(QueryCallbacksRequest) returns (QueryCallbacksResponse) { 23 | option (google.api.http).get = "/rollapp/callback/v1/callbacks"; 24 | } 25 | } 26 | 27 | // QueryParamsRequest is the request for Query.Params. 28 | message QueryParamsRequest {} 29 | 30 | // QueryParamsResponse is the response for Query.Params. 31 | message QueryParamsResponse { 32 | // params defines all the module parameters. 33 | Params params = 1 [ (gogoproto.nullable) = false ]; 34 | } 35 | 36 | // QueryEstimateCallbackFeesRequest is the request for Query.EstimateCallbackFees. 37 | message QueryEstimateCallbackFeesRequest{ 38 | // block_height is the height at which to estimate the callback fees 39 | int64 block_height = 1; 40 | } 41 | 42 | // QueryEstimateCallbackFeesResponse is the response for Query.EstimateCallbackFees. 43 | message QueryEstimateCallbackFeesResponse{ 44 | // total_fees is the total fees that needs to be paid by the contract to reserve a callback 45 | cosmos.base.v1beta1.Coin total_fees = 1; 46 | // fee_split is the breakdown of the total_fees 47 | CallbackFeesFeeSplit fee_split = 2; 48 | } 49 | 50 | // QueryCallbacksRequest is the request for Query.Callbacks. 51 | message QueryCallbacksRequest{ 52 | // block_height is the height at which to query the callbacks 53 | int64 block_height = 1; 54 | } 55 | 56 | // QueryCallbacksResponse is the response for Query.Callbacks. 57 | message QueryCallbacksResponse{ 58 | // callbacks is the list of callbacks registered at the given height 59 | repeated Callback callbacks = 1; 60 | } -------------------------------------------------------------------------------- /proto/rollapp/cwerrors/v1/cwerrors.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rollapp.cwerrors.v1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | 6 | option go_package = "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types"; 7 | 8 | // SudoError defines the sudo message for the error callback 9 | message SudoError { 10 | // module_name is the name of the module throwing the error 11 | string module_name = 1; 12 | // error_code is the module level error code 13 | int32 error_code = 2; 14 | // contract_address is the address of the contract which will receive the 15 | // error callback 16 | string contract_address = 3; 17 | // input_payload is any input which caused the error 18 | string input_payload = 4; 19 | // error_message is the error message 20 | string error_message = 5; 21 | } 22 | 23 | // ModuleErrors defines the module level error codes 24 | enum ModuleErrors { 25 | // ERR_UNKNOWN is the default error code 26 | ERR_UNKNOWN = 0; 27 | // ERR_CALLBACK_EXECUTION_FAILED is the error code for when the error callback fails 28 | ERR_CALLBACK_EXECUTION_FAILED = 1; 29 | } -------------------------------------------------------------------------------- /proto/rollapp/cwerrors/v1/events.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rollapp.cwerrors.v1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "rollapp/cwerrors/v1/params.proto"; 6 | import "rollapp/cwerrors/v1/cwerrors.proto"; 7 | import "cosmos/base/v1beta1/coin.proto"; 8 | 9 | option go_package = "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types"; 10 | 11 | // ParamsUpdatedEvent defines the event which is thrown when the module 12 | // parameters are updated 13 | message ParamsUpdatedEvent { 14 | // new_params are the new parameters for the module 15 | Params new_params = 1 [ (gogoproto.nullable) = false ]; 16 | } 17 | 18 | // SubscribedToErrorsEvent defines the event which is thrown when a contract 19 | // subscribes to errors 20 | message SubscribedToErrorsEvent { 21 | // sender is the address which initiated the subscription 22 | string sender = 1; 23 | // contract_address is the address of the contract which is subscribed to 24 | // errors 25 | string contract_address = 2; 26 | // fees_paid is the fees paid for the subscription 27 | cosmos.base.v1beta1.Coin fees_paid = 3 [ (gogoproto.nullable) = false ]; 28 | // subscription_valid_till is the block height till which the subscription is 29 | // valid 30 | int64 subscription_valid_till = 4; 31 | } 32 | 33 | // StoringErrorEvent defines the event which is thrown when an error is stored 34 | message StoringErrorEvent { 35 | // error is the error which is stored 36 | SudoError error = 1 [ (gogoproto.nullable) = false ]; 37 | // deletion_block_height is the block height at which the error will be pruned 38 | // from the state 39 | int64 deletion_block_height = 2; 40 | } 41 | 42 | // SudoErrorCallbackFailedEvent defines the event which is thrown when a sudo 43 | // error callback fails 44 | message SudoErrorCallbackFailedEvent { 45 | // error is the error for which the callback is executed 46 | SudoError error = 1 [ (gogoproto.nullable) = false ]; 47 | // callback_error_message is the error message of why the callback failed 48 | string callback_error_message = 2; 49 | } -------------------------------------------------------------------------------- /proto/rollapp/cwerrors/v1/genesis.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rollapp.cwerrors.v1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "rollapp/cwerrors/v1/params.proto"; 6 | import "rollapp/cwerrors/v1/cwerrors.proto"; 7 | 8 | option go_package = "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types"; 9 | 10 | // GenesisState defines the cwerrors module's genesis state. 11 | message GenesisState { 12 | // params defines all the module parameters. 13 | Params params = 1 [ (gogoproto.nullable) = false ]; 14 | // errors defines all the sudo errors currently registered. 15 | repeated SudoError errors = 2 [ (gogoproto.nullable) = false ]; 16 | } -------------------------------------------------------------------------------- /proto/rollapp/cwerrors/v1/params.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rollapp.cwerrors.v1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "cosmos/base/v1beta1/coin.proto"; 6 | 7 | option go_package = "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types"; 8 | 9 | // Params defines the set of parameters for the cwerrors module. 10 | message Params { 11 | // error_stored_time is the block height until which error is stored 12 | int64 error_stored_time = 1; 13 | // subsciption_fee is the fee required to subscribe to error callbacks 14 | cosmos.base.v1beta1.Coin subscription_fee = 2 15 | [ (gogoproto.nullable) = false ]; 16 | // subscription_period is the period for which the subscription is valid 17 | int64 subscription_period = 3; 18 | } -------------------------------------------------------------------------------- /proto/rollapp/cwerrors/v1/query.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rollapp.cwerrors.v1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "google/api/annotations.proto"; 6 | import "cosmos/base/v1beta1/coin.proto"; 7 | import "rollapp/cwerrors/v1/cwerrors.proto"; 8 | import "rollapp/cwerrors/v1/params.proto"; 9 | 10 | option go_package = "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types"; 11 | 12 | // Query service for the cwerrors module. 13 | service Query { 14 | // Params queries all the module parameters. 15 | rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { 16 | option (google.api.http).get = "/rollapp/cwerrors/v1/params"; 17 | } 18 | 19 | // Errors queries all the errors for a given contract. 20 | rpc Errors(QueryErrorsRequest) returns (QueryErrorsResponse) { 21 | option (google.api.http).get = "/rollapp/cwerrors/v1/errors"; 22 | } 23 | 24 | // IsSubscribed queries if a contract is subscribed to sudo error callbacks. 25 | rpc IsSubscribed(QueryIsSubscribedRequest) 26 | returns (QueryIsSubscribedResponse) { 27 | option (google.api.http).get = "/rollapp/cwerrors/v1/is_subscribed"; 28 | } 29 | } 30 | 31 | // QueryParamsRequest is the request for Query.Params. 32 | message QueryParamsRequest {} 33 | 34 | // QueryParamsResponse is the response for Query.Params. 35 | message QueryParamsResponse { 36 | // params defines all the module parameters. 37 | Params params = 1 [ (gogoproto.nullable) = false ]; 38 | } 39 | 40 | // QueryErrorsRequest is the request for Query.Errors. 41 | message QueryErrorsRequest { 42 | // contract_address is the address of the contract whose errors to query for 43 | string contract_address = 1; 44 | } 45 | 46 | // QueryErrorsResponse is the response for Query.Errors. 47 | message QueryErrorsResponse { 48 | // errors defines all the contract errors which will be returned 49 | repeated SudoError errors = 1 [ (gogoproto.nullable) = false ]; 50 | } 51 | 52 | // QueryIsSubscribedRequest is the request for Query.IsSubscribed. 53 | message QueryIsSubscribedRequest { 54 | // contract_address is the address of the contract to query if subscribed 55 | string contract_address = 1; 56 | } 57 | 58 | // QueryIsSubscribedResponse is the response for Query.IsSubscribed. 59 | message QueryIsSubscribedResponse { 60 | // subscribed defines if the contract is subscribed to sudo error callbacks 61 | bool subscribed = 1; 62 | // subscription_valid_till defines the block height till which the 63 | // subscription is valid 64 | int64 subscription_valid_till = 2; 65 | } -------------------------------------------------------------------------------- /proto/rollapp/cwerrors/v1/tx.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rollapp.cwerrors.v1; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "cosmos/msg/v1/msg.proto"; 6 | import "cosmos/base/v1beta1/coin.proto"; 7 | import "rollapp/cwerrors/v1/params.proto"; 8 | import "cosmos_proto/cosmos.proto"; 9 | 10 | option go_package = "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types"; 11 | 12 | // Msg defines the cwerrors Msg service. 13 | service Msg { 14 | // SubscribeToError defines an operation which will register a contract for a 15 | // sudo callback on errors 16 | rpc SubscribeToError(MsgSubscribeToError) 17 | returns (MsgSubscribeToErrorResponse); 18 | 19 | // UpdateParams is used for updating module params. 20 | rpc UpdateParams(MsgUpdateParams) 21 | returns (MsgUpdateParamsResponse); 22 | } 23 | 24 | // MsgSubscribeToError is the Msg/SubscribeToError request type. 25 | message MsgSubscribeToError { 26 | option (cosmos.msg.v1.signer) = "sender"; 27 | // sender is the address of who is registering the contarcts for callback on 28 | // error 29 | string sender = 1; 30 | // contract is the address of the contract that will be called on error 31 | string contract_address = 2; 32 | // fee is the subscription fee for the feature (current no fee is charged for 33 | // this feature) 34 | cosmos.base.v1beta1.Coin fee = 3 [ (gogoproto.nullable) = false ]; 35 | } 36 | 37 | // MsgSubscribeToErrorResponse defines the response structure for executing a 38 | // MsgSubscribeToError message. 39 | message MsgSubscribeToErrorResponse { 40 | // subscription_valid_till is the block height till which the subscription is 41 | // valid 42 | int64 subscription_valid_till = 1; 43 | } 44 | 45 | // MsgUpdateParams is the Msg/UpdateParams request type. 46 | message MsgUpdateParams { 47 | option (cosmos.msg.v1.signer) = "authority"; 48 | 49 | // authority is the address that controls the module (defaults to x/gov unless overwritten). 50 | string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; 51 | // NOTE: All parameters must be supplied. 52 | Params params = 2 [(gogoproto.nullable) = false]; 53 | } 54 | 55 | // MsgUpdateParamsResponse defines the response structure for executing a MsgUpdateParams message. 56 | message MsgUpdateParamsResponse {} -------------------------------------------------------------------------------- /proto/rollapp/wasm/authz.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rollapp.wasm; 3 | 4 | import "gogoproto/gogo.proto"; 5 | import "cosmos_proto/cosmos.proto"; 6 | import "cosmos/base/v1beta1/coin.proto"; 7 | 8 | option go_package = "github.com/dymensionxyz/rollapp-wasm/x/wasm"; 9 | 10 | // ContractExecutionAuthorization defines authorization for wasm execute. 11 | message ContractExecutionAuthorization { 12 | option (cosmos_proto.implements_interface) = "cosmos.authz.v1beta1.Authorization"; 13 | 14 | // Contracts is a list of allowed contracts. Optional. 15 | repeated string contracts = 1; 16 | 17 | // SpendLimits defines spending limits for contracts interactions. 18 | repeated cosmos.base.v1beta1.Coin spend_limit = 2 [ 19 | (gogoproto.nullable) = false, 20 | (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" 21 | ]; 22 | } -------------------------------------------------------------------------------- /ra_wasm_be_rpc/backend/backend.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "context" 5 | "github.com/bcdevtools/block-explorer-rpc-cosmos/be_rpc/config" 6 | "github.com/cosmos/cosmos-sdk/client" 7 | "github.com/cosmos/cosmos-sdk/server" 8 | sequencerstypes "github.com/dymensionxyz/dymension-rdk/x/sequencers/types" 9 | rawberpctypes "github.com/dymensionxyz/rollapp-wasm/ra_wasm_be_rpc/types" 10 | "github.com/tendermint/tendermint/libs/log" 11 | ) 12 | 13 | type RollAppWasmBackendI interface { 14 | // Misc 15 | 16 | GetSequencersModuleParams() (*sequencerstypes.Params, error) 17 | } 18 | 19 | var _ RollAppWasmBackendI = (*RollAppWasmBackend)(nil) 20 | 21 | // RollAppWasmBackend implements the RollAppWasmBackendI interface 22 | type RollAppWasmBackend struct { 23 | ctx context.Context 24 | clientCtx client.Context 25 | queryClient *rawberpctypes.QueryClient // gRPC query client 26 | logger log.Logger 27 | cfg config.BeJsonRpcConfig 28 | } 29 | 30 | // NewRollAppWasmBackend creates a new RollAppWasmBackend instance for RollApp EVM Block Explorer 31 | func NewRollAppWasmBackend( 32 | ctx *server.Context, 33 | logger log.Logger, 34 | clientCtx client.Context, 35 | ) *RollAppWasmBackend { 36 | appConf, err := config.GetConfig(ctx.Viper) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | return &RollAppWasmBackend{ 42 | ctx: context.Background(), 43 | clientCtx: clientCtx, 44 | queryClient: rawberpctypes.NewQueryClient(clientCtx), 45 | logger: logger.With("module", "raw_be_rpc"), 46 | cfg: appConf, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ra_wasm_be_rpc/backend/rollapp_wasm_interceptor.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | berpcbackend "github.com/bcdevtools/block-explorer-rpc-cosmos/be_rpc/backend" 5 | berpctypes "github.com/bcdevtools/block-explorer-rpc-cosmos/be_rpc/types" 6 | "github.com/pkg/errors" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | var _ berpcbackend.RequestInterceptor = (*RollAppWasmRequestInterceptor)(nil) 12 | 13 | type RollAppWasmRequestInterceptor struct { 14 | beRpcBackend berpcbackend.BackendI 15 | backend RollAppWasmBackendI 16 | defaultInterceptor berpcbackend.RequestInterceptor 17 | } 18 | 19 | func NewRollAppWasmRequestInterceptor( 20 | beRpcBackend berpcbackend.BackendI, 21 | backend RollAppWasmBackendI, 22 | defaultInterceptor berpcbackend.RequestInterceptor, 23 | ) *RollAppWasmRequestInterceptor { 24 | return &RollAppWasmRequestInterceptor{ 25 | beRpcBackend: beRpcBackend, 26 | backend: backend, 27 | defaultInterceptor: defaultInterceptor, 28 | } 29 | } 30 | 31 | func (m *RollAppWasmRequestInterceptor) GetTransactionByHash(hashStr string) (intercepted bool, response berpctypes.GenericBackendResponse, err error) { 32 | // handled completely by the default interceptor 33 | return m.defaultInterceptor.GetTransactionByHash(hashStr) 34 | } 35 | 36 | func (m *RollAppWasmRequestInterceptor) GetDenomsInformation() (intercepted, append bool, denoms map[string]string, err error) { 37 | // handled completely by the default interceptor 38 | return m.defaultInterceptor.GetDenomsInformation() 39 | } 40 | 41 | func (m *RollAppWasmRequestInterceptor) GetModuleParams(moduleName string) (intercepted bool, res berpctypes.GenericBackendResponse, err error) { 42 | var params any 43 | 44 | switch moduleName { 45 | case "sequencers": 46 | sequencersParams, errFetch := m.backend.GetSequencersModuleParams() 47 | if errFetch != nil { 48 | err = errors.Wrap(errFetch, "failed to get sequencers params") 49 | } else { 50 | params = *sequencersParams 51 | } 52 | default: 53 | return m.defaultInterceptor.GetModuleParams(moduleName) 54 | } 55 | 56 | if err != nil { 57 | return 58 | } 59 | 60 | res, err = berpctypes.NewGenericBackendResponseFrom(params) 61 | if err != nil { 62 | err = status.Error(codes.Internal, errors.Wrap(err, "module params").Error()) 63 | return 64 | } 65 | 66 | intercepted = true 67 | return 68 | } 69 | 70 | func (m *RollAppWasmRequestInterceptor) GetAccount(accountAddressStr string) (intercepted, append bool, response berpctypes.GenericBackendResponse, err error) { 71 | // handled completely by the default interceptor 72 | return m.defaultInterceptor.GetAccount(accountAddressStr) 73 | } 74 | -------------------------------------------------------------------------------- /ra_wasm_be_rpc/backend/sequencers.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | sequencerstypes "github.com/dymensionxyz/dymension-rdk/x/sequencers/types" 5 | ) 6 | 7 | func (m *RollAppWasmBackend) GetSequencersModuleParams() (*sequencerstypes.Params, error) { 8 | res, err := m.queryClient.SequencersQueryClient.Params(m.ctx, &sequencerstypes.QueryParamsRequest{}) 9 | if err != nil { 10 | return nil, err 11 | } 12 | return &res.Params, nil 13 | } 14 | -------------------------------------------------------------------------------- /ra_wasm_be_rpc/namespaces/raw/api.go: -------------------------------------------------------------------------------- 1 | package raw 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cosmos/cosmos-sdk/server" 6 | rawberpcbackend "github.com/dymensionxyz/rollapp-wasm/ra_wasm_be_rpc/backend" 7 | "github.com/tendermint/tendermint/libs/log" 8 | ) 9 | 10 | // RPC namespaces and API version 11 | const ( 12 | DymRollAppWasmBlockExplorerNamespace = "raw" 13 | 14 | ApiVersion = "1.0" 15 | ) 16 | 17 | // API is the RollApp Wasm Block Explorer JSON-RPC. 18 | // Developers can create custom API for the chain. 19 | type API struct { 20 | ctx *server.Context 21 | logger log.Logger 22 | backend rawberpcbackend.RollAppWasmBackendI 23 | } 24 | 25 | // NewRollAppWasmAPI creates an instance of the RollApp Wasm Block Explorer API. 26 | func NewRollAppWasmAPI( 27 | ctx *server.Context, 28 | backend rawberpcbackend.RollAppWasmBackendI, 29 | ) *API { 30 | return &API{ 31 | ctx: ctx, 32 | logger: ctx.Logger.With("api", "raw"), 33 | backend: backend, 34 | } 35 | } 36 | 37 | func (api *API) Echo(text string) string { 38 | api.logger.Debug("raw_echo") 39 | return fmt.Sprintf("hello \"%s\" from RollApp Wasm Block Explorer API", text) 40 | } 41 | -------------------------------------------------------------------------------- /ra_wasm_be_rpc/types/query_client.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/client" 5 | "github.com/cosmos/cosmos-sdk/types/tx" 6 | banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" 7 | epochstypes "github.com/dymensionxyz/dymension-rdk/x/epochs/types" 8 | sequencerstypes "github.com/dymensionxyz/dymension-rdk/x/sequencers/types" 9 | ) 10 | 11 | // QueryClient defines a gRPC Client used for: 12 | // - Transaction simulation 13 | type QueryClient struct { 14 | tx.ServiceClient 15 | 16 | BankQueryClient banktypes.QueryClient 17 | SequencersQueryClient sequencerstypes.QueryClient 18 | EpochQueryClient epochstypes.QueryClient 19 | } 20 | 21 | // NewQueryClient creates a new gRPC query client 22 | func NewQueryClient(clientCtx client.Context) *QueryClient { 23 | return &QueryClient{ 24 | ServiceClient: tx.NewServiceClient(clientCtx), 25 | BankQueryClient: banktypes.NewQueryClient(clientCtx), 26 | SequencersQueryClient: sequencerstypes.NewQueryClient(clientCtx), 27 | EpochQueryClient: epochstypes.NewQueryClient(clientCtx), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rollappd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/cosmos/cosmos-sdk/server" 7 | svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" 8 | 9 | "github.com/dymensionxyz/rollapp-wasm/app" 10 | "github.com/dymensionxyz/rollapp-wasm/rollappd/cmd" 11 | ) 12 | 13 | func main() { 14 | rootCmd, _ := cmd.NewRootCmd() 15 | if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil { 16 | switch e := err.(type) { 17 | case server.ErrorCode: 18 | os.Exit(e.Code) 19 | 20 | default: 21 | os.Exit(1) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /roller.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19-alpine3.16 as go-builder 2 | 3 | WORKDIR /app 4 | 5 | COPY go.mod go.sum* ./ 6 | 7 | RUN go mod download 8 | 9 | COPY . . 10 | 11 | ENV PACKAGES curl make git libc-dev bash gcc linux-headers eudev-dev python3 12 | 13 | RUN apk add --no-cache $PACKAGES 14 | 15 | RUN make build 16 | 17 | RUN git clone https://github.com/dymensionxyz/roller 18 | 19 | RUN cd roller && make build 20 | 21 | FROM alpine:3.16.1 22 | 23 | RUN apk add curl jq bash vim 24 | 25 | COPY --from=go-builder /app/build/rollapp-wasm /usr/local/bin/rollappd 26 | COPY --from=go-builder /app/roller/build/roller /usr/local/bin/ 27 | 28 | WORKDIR /app 29 | 30 | EXPOSE 26657 1317 -------------------------------------------------------------------------------- /scripts/add_vesting_accounts_to_genesis_file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | BINARY="rollapp-wasm" 3 | 4 | $BINARY keys add three-year-vester --keyring-backend test 5 | $BINARY add-genesis-account three-year-vester \ 6 | 10000000000000000000000${BASE_DENOM} --keyring-backend test \ 7 | --vesting-amount 10000000000000000000000${BASE_DENOM} \ 8 | --vesting-end-time 1805902584 9 | 10 | $BINARY keys add two-year-vester-after-1-week --keyring-backend test 11 | $BINARY add-genesis-account two-year-vester-after-1-week \ 12 | 10000000000000000000000${BASE_DENOM} --keyring-backend test \ 13 | --vesting-amount 10000000000000000000000${BASE_DENOM} \ 14 | --vesting-end-time 1774366584 --vesting-start-time 1711985835 15 | -------------------------------------------------------------------------------- /scripts/bytecode/cw20_base.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymensionxyz/rollapp-wasm/b135a5543c1963a77e4402b4c1d6613d49596754/scripts/bytecode/cw20_base.wasm -------------------------------------------------------------------------------- /scripts/bytecode/cw20_ics20.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymensionxyz/rollapp-wasm/b135a5543c1963a77e4402b4c1d6613d49596754/scripts/bytecode/cw20_ics20.wasm -------------------------------------------------------------------------------- /scripts/config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Function to set and log environment variable 4 | set_env_var() { 5 | local var_name="$1" 6 | local value="$2" 7 | 8 | export "$var_name"="$value" 9 | echo "Exported $var_name: $value" 10 | } 11 | 12 | # Set and log environment variables 13 | set_env_var ROLLAPP_CHAIN_ID "rollappwasm_1234-1" 14 | set_env_var KEY_NAME_ROLLAPP "rol-user" 15 | set_env_var DENOM "urax" 16 | set_env_var MONIKER "$ROLLAPP_CHAIN_ID-sequencer" 17 | -------------------------------------------------------------------------------- /scripts/download_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | command -v shellcheck > /dev/null && shellcheck "$0" 4 | 5 | if [ $# -ne 1 ]; then 6 | echo "Usage: ./download_releases.sh RELEASE_TAG" 7 | exit 1 8 | fi 9 | 10 | # eg. v0.13.1 11 | tag="$1" 12 | 13 | for contract in cw20_base cw20_ics20; do 14 | url="https://github.com/CosmWasm/cosmwasm-plus/releases/download/${tag}/${contract}.wasm" 15 | echo "Downloading $url ..." 16 | wget -O "scripts/bytecode/${contract}.wasm" "$url" 17 | done 18 | 19 | rm -f scripts/version.txt 20 | echo "$tag" >scripts/version.txt -------------------------------------------------------------------------------- /scripts/ibc/hub.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "cosmos", 3 | "value": { 4 | "key": "relayer-hub-key", 5 | "chain-id": "dymension_100-1", 6 | "rpc-addr": "http://localhost:36657", 7 | "account-prefix": "dym", 8 | "keyring-backend": "test", 9 | "gas-adjustment": 1.2, 10 | "gas-prices": "1000000000adym", 11 | "debug": true, 12 | "timeout": "10s", 13 | "output-format": "json", 14 | "sign-mode": "direct", 15 | "client-type": "07-tendermint" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /scripts/ibc/rollapp.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "cosmos", 3 | "value": { 4 | "key": "relayer-rollapp-key", 5 | "chain-id": "rollappwasm_1234-1", 6 | "rpc-addr": "tcp://localhost:26657", 7 | "account-prefix": "rol", 8 | "keyring-backend": "test", 9 | "gas-adjustment": 1.2, 10 | "gas-prices": "1000000000awsm", 11 | "debug": true, 12 | "timeout": "10s", 13 | "output-format": "json", 14 | "sign-mode": "direct", 15 | "client-type": "07-tendermint" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /scripts/protogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | ## make sure your `go env GOPATH` is in the `$PATH` 4 | ## Install: 5 | ## + latest buf (v1.0.0-rc11 or later) 6 | ## + protobuf v3 7 | # 8 | ## All protoc dependencies must be installed not in the module scope 9 | ## currently we must use grpc-gateway v1 10 | # cd ~ 11 | # go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 12 | # go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 13 | # go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v1.16.0 14 | # go install github.com/cosmos/cosmos-proto/cmd/protoc-gen-go-pulsar@latest 15 | # go get github.com/regen-network/cosmos-proto@latest # doesn't work in install mode 16 | # go get github.com/regen-network/cosmos-proto/protoc-gen-gocosmos@v0.3.1 17 | 18 | set -eo pipefail 19 | 20 | proto_dirs=$(find ./proto -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) 21 | for dir in $proto_dirs; do 22 | for file in $(find "${dir}" -maxdepth 1 -name '*.proto'); do 23 | if grep "option go_package" $file &> /dev/null ; then 24 | buf generate --template ./proto/buf.gen.gogo.yaml $file 25 | fi 26 | done 27 | done 28 | 29 | # move proto files to the right places 30 | cp -r github.com/dymensionxyz/rollapp-wasm/* ./ 31 | rm -rf github.com -------------------------------------------------------------------------------- /scripts/settlement/add_genesis_accounts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -d "$ROLLAPP_SETTLEMENT_INIT_DIR_PATH" ]; then 4 | mkdir -p "$ROLLAPP_SETTLEMENT_INIT_DIR_PATH" 5 | echo "Creating the ROLLAPP_SETTLEMENT_INIT_DIR_PATH: $ROLLAPP_SETTLEMENT_INIT_DIR_PATH" 6 | else 7 | echo "ROLLAPP_SETTLEMENT_INIT_DIR_PATH already exists: $ROLLAPP_SETTLEMENT_INIT_DIR_PATH" 8 | fi 9 | 10 | dymd keys add alice-genesis --keyring-backend test 11 | dymd keys add bob-genesis --keyring-backend test 12 | 13 | tee "$ROLLAPP_SETTLEMENT_INIT_DIR_PATH/genesis_accounts.json" >/dev/null </dev/null < futureReservationThreshold { 28 | return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, status.Errorf(codes.OutOfRange, "block height %d is too far in the future. max block height callback can be registered at %d", blockHeight, futureReservationThreshold) 29 | } 30 | // futureReservationFeeMultiplies * (requestBlockHeight - currentBlockHeight) 31 | futureReservationFeesAmount := params.FutureReservationFeeMultiplier.MulInt64((blockHeight - ctx.BlockHeight())) 32 | 33 | // Calculates the fees based on how many callbacks are registered at the given block height 34 | callbacksForHeight, err := k.GetCallbacksByHeight(ctx, blockHeight) 35 | if err != nil { 36 | return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, status.Errorf(codes.NotFound, "could not fetch callbacks for given height: %s", err.Error()) 37 | } 38 | totalCallbacks := len(callbacksForHeight) 39 | if totalCallbacks >= int(params.MaxBlockReservationLimit) { 40 | return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, status.Errorf(codes.OutOfRange, "block height %d has reached max reservation limit", blockHeight) 41 | } 42 | // blockReservationFeeMultiplier * totalCallbacksRegistered 43 | blockReservationFeesAmount := params.BlockReservationFeeMultiplier.MulInt64(int64(totalCallbacks)) 44 | 45 | // Calculates the fees based on the max gas limit of the callback and current price of gas 46 | transactionFee := k.CalculateTransactionFees(ctx, params.GetCallbackGasLimit(), params.GetMinPriceOfGas()) 47 | futureReservationFee := sdk.NewCoin(transactionFee.Denom, futureReservationFeesAmount.RoundInt()) 48 | blockReservationFee := sdk.NewCoin(transactionFee.Denom, blockReservationFeesAmount.RoundInt()) 49 | return futureReservationFee, blockReservationFee, transactionFee, nil 50 | } 51 | 52 | func (k Keeper) CalculateTransactionFees(ctx sdk.Context, gasAmount uint64, minPriceOfGas sdk.Coin) sdk.Coin { 53 | transactionFeeAmount := minPriceOfGas.Amount.MulRaw(int64(gasAmount)) 54 | transactionFee := sdk.NewCoin(minPriceOfGas.Denom, transactionFeeAmount) 55 | return transactionFee 56 | } 57 | -------------------------------------------------------------------------------- /x/callback/keeper/grpc_query.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | 10 | "github.com/dymensionxyz/rollapp-wasm/x/callback/types" 11 | ) 12 | 13 | var _ types.QueryServer = &QueryServer{} 14 | 15 | // QueryServer implements the module gRPC query service. 16 | type QueryServer struct { 17 | keeper Keeper 18 | } 19 | 20 | // NewQueryServer creates a new gRPC query server. 21 | func NewQueryServer(keeper Keeper) *QueryServer { 22 | return &QueryServer{ 23 | keeper: keeper, 24 | } 25 | } 26 | 27 | // Callbacks implements types.QueryServer. 28 | func (qs *QueryServer) Callbacks(c context.Context, request *types.QueryCallbacksRequest) (*types.QueryCallbacksResponse, error) { 29 | if request == nil { 30 | return nil, status.Error(codes.InvalidArgument, "empty request") 31 | } 32 | 33 | callbacks, err := qs.keeper.GetCallbacksByHeight(sdk.UnwrapSDKContext(c), request.GetBlockHeight()) 34 | if err != nil { 35 | return nil, status.Errorf(codes.Internal, "could not fetch the callbacks at height %d: %s", request.GetBlockHeight(), err.Error()) 36 | } 37 | 38 | return &types.QueryCallbacksResponse{ 39 | Callbacks: callbacks, 40 | }, nil 41 | } 42 | 43 | // EstimateCallbackFees implements types.QueryServer. 44 | func (qs *QueryServer) EstimateCallbackFees(c context.Context, request *types.QueryEstimateCallbackFeesRequest) (*types.QueryEstimateCallbackFeesResponse, error) { 45 | if request == nil { 46 | return nil, status.Error(codes.InvalidArgument, "empty request") 47 | } 48 | 49 | futureReservationFee, blockReservationFee, transactionFee, err := qs.keeper.EstimateCallbackFees(sdk.UnwrapSDKContext(c), request.GetBlockHeight()) 50 | if err != nil { 51 | return nil, err 52 | } 53 | totalFees := transactionFee.Add(blockReservationFee).Add(futureReservationFee) 54 | 55 | return &types.QueryEstimateCallbackFeesResponse{ 56 | FeeSplit: &types.CallbackFeesFeeSplit{ 57 | TransactionFees: &transactionFee, 58 | BlockReservationFees: &blockReservationFee, 59 | FutureReservationFees: &futureReservationFee, 60 | }, 61 | TotalFees: &totalFees, 62 | }, nil 63 | } 64 | 65 | // Params implements types.QueryServer. 66 | func (qs *QueryServer) Params(c context.Context, request *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { 67 | if request == nil { 68 | return nil, status.Error(codes.InvalidArgument, "empty request") 69 | } 70 | 71 | params, err := qs.keeper.GetParams(sdk.UnwrapSDKContext(c)) 72 | if err != nil { 73 | return nil, status.Errorf(codes.NotFound, "could not fetch the module params: %s", err.Error()) 74 | } 75 | 76 | return &types.QueryParamsResponse{ 77 | Params: params, 78 | }, nil 79 | } 80 | -------------------------------------------------------------------------------- /x/callback/keeper/keeper_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | 8 | e2eTesting "github.com/dymensionxyz/rollapp-wasm/e2e/testing" 9 | ) 10 | 11 | type KeeperTestSuite struct { 12 | suite.Suite 13 | 14 | chain *e2eTesting.TestChain 15 | } 16 | 17 | func (s *KeeperTestSuite) SetupTest() { 18 | s.chain = e2eTesting.NewTestChain(s.T(), 1) 19 | } 20 | 21 | func TestCallbackKeeper(t *testing.T) { 22 | suite.Run(t, new(KeeperTestSuite)) 23 | } 24 | -------------------------------------------------------------------------------- /x/callback/keeper/params.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | 6 | "github.com/dymensionxyz/rollapp-wasm/x/callback/types" 7 | ) 8 | 9 | // GetParams return all module parameters. 10 | func (k Keeper) GetParams(ctx sdk.Context) (params types.Params, err error) { 11 | return k.Params.Get(ctx) 12 | } 13 | 14 | // SetParams sets all module parameters. 15 | func (k Keeper) SetParams(ctx sdk.Context, params types.Params) error { 16 | return k.Params.Set(ctx, params) 17 | } 18 | -------------------------------------------------------------------------------- /x/callback/spec/01_state.md: -------------------------------------------------------------------------------- 1 | # State 2 | 3 | Section describes all stored by the module objects and their storage keys. 4 | 5 | Refer to the [callback.proto](../../../proto/rollapp/callback/v1/callback.proto) for objects fields description. 6 | 7 | ## Params 8 | 9 | [Params](../../../proto/rollapp/callback/v1/callback.proto#L38) object is used to store the module params. 10 | 11 | The params value can only be updated by x/gov module via a governance upgrade proposal. 12 | 13 | Storage keys: 14 | 15 | * Params: `ParamsKey -> ProtocolBuffer(Params)` 16 | 17 | ## Callback 18 | 19 | [Callback](../../../proto/rollapp/callback/v1/callback.proto#L12) object is used to store the callbacks which are registered. 20 | 21 | The callbacks are pruned after they are executed. 22 | 23 | Storage keys: 24 | 25 | * Callback: `CallbacksKey | BlockHeight | ContractAddress | JobID -> ProtocolBuffer(Callback)` 26 | -------------------------------------------------------------------------------- /x/callback/spec/02_messages.md: -------------------------------------------------------------------------------- 1 | # Messages 2 | 3 | Section describes the processing of the module messages 4 | 5 | ## MsgRequestCallback 6 | 7 | A new callback can be registered by using the [MsgRequestCallback](../../../proto/rollapp/callback/v1/tx.proto#L39) message. 8 | 9 | On success: 10 | 11 | * A callback is queued to be executed at the given height. 12 | * The fee amount specified is transferred from the sender's account to the module account 13 | 14 | This message is expected to fail if: 15 | 16 | * Insufficient fees are sent 17 | * The account has insufficient balance 18 | * The contract with given address does not exist 19 | * A callback with at given height for specified height with given job id already exists 20 | * The callback request height is in the past or in the current block 21 | * The sender is not authorized to request a callback. The callback can only be request by the following 22 | * The contract itself 23 | * The contract admin as set in the x/wasmd module 24 | * The contract owner as set in the x/rewards module 25 | 26 | ## MsgCancelCallback 27 | 28 | An existing callback can be cancelled by using th [MsgCancelCallback](../../../proto/rollapp/callback/v1/tx.proto#L58) message, 29 | 30 | On success: 31 | 32 | * The exisiting callback is removed from the execution queue. 33 | * The txFee and surplusFee amount is refunded back to the sender. 34 | * The rest of the fees are sent to fee_collector to be distributed to validators and stakers 35 | 36 | This message is expected to fail if: 37 | 38 | * Callback with specified block height, contract address and job id does not exist 39 | * The sender is not authorized to cancel the callback. The callback can only be cancelled by the following: 40 | * The contract itself 41 | * The contract admin as set in the x/wasmd module 42 | * The contract owner as set in the x/rewards module 43 | -------------------------------------------------------------------------------- /x/callback/spec/03_end_block.md: -------------------------------------------------------------------------------- 1 | # End Block 2 | 3 | Section describes the module state changes on the ABCI end block call 4 | 5 | ## Callback Execution 6 | 7 | Every end block we iterate over all the callbacks registered at that height. For each of the registered callback we, 8 | 9 | 1. Create a CallbackMsg 10 | 11 | It is a json encoded msg which includes the job id and is sent to the contract 12 | 13 | 2. Execute the callback 14 | 15 | A new sdk context is used with a limited gas meter. The gas limit is set to the value of the module param [CallbackGasLimit](../../../proto/rollapp/callback/v1/callback.proto). Execute using the Sudo entrypoint and track the amount of gasUsed and errors, if any. 16 | 17 | 3. Handle error 18 | 19 | If there was any error during the execution of the callback, whether from the contract returning an error, or an out of gas error, set the error with the [x/cwerrors](../../cwerrors/spec/README.md) module with the appropriate error code. 20 | 21 | If the callback was successfull, throw a success event. 22 | 23 | 4. Calculate tx fees 24 | 25 | Based on the gas used, calculate the transaction fees for the executed callback. If the calculated fee is less than what was paid, refund the surplus to the address which registered the callback. 26 | 27 | 5. Distribute fees 28 | 29 | The consumed tx fees and all the other fees are sent to the fee collector to be distributed to the validators and stakers. 30 | 31 | 6. Cleanup 32 | 33 | Remove the callback entry from the state 34 | -------------------------------------------------------------------------------- /x/callback/spec/04_events.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | Section describes the module events 4 | 5 | The module emits the following proto-events 6 | 7 | | Source type | Source name | Protobuf reference | 8 | | ----------- | -------------------- |--------------------------------------------------------------------------------------| 9 | | Message | `MsgRequestCallback` | [CallbackRegisteredEvent](../../../proto/rollapp/callback/v1/events.proto#L11) | 10 | | Message | `MsgCancelCallback` | [CallbackCancelledEvent](../../../proto/rollapp/callback/v1/events.proto#L25) | 11 | | Module | `EndBlocker` | [CallbackExecutedSuccessEvent](../../../proto/rollapp/callback/v1/events.proto#L39) | 12 | | Module | `EndBlocker` | [CallbackExecutedFailedEvent](../../../proto/rollapp/callback/v1/events.proto#L53) | 13 | -------------------------------------------------------------------------------- /x/callback/spec/06_wasm_bindings.md: -------------------------------------------------------------------------------- 1 | # Wasm Bindings 2 | 3 | The only custom binding the module has is in the callback message which is sent to the contract during the execution of the callback. 4 | 5 | ```go 6 | // SudoMsg callback message sent to a contract. 7 | // This is encoded as JSON input to the contract when executing the callback 8 | type SudoMsg struct { 9 | // Callback is the endpoint name at the contract which is called 10 | Callback *CallbackMsg `json:"callback,omitempty"` 11 | } 12 | 13 | // CallbackMsg is the callback message sent to a contract. 14 | type CallbackMsg struct { 15 | // JobID is the user specified job id 16 | JobID uint64 `json:"job_id"` 17 | } 18 | ``` 19 | 20 | The above struct is converted into the json encoded string in the following way. 21 | 22 | ```json 23 | {"callback":{"job_id":1}} 24 | ``` 25 | 26 | ## Requesting Callback 27 | 28 | The contract can request a callback by using proto msg [MsgRequestCallback](./02_messages.md#msgrequestcallback) 29 | 30 | ## Cancelling Callback 31 | 32 | The contract can cancel an existing callback by using proto msg [MsgCancelCallback](./02_messages.md#msgcancelcallback) 33 | -------------------------------------------------------------------------------- /x/callback/spec/07_errors.md: -------------------------------------------------------------------------------- 1 | # Errors 2 | 3 | The module exposes the following error codes which are used with the x/cwerrors module in case of callback failures. 4 | 5 | ```proto 6 | enum ModuleErrors { 7 | // ERR_UNKNOWN is the default error code 8 | ERR_UNKNOWN = 0; 9 | // ERR_OUT_OF_GAS is the error code when the contract callback exceeds the gas limit allowed by the module 10 | ERR_OUT_OF_GAS = 1; 11 | // ERR_CONTRACT_EXECUTION_FAILED is the error code when the contract callback execution fails 12 | ERR_CONTRACT_EXECUTION_FAILED = 2; 13 | } 14 | ``` 15 | -------------------------------------------------------------------------------- /x/callback/types/callback.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import sdk "github.com/cosmos/cosmos-sdk/types" 4 | 5 | // NewCallback creates a new Callback instance. 6 | func NewCallback(sender string, contractAddress string, height int64, jobID uint64, txFees sdk.Coin, blockReservationFees sdk.Coin, futureReservationFees sdk.Coin, surplusFees sdk.Coin) Callback { 7 | return Callback{ 8 | ContractAddress: contractAddress, 9 | CallbackHeight: height, 10 | JobId: jobID, 11 | ReservedBy: sender, 12 | FeeSplit: &CallbackFeesFeeSplit{ 13 | TransactionFees: &txFees, 14 | BlockReservationFees: &blockReservationFees, 15 | FutureReservationFees: &futureReservationFees, 16 | SurplusFees: &surplusFees, 17 | }, 18 | } 19 | } 20 | 21 | // Validate perform object fields validation. 22 | func (c Callback) Validate() error { 23 | if _, err := sdk.AccAddressFromBech32(c.GetContractAddress()); err != nil { 24 | return err 25 | } 26 | if _, err := sdk.AccAddressFromBech32(c.GetReservedBy()); err != nil { 27 | return err 28 | } 29 | if c.GetCallbackHeight() <= 0 { 30 | return ErrCallbackHeightNotInFuture 31 | } 32 | if err := c.GetFeeSplit().GetTransactionFees().Validate(); err != nil { 33 | return err 34 | } 35 | if err := c.GetFeeSplit().GetBlockReservationFees().Validate(); err != nil { 36 | return err 37 | } 38 | if err := c.GetFeeSplit().GetFutureReservationFees().Validate(); err != nil { 39 | return err 40 | } 41 | if err := c.GetFeeSplit().GetSurplusFees().Validate(); err != nil { 42 | return err 43 | } 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /x/callback/types/callback_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | 10 | e2eTesting "github.com/dymensionxyz/rollapp-wasm/e2e/testing" 11 | "github.com/dymensionxyz/rollapp-wasm/x/callback/types" 12 | ) 13 | 14 | func TestCallbackValidate(t *testing.T) { 15 | accAddrs, _ := e2eTesting.GenAccounts(1) 16 | accAddr := accAddrs[0] 17 | contractAddr := e2eTesting.GenContractAddresses(1)[0] 18 | validCoin := sdk.NewInt64Coin("stake", 1) 19 | 20 | type testCase struct { 21 | name string 22 | callback types.Callback 23 | errExpected bool 24 | } 25 | 26 | testCases := []testCase{ 27 | { 28 | name: "Fail: Empty values", 29 | callback: types.Callback{}, 30 | errExpected: true, 31 | }, 32 | { 33 | name: "Fail: Invalid contract address", 34 | callback: types.Callback{ 35 | ContractAddress: "👻", 36 | ReservedBy: accAddr.String(), 37 | CallbackHeight: 1, 38 | FeeSplit: &types.CallbackFeesFeeSplit{ 39 | TransactionFees: &validCoin, 40 | BlockReservationFees: &validCoin, 41 | FutureReservationFees: &validCoin, 42 | SurplusFees: &validCoin, 43 | }, 44 | }, 45 | errExpected: true, 46 | }, 47 | { 48 | name: "Fail: Invalid reservedby address", 49 | callback: types.Callback{ 50 | ContractAddress: contractAddr.String(), 51 | ReservedBy: "👻", 52 | CallbackHeight: 1, 53 | FeeSplit: &types.CallbackFeesFeeSplit{ 54 | TransactionFees: &validCoin, 55 | BlockReservationFees: &validCoin, 56 | FutureReservationFees: &validCoin, 57 | SurplusFees: &validCoin, 58 | }, 59 | }, 60 | errExpected: true, 61 | }, 62 | { 63 | name: "Fail: Invalid callback height", 64 | callback: types.Callback{ 65 | ContractAddress: contractAddr.String(), 66 | ReservedBy: accAddr.String(), 67 | CallbackHeight: -1, 68 | FeeSplit: &types.CallbackFeesFeeSplit{ 69 | TransactionFees: &validCoin, 70 | BlockReservationFees: &validCoin, 71 | FutureReservationFees: &validCoin, 72 | SurplusFees: &validCoin, 73 | }, 74 | }, 75 | errExpected: true, 76 | }, 77 | { 78 | name: "OK: Valid callback", 79 | callback: types.Callback{ 80 | ContractAddress: contractAddr.String(), 81 | ReservedBy: accAddr.String(), 82 | CallbackHeight: 1, 83 | FeeSplit: &types.CallbackFeesFeeSplit{ 84 | TransactionFees: &validCoin, 85 | BlockReservationFees: &validCoin, 86 | FutureReservationFees: &validCoin, 87 | SurplusFees: &validCoin, 88 | }, 89 | }, 90 | errExpected: false, 91 | }, 92 | } 93 | 94 | for _, tc := range testCases { 95 | t.Run(tc.name, func(t *testing.T) { 96 | err := tc.callback.Validate() 97 | if tc.errExpected { 98 | assert.Error(t, err) 99 | return 100 | } 101 | assert.NoError(t, err) 102 | }) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /x/callback/types/codec.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | "github.com/cosmos/cosmos-sdk/codec/types" 6 | cryptoCodec "github.com/cosmos/cosmos-sdk/crypto/codec" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/cosmos/cosmos-sdk/types/msgservice" 9 | ) 10 | 11 | // RegisterLegacyAminoCodec registers the necessary interfaces and concrete types on the provided LegacyAmino codec. 12 | // These types are used for Amino JSON serialization. 13 | func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { 14 | cdc.RegisterConcrete(&MsgRequestCallback{}, "callback/MsgRequestCallback", nil) 15 | cdc.RegisterConcrete(&MsgCancelCallback{}, "callback/MsgCancelCallback", nil) 16 | cdc.RegisterConcrete(&MsgUpdateParams{}, "callback/MsgUpdateParams", nil) 17 | } 18 | 19 | // RegisterInterfaces registers interfaces types with the interface registry. 20 | func RegisterInterfaces(registry types.InterfaceRegistry) { 21 | registry.RegisterImplementations((*sdk.Msg)(nil), 22 | &MsgRequestCallback{}, 23 | &MsgCancelCallback{}, 24 | &MsgUpdateParams{}, 25 | ) 26 | 27 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 28 | } 29 | 30 | var ( 31 | ModuleCdc = codec.NewAminoCodec(amino) 32 | amino = codec.NewLegacyAmino() 33 | ) 34 | 35 | func init() { 36 | RegisterLegacyAminoCodec(amino) 37 | cryptoCodec.RegisterCrypto(amino) 38 | sdk.RegisterLegacyAminoCodec(amino) 39 | amino.Seal() 40 | } 41 | -------------------------------------------------------------------------------- /x/callback/types/errors.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | errorsmod "cosmossdk.io/errors" 5 | 6 | cwerrortypes "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 7 | ) 8 | 9 | var ( 10 | DefaultCodespace = ModuleName 11 | ErrContractNotFound = errorsmod.Register(DefaultCodespace, 2, "contract with given address not found") 12 | ErrCallbackJobIDExists = errorsmod.Register(DefaultCodespace, 3, "callback with given job id already exists for given height") 13 | ErrCallbackHeightNotInFuture = errorsmod.Register(DefaultCodespace, 4, "callback request height is not in the future") 14 | ErrUnauthorized = errorsmod.Register(DefaultCodespace, 5, "sender not authorized to register callback") 15 | ErrCallbackNotFound = errorsmod.Register(DefaultCodespace, 6, "callback with given job id does not exist for given height") 16 | ErrInsufficientFees = errorsmod.Register(DefaultCodespace, 7, "insufficient fees to register callback") 17 | ErrCallbackExists = errorsmod.Register(DefaultCodespace, 8, "callback with given job id already exists for given height") 18 | ErrCallbackHeightTooFarInFuture = errorsmod.Register(DefaultCodespace, 9, "callback request height is too far in the future") 19 | ErrBlockFilled = errorsmod.Register(DefaultCodespace, 10, "block filled with max capacity of callbacks") 20 | ) 21 | 22 | // NewSudoError creates a new sudo error instance to pass on to the errors module 23 | func NewSudoError(errorCode ModuleErrors, contractAddr string, inputPayload string, errMsg string) cwerrortypes.SudoError { 24 | return cwerrortypes.SudoError{ 25 | ModuleName: ModuleName, 26 | ErrorCode: int32(errorCode), 27 | ContractAddress: contractAddr, 28 | InputPayload: inputPayload, 29 | ErrorMessage: errMsg, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /x/callback/types/events.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | ) 8 | 9 | func EmitCallbackRegisteredEvent( 10 | ctx sdk.Context, 11 | contractAddress string, 12 | jobId uint64, 13 | callbackHeight int64, 14 | feeSplit *CallbackFeesFeeSplit, 15 | reservedBy string, 16 | ) { 17 | err := ctx.EventManager().EmitTypedEvent(&CallbackRegisteredEvent{ 18 | ContractAddress: contractAddress, 19 | JobId: jobId, 20 | CallbackHeight: callbackHeight, 21 | FeeSplit: feeSplit, 22 | ReservedBy: reservedBy, 23 | }) 24 | if err != nil { 25 | panic(fmt.Errorf("sending CallbackRegisteredEvent event: %w", err)) 26 | } 27 | } 28 | 29 | func EmitCallbackCancelledEvent( 30 | ctx sdk.Context, 31 | contractAddress string, 32 | jobId uint64, 33 | callbackHeight int64, 34 | cancelledBy string, 35 | refundAmount sdk.Coin, 36 | ) { 37 | err := ctx.EventManager().EmitTypedEvent(&CallbackCancelledEvent{ 38 | ContractAddress: contractAddress, 39 | JobId: jobId, 40 | CallbackHeight: callbackHeight, 41 | CancelledBy: cancelledBy, 42 | RefundAmount: refundAmount, 43 | }) 44 | if err != nil { 45 | panic(fmt.Errorf("sending CallbackCancelledEvent event: %w", err)) 46 | } 47 | } 48 | 49 | func EmitCallbackExecutedSuccessEvent( 50 | ctx sdk.Context, 51 | contractAddress string, 52 | jobId uint64, 53 | sudoMsg string, 54 | gasUsed uint64, 55 | ) { 56 | err := ctx.EventManager().EmitTypedEvent(&CallbackExecutedSuccessEvent{ 57 | ContractAddress: contractAddress, 58 | JobId: jobId, 59 | SudoMsg: sudoMsg, 60 | GasUsed: gasUsed, 61 | }) 62 | if err != nil { 63 | panic(fmt.Errorf("sending CallbackExecutedSuccessEvent event: %w", err)) 64 | } 65 | } 66 | 67 | func EmitCallbackExecutedFailedEvent( 68 | ctx sdk.Context, 69 | contractAddress string, 70 | jobId uint64, 71 | sudoMsg string, 72 | gasUsed uint64, 73 | errMsg string, 74 | ) { 75 | err := ctx.EventManager().EmitTypedEvent(&CallbackExecutedFailedEvent{ 76 | Error: errMsg, 77 | ContractAddress: contractAddress, 78 | JobId: jobId, 79 | SudoMsg: sudoMsg, 80 | GasUsed: gasUsed, 81 | }) 82 | if err != nil { 83 | panic(fmt.Errorf("sending CallbackExecutedFailedEvent event: %w", err)) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /x/callback/types/expected_keepers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | wasmdtypes "github.com/CosmWasm/wasmd/x/wasm/types" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | cwerrortypes "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 7 | ) 8 | 9 | type WasmKeeperExpected interface { 10 | HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool 11 | GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmdtypes.ContractInfo 12 | Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) 13 | } 14 | 15 | type BankKeeperExpected interface { 16 | SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error 17 | SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error 18 | SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error 19 | BlockedAddr(addr sdk.AccAddress) bool 20 | } 21 | 22 | type ErrorsKeeperExpected interface { 23 | SetError(ctx sdk.Context, sudoErr cwerrortypes.SudoError) error 24 | } 25 | -------------------------------------------------------------------------------- /x/callback/types/genesis.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // NewGenesisState creates a new GenesisState object. 4 | func NewGenesisState( 5 | params Params, 6 | callbacks []Callback, 7 | ) *GenesisState { 8 | var callbacksCopy []*Callback 9 | for _, c := range callbacks { 10 | callbacksCopy = append(callbacksCopy, &c) 11 | } 12 | return &GenesisState{ 13 | Params: params, 14 | Callbacks: callbacksCopy, 15 | } 16 | } 17 | 18 | // DefaultGenesisState returns a default genesis state. 19 | func DefaultGenesis() *GenesisState { 20 | defaultParams := DefaultParams() 21 | return NewGenesisState(defaultParams, nil) 22 | } 23 | 24 | // Validate perform object fields validation. 25 | func (g GenesisState) Validate() error { 26 | if err := g.Params.Validate(); err != nil { 27 | return err 28 | } 29 | for _, callback := range g.GetCallbacks() { 30 | if err := callback.Validate(); err != nil { 31 | return err 32 | } 33 | } 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /x/callback/types/genesis_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | 10 | e2eTesting "github.com/dymensionxyz/rollapp-wasm/e2e/testing" 11 | "github.com/dymensionxyz/rollapp-wasm/x/callback/types" 12 | ) 13 | 14 | func TestGenesisValidate(t *testing.T) { 15 | accAddrs, _ := e2eTesting.GenAccounts(1) 16 | accAddr := accAddrs[0] 17 | contractAddr := e2eTesting.GenContractAddresses(1)[0] 18 | validCoin := sdk.NewInt64Coin("stake", 1) 19 | 20 | type testCase struct { 21 | name string 22 | genesis types.GenesisState 23 | errExpected bool 24 | } 25 | 26 | testCases := []testCase{ 27 | { 28 | name: "Fail: Empty values", 29 | genesis: types.GenesisState{}, 30 | errExpected: true, 31 | }, 32 | { 33 | name: "Fail: Invalid params", 34 | genesis: types.GenesisState{ 35 | Params: types.NewParams( 36 | 0, 37 | 100, 38 | 100, 39 | sdk.MustNewDecFromStr("1.0"), 40 | sdk.MustNewDecFromStr("1.0"), 41 | sdk.NewCoin(sdk.DefaultBondDenom, sdk.ZeroInt()), 42 | ), 43 | Callbacks: []*types.Callback{ 44 | { 45 | ContractAddress: contractAddr.String(), 46 | ReservedBy: accAddr.String(), 47 | CallbackHeight: 1, 48 | FeeSplit: &types.CallbackFeesFeeSplit{ 49 | TransactionFees: &validCoin, 50 | BlockReservationFees: &validCoin, 51 | FutureReservationFees: &validCoin, 52 | SurplusFees: &validCoin, 53 | }, 54 | }, 55 | }, 56 | }, 57 | errExpected: true, 58 | }, { 59 | name: "Fail: Invalid callback", 60 | genesis: types.GenesisState{ 61 | Params: types.DefaultParams(), 62 | Callbacks: []*types.Callback{ 63 | { 64 | ContractAddress: "👻", 65 | ReservedBy: accAddr.String(), 66 | CallbackHeight: 1, 67 | FeeSplit: &types.CallbackFeesFeeSplit{ 68 | TransactionFees: &validCoin, 69 | BlockReservationFees: &validCoin, 70 | FutureReservationFees: &validCoin, 71 | SurplusFees: &validCoin, 72 | }, 73 | }, 74 | }, 75 | }, 76 | errExpected: true, 77 | }, 78 | { 79 | name: "OK: Valid genesis state", 80 | genesis: types.GenesisState{ 81 | Params: types.DefaultParams(), 82 | Callbacks: []*types.Callback{ 83 | { 84 | ContractAddress: contractAddr.String(), 85 | ReservedBy: accAddr.String(), 86 | CallbackHeight: 1, 87 | FeeSplit: &types.CallbackFeesFeeSplit{ 88 | TransactionFees: &validCoin, 89 | BlockReservationFees: &validCoin, 90 | FutureReservationFees: &validCoin, 91 | SurplusFees: &validCoin, 92 | }, 93 | }, 94 | }, 95 | }, 96 | errExpected: false, 97 | }, 98 | } 99 | 100 | for _, tc := range testCases { 101 | t.Run(tc.name, func(t *testing.T) { 102 | err := tc.genesis.Validate() 103 | if tc.errExpected { 104 | assert.Error(t, err) 105 | return 106 | } 107 | assert.NoError(t, err) 108 | }) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /x/callback/types/keys.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "cosmossdk.io/collections" 5 | ) 6 | 7 | const ( 8 | // ModuleName is the module name. 9 | ModuleName = "callback" 10 | // StoreKey is the module KV storage prefix key. 11 | StoreKey = ModuleName 12 | // QuerierRoute is the querier route for the module. 13 | QuerierRoute = ModuleName 14 | 15 | RouterKey = ModuleName 16 | ) 17 | 18 | var ( 19 | ParamsKeyPrefix = collections.NewPrefix(1) 20 | CallbackKeyPrefix = collections.NewPrefix(2) 21 | ) 22 | -------------------------------------------------------------------------------- /x/callback/types/params.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | fmt "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | ) 8 | 9 | var ( 10 | DefaultCallbackGasLimit = uint64(1000000) 11 | DefaultMaxBlockReservationLimit = uint64(3) 12 | DefaultMaxFutureReservationLimit = uint64(10000) 13 | DefaultBlockReservationFeeMultiplier = sdk.ZeroDec() 14 | DefaultFutureReservationFeeMultiplier = sdk.ZeroDec() 15 | DefaultMinPriceOfGas = sdk.NewCoin(sdk.DefaultBondDenom, sdk.ZeroInt()) 16 | ) 17 | 18 | // NewParams creates a new Params instance. 19 | func NewParams( 20 | callbackGasLimit uint64, 21 | maxBlockReservationLimit uint64, 22 | maxFutureReservationLimit uint64, 23 | blockReservationFeeMultiplier sdk.Dec, 24 | futureReservationFeeMultiplier sdk.Dec, 25 | minPriceOfGas sdk.Coin, 26 | ) Params { 27 | return Params{ 28 | CallbackGasLimit: callbackGasLimit, 29 | MaxBlockReservationLimit: maxBlockReservationLimit, 30 | MaxFutureReservationLimit: maxFutureReservationLimit, 31 | BlockReservationFeeMultiplier: blockReservationFeeMultiplier, 32 | FutureReservationFeeMultiplier: futureReservationFeeMultiplier, 33 | MinPriceOfGas: minPriceOfGas, 34 | } 35 | } 36 | 37 | // DefaultParams returns a default set of parameters. 38 | func DefaultParams() Params { 39 | return NewParams( 40 | DefaultCallbackGasLimit, 41 | DefaultMaxBlockReservationLimit, 42 | DefaultMaxFutureReservationLimit, 43 | DefaultBlockReservationFeeMultiplier, 44 | DefaultFutureReservationFeeMultiplier, 45 | DefaultMinPriceOfGas, 46 | ) 47 | } 48 | 49 | // Validate perform object fields validation. 50 | func (p Params) Validate() error { 51 | if p.CallbackGasLimit == 0 { 52 | return fmt.Errorf("CallbackGasLimit must be greater than 0") 53 | } 54 | if p.BlockReservationFeeMultiplier.IsNegative() { 55 | return fmt.Errorf("BlockReservationFeeMultiplier must be greater than 0") 56 | } 57 | if p.FutureReservationFeeMultiplier.IsNegative() { 58 | return fmt.Errorf("FutureReservationFeeMultiplier must be greater than 0") 59 | } 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /x/callback/types/params_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/dymensionxyz/rollapp-wasm/x/callback/types" 10 | ) 11 | 12 | func TestParamsValidate(t *testing.T) { 13 | type testCase struct { 14 | name string 15 | params types.Params 16 | errExpected bool 17 | } 18 | 19 | testCases := []testCase{ 20 | { 21 | name: "OK: Default values", 22 | params: types.DefaultParams(), 23 | errExpected: false, 24 | }, 25 | { 26 | name: "OK: All valid values", 27 | params: types.NewParams( 28 | 100, 29 | 100, 30 | 100, 31 | sdk.MustNewDecFromStr("1.0"), 32 | sdk.MustNewDecFromStr("1.0"), 33 | sdk.NewCoin(sdk.DefaultBondDenom, sdk.ZeroInt()), 34 | ), 35 | errExpected: false, 36 | }, 37 | { 38 | name: "Fail: CallbackGasLimit: zero", 39 | params: types.NewParams( 40 | 0, 41 | 100, 42 | 100, 43 | sdk.MustNewDecFromStr("1.0"), 44 | sdk.MustNewDecFromStr("1.0"), 45 | sdk.NewCoin(sdk.DefaultBondDenom, sdk.ZeroInt()), 46 | ), 47 | errExpected: true, 48 | }, 49 | { 50 | name: "Fail: BlockReservationFeeMultiplier: negative", 51 | params: types.NewParams( 52 | 100, 53 | 100, 54 | 100, 55 | sdk.MustNewDecFromStr("-1.0"), 56 | sdk.MustNewDecFromStr("1.0"), 57 | sdk.NewCoin(sdk.DefaultBondDenom, sdk.ZeroInt()), 58 | ), 59 | errExpected: true, 60 | }, 61 | { 62 | name: "Fail: FutureReservationFeeMultiplier: negative", 63 | params: types.NewParams( 64 | 100, 65 | 100, 66 | 100, 67 | sdk.MustNewDecFromStr("1.0"), 68 | sdk.MustNewDecFromStr("-1.0"), 69 | sdk.NewCoin(sdk.DefaultBondDenom, sdk.ZeroInt()), 70 | ), 71 | errExpected: true, 72 | }, 73 | } 74 | 75 | for _, tc := range testCases { 76 | t.Run(tc.name, func(t *testing.T) { 77 | err := tc.params.Validate() 78 | if tc.errExpected { 79 | assert.Error(t, err) 80 | return 81 | } 82 | assert.NoError(t, err) 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /x/callback/types/sudo_msg.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "encoding/json" 4 | 5 | // SudoMsg callback message sent to a contract. 6 | // This is encoded as JSON input to the contract when executing the callback 7 | type SudoMsg struct { 8 | // Callback is the endpoint name at the contract which is called 9 | Callback *CallbackMsg `json:"callback,omitempty"` 10 | } 11 | 12 | // CallbackMsg is the callback message sent to a contract. 13 | type CallbackMsg struct { 14 | // JobID is the user specified job id 15 | JobID uint64 `json:"job_id"` 16 | } 17 | 18 | // NewCallback creates a new Callback instance. 19 | func NewCallbackMsg(jobID uint64) SudoMsg { 20 | return SudoMsg{ 21 | Callback: &CallbackMsg{ 22 | JobID: jobID, 23 | }, 24 | } 25 | } 26 | 27 | // Bytes returns the callback message as JSON bytes 28 | func (s SudoMsg) Bytes() []byte { 29 | msgBz, err := json.Marshal(s) 30 | if err != nil { 31 | panic(err) 32 | } 33 | return msgBz 34 | } 35 | 36 | // String returns the callback message as JSON string 37 | func (s SudoMsg) String() string { 38 | return string(s.Bytes()) 39 | } 40 | -------------------------------------------------------------------------------- /x/callback/types/sudo_msg_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/dymensionxyz/rollapp-wasm/x/callback/types" 9 | ) 10 | 11 | func TestSudoMsgString(t *testing.T) { 12 | testCases := []struct { 13 | testCase string 14 | msg types.SudoMsg 15 | expectedMsg string 16 | }{ 17 | { 18 | "ok: callback job_id is 1", 19 | types.NewCallbackMsg(1), 20 | `{"callback":{"job_id":1}}`, 21 | }, 22 | } 23 | 24 | for _, tc := range testCases { 25 | tc := tc 26 | t.Run(tc.testCase, func(t *testing.T) { 27 | res := tc.msg.String() 28 | require.EqualValues(t, tc.expectedMsg, res) 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /x/callback/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | -------------------------------------------------------------------------------- /x/cwerrors/abci.go: -------------------------------------------------------------------------------- 1 | package cwerrors 2 | 3 | import ( 4 | abci "github.com/tendermint/tendermint/abci/types" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | 7 | "github.com/dymensionxyz/rollapp-wasm/pkg" 8 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/keeper" 9 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 10 | ) 11 | 12 | const ErrorCallbackGasLimit = 150_000 13 | 14 | // EndBlocker is called every block, and prunes errors that are older than the current block height. 15 | func EndBlocker(ctx sdk.Context, k keeper.Keeper, wk types.WasmKeeperExpected) []abci.ValidatorUpdate { 16 | // Iterate over all errors (with callback subscription) and execute the error callback for each error 17 | k.IterateSudoErrorCallbacks(ctx, sudoErrorCallbackExec(ctx, k, wk)) 18 | // Prune any error callback subscripitons that have expired in the current block height 19 | if err := k.PruneSubscriptionsEndBlock(ctx); err != nil { 20 | panic(err) 21 | } 22 | // Prune any errors(in state) that have expired in the current block height 23 | if err := k.PruneErrorsCurrentBlock(ctx); err != nil { 24 | panic(err) 25 | } 26 | 27 | return nil 28 | } 29 | 30 | func sudoErrorCallbackExec(ctx sdk.Context, k keeper.Keeper, wk types.WasmKeeperExpected) func(types.SudoError) bool { 31 | return func(sudoError types.SudoError) bool { 32 | contractAddr := sdk.MustAccAddressFromBech32(sudoError.ContractAddress) 33 | 34 | sudoMsg := types.NewSudoMsg(sudoError) 35 | _, err := pkg.ExecuteWithGasLimit(ctx, ErrorCallbackGasLimit, func(ctx sdk.Context) error { 36 | _, err := wk.Sudo(ctx, contractAddr, sudoMsg.Bytes()) 37 | return err 38 | }) 39 | if err != nil { 40 | // In case Sudo error, such as out of gas, emit an event and store the error in state (so that the error is not lost) 41 | types.EmitSudoErrorCallbackFailedEvent( 42 | ctx, 43 | sudoError, 44 | err.Error(), 45 | ) 46 | newSudoErr := types.SudoError{ 47 | ModuleName: types.ModuleName, 48 | ContractAddress: sudoError.ContractAddress, 49 | ErrorCode: int32(types.ModuleErrors_ERR_CALLBACK_EXECUTION_FAILED), 50 | InputPayload: string(sudoError.Bytes()), 51 | ErrorMessage: err.Error(), 52 | } 53 | err = k.StoreErrorInState(ctx, contractAddr, newSudoErr) 54 | if err != nil { 55 | panic(err) 56 | } 57 | } 58 | return false 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /x/cwerrors/client/cli/query.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/client" 5 | "github.com/cosmos/cosmos-sdk/client/flags" 6 | "github.com/spf13/cobra" 7 | 8 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 9 | ) 10 | 11 | // GetQueryCmd builds query command group for the module. 12 | func GetQueryCmd() *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: types.ModuleName, 15 | Short: "Querying commands for the callback module", 16 | DisableFlagParsing: true, 17 | SuggestionsMinimumDistance: 2, 18 | RunE: client.ValidateCmd, 19 | } 20 | cmd.AddCommand( 21 | getQueryErrorsCmd(), 22 | getQueryIsSubscribedCmd(), 23 | getQueryParamsCmd(), 24 | ) 25 | return cmd 26 | } 27 | 28 | // getQueryErrorsCmd returns the command to query errors for a contract address. 29 | func getQueryErrorsCmd() *cobra.Command { 30 | cmd := &cobra.Command{ 31 | Use: "errors [contract_address]", 32 | Args: cobra.ExactArgs(1), 33 | Short: "Query errors for a contract address", 34 | RunE: func(cmd *cobra.Command, args []string) error { 35 | clientCtx, err := client.GetClientQueryContext(cmd) 36 | if err != nil { 37 | return err 38 | } 39 | queryClient := types.NewQueryClient(clientCtx) 40 | 41 | res, err := queryClient.Errors(cmd.Context(), &types.QueryErrorsRequest{ 42 | ContractAddress: args[0], 43 | }) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | return clientCtx.PrintProto(res) 49 | }, 50 | } 51 | flags.AddQueryFlagsToCmd(cmd) 52 | return cmd 53 | } 54 | 55 | // getQueryIsSubscribedCmd returns the command to query if a contract address is subscribed to errors. 56 | func getQueryIsSubscribedCmd() *cobra.Command { 57 | cmd := &cobra.Command{ 58 | Use: "is-subscribed [contract_address]", 59 | Args: cobra.ExactArgs(1), 60 | Short: "Query if a contract address is subscribed to errors", 61 | RunE: func(cmd *cobra.Command, args []string) error { 62 | clientCtx, err := client.GetClientQueryContext(cmd) 63 | if err != nil { 64 | return err 65 | } 66 | queryClient := types.NewQueryClient(clientCtx) 67 | 68 | res, err := queryClient.IsSubscribed(cmd.Context(), &types.QueryIsSubscribedRequest{ 69 | ContractAddress: args[0], 70 | }) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | return clientCtx.PrintProto(res) 76 | }, 77 | } 78 | flags.AddQueryFlagsToCmd(cmd) 79 | return cmd 80 | } 81 | 82 | // getQueryParamsCmd returns the command to query module parameters. 83 | func getQueryParamsCmd() *cobra.Command { 84 | cmd := &cobra.Command{ 85 | Use: "params", 86 | Args: cobra.NoArgs, 87 | Short: "Query module parameters", 88 | RunE: func(cmd *cobra.Command, _ []string) error { 89 | clientCtx, err := client.GetClientQueryContext(cmd) 90 | if err != nil { 91 | return err 92 | } 93 | queryClient := types.NewQueryClient(clientCtx) 94 | 95 | res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | return clientCtx.PrintProto(&res.Params) 101 | }, 102 | } 103 | flags.AddQueryFlagsToCmd(cmd) 104 | return cmd 105 | } 106 | -------------------------------------------------------------------------------- /x/cwerrors/client/cli/tx.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/client" 5 | "github.com/cosmos/cosmos-sdk/client/flags" 6 | "github.com/cosmos/cosmos-sdk/client/tx" 7 | "github.com/spf13/cobra" 8 | 9 | "github.com/dymensionxyz/rollapp-wasm/pkg" 10 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 11 | ) 12 | 13 | // GetTxCmd builds tx command group for the module. 14 | func GetTxCmd() *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: types.ModuleName, 17 | Short: "Transaction commands for the cwerrors module", 18 | DisableFlagParsing: true, 19 | SuggestionsMinimumDistance: 2, 20 | RunE: client.ValidateCmd, 21 | } 22 | cmd.AddCommand( 23 | getTxSubscribeToErrorCmd(), 24 | ) 25 | 26 | return cmd 27 | } 28 | 29 | // getTxSubscribeToErrorCmd returns the command to subscribe to error callbacks for a contract address. 30 | func getTxSubscribeToErrorCmd() *cobra.Command { 31 | cmd := &cobra.Command{ 32 | Use: "subscribe-to-error [contract-address] [fee-amount]", 33 | Args: cobra.ExactArgs(2), 34 | Short: "Subscribe to error callbacks for a contract address", 35 | Aliases: []string{"subscribe"}, 36 | RunE: func(cmd *cobra.Command, args []string) error { 37 | clientCtx, err := client.GetClientTxContext(cmd) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | senderAddr := clientCtx.GetFromAddress() 43 | 44 | fees, err := pkg.ParseCoinArg("fee-amount", args[1]) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | msg := types.MsgSubscribeToError{ 50 | Sender: senderAddr.String(), 51 | ContractAddress: args[0], 52 | Fee: fees, 53 | } 54 | return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) 55 | }, 56 | } 57 | 58 | flags.AddTxFlagsToCmd(cmd) 59 | 60 | return cmd 61 | } 62 | -------------------------------------------------------------------------------- /x/cwerrors/genesis.go: -------------------------------------------------------------------------------- 1 | package cwerrors 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | 6 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/keeper" 7 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 8 | ) 9 | 10 | // InitGenesis initializes the module genesis state. 11 | func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { 12 | params := genState.Params 13 | if err := k.Params.Set(ctx, params); err != nil { 14 | panic(err) 15 | } 16 | } 17 | 18 | // ExportGenesis exports the module genesis for the current block. 19 | func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { 20 | params, err := k.Params.Get(ctx) 21 | if err != nil { 22 | panic(err) 23 | } 24 | sudoErrs, err := k.ExportErrors(ctx) 25 | if err != nil { 26 | panic(err) 27 | } 28 | genesis := types.NewGenesisState(params) 29 | genesis.Errors = sudoErrs 30 | return genesis 31 | } 32 | -------------------------------------------------------------------------------- /x/cwerrors/genesis_test.go: -------------------------------------------------------------------------------- 1 | package cwerrors_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | 10 | e2eTesting "github.com/dymensionxyz/rollapp-wasm/e2e/testing" 11 | "github.com/dymensionxyz/rollapp-wasm/pkg/testutils" 12 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors" 13 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 14 | ) 15 | 16 | func TestExportGenesis(t *testing.T) { 17 | chain := e2eTesting.NewTestChain(t, 1) 18 | ctx, keeper := chain.GetContext(), chain.GetApp().CWErrorsKeeper 19 | contractViewer := testutils.NewMockContractViewer() 20 | keeper.SetWasmKeeper(contractViewer) 21 | contractAddresses := e2eTesting.GenContractAddresses(3) 22 | contractAddr := contractAddresses[0] 23 | contractViewer.AddContractAdmin( 24 | contractAddr.String(), 25 | contractAddr.String(), 26 | ) 27 | err := keeper.SetError(ctx, types.SudoError{ContractAddress: contractAddr.String(), ModuleName: "test"}) 28 | require.NoError(t, err) 29 | err = keeper.SetError(ctx, types.SudoError{ContractAddress: contractAddr.String(), ModuleName: "test"}) 30 | require.NoError(t, err) 31 | 32 | exportedState := cwerrors.ExportGenesis(ctx, keeper) 33 | require.Equal(t, types.DefaultParams(), exportedState.Params) 34 | require.Len(t, exportedState.Errors, 2) 35 | 36 | newParams := types.Params{ 37 | ErrorStoredTime: 99999, 38 | SubscriptionFee: sdk.NewCoin("stake", sdk.NewInt(100)), 39 | SubscriptionPeriod: 1, 40 | } 41 | err = keeper.SetParams(ctx, newParams) 42 | require.NoError(t, err) 43 | 44 | exportedState = cwerrors.ExportGenesis(ctx, keeper) 45 | require.Equal(t, newParams.ErrorStoredTime, exportedState.Params.ErrorStoredTime) 46 | require.Equal(t, newParams.SubscriptionFee, exportedState.Params.SubscriptionFee) 47 | require.Equal(t, newParams.SubscriptionPeriod, exportedState.Params.SubscriptionPeriod) 48 | } 49 | 50 | func TestInitGenesis(t *testing.T) { 51 | chain := e2eTesting.NewTestChain(t, 1) 52 | ctx, keeper := chain.GetContext(), chain.GetApp().CWErrorsKeeper 53 | 54 | genstate := types.GenesisState{ 55 | Params: types.DefaultGenesis().Params, 56 | } 57 | cwerrors.InitGenesis(ctx, keeper, genstate) 58 | 59 | params, err := keeper.GetParams(ctx) 60 | require.NoError(t, err) 61 | require.Equal(t, types.DefaultParams(), params) 62 | 63 | genstate = types.GenesisState{ 64 | Params: types.Params{ 65 | ErrorStoredTime: 99999, 66 | SubscriptionFee: sdk.NewCoin("stake", sdk.NewInt(100)), 67 | SubscriptionPeriod: 1, 68 | }, 69 | Errors: []types.SudoError{ 70 | {ContractAddress: "addr1", ModuleName: "test"}, 71 | }, 72 | } 73 | cwerrors.InitGenesis(ctx, keeper, genstate) 74 | 75 | params, err = keeper.GetParams(ctx) 76 | require.NoError(t, err) 77 | require.Equal(t, genstate.Params.ErrorStoredTime, params.ErrorStoredTime) 78 | require.Equal(t, genstate.Params.SubscriptionFee, params.SubscriptionFee) 79 | require.Equal(t, genstate.Params.SubscriptionPeriod, params.SubscriptionPeriod) 80 | 81 | sudoErrs, err := keeper.ExportErrors(ctx) 82 | require.NoError(t, err) 83 | require.Len(t, sudoErrs, 0) // We only export errors and dont import them as they wont be relevant 84 | } 85 | -------------------------------------------------------------------------------- /x/cwerrors/keeper/grpc_query.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | 10 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 11 | ) 12 | 13 | var _ types.QueryServer = &QueryServer{} 14 | 15 | // QueryServer implements the module gRPC query service. 16 | type QueryServer struct { 17 | keeper Keeper 18 | } 19 | 20 | // NewQueryServer creates a new gRPC query server. 21 | func NewQueryServer(keeper Keeper) *QueryServer { 22 | return &QueryServer{ 23 | keeper: keeper, 24 | } 25 | } 26 | 27 | // Errors implements types.QueryServer. 28 | func (qs *QueryServer) Errors(c context.Context, request *types.QueryErrorsRequest) (*types.QueryErrorsResponse, error) { 29 | if request == nil { 30 | return nil, status.Error(codes.InvalidArgument, "empty request") 31 | } 32 | 33 | contractAddr, err := sdk.AccAddressFromBech32(request.ContractAddress) 34 | if err != nil { 35 | return nil, status.Errorf(codes.InvalidArgument, "invalid contract address: %s", err.Error()) 36 | } 37 | 38 | errors, err := qs.keeper.GetErrorsByContractAddress(sdk.UnwrapSDKContext(c), contractAddr) 39 | if err != nil { 40 | return nil, status.Errorf(codes.NotFound, "could not fetch the errors: %s", err.Error()) 41 | } 42 | 43 | return &types.QueryErrorsResponse{ 44 | Errors: errors, 45 | }, nil 46 | } 47 | 48 | // IsSubscribed implements types.QueryServer. 49 | func (qs *QueryServer) IsSubscribed(c context.Context, request *types.QueryIsSubscribedRequest) (*types.QueryIsSubscribedResponse, error) { 50 | if request == nil { 51 | return nil, status.Error(codes.InvalidArgument, "empty request") 52 | } 53 | 54 | contractAddr, err := sdk.AccAddressFromBech32(request.ContractAddress) 55 | if err != nil { 56 | return nil, status.Errorf(codes.InvalidArgument, "invalid contract address: %s", err.Error()) 57 | } 58 | 59 | hasSub, validtill := qs.keeper.GetSubscription(sdk.UnwrapSDKContext(c), contractAddr) 60 | return &types.QueryIsSubscribedResponse{ 61 | Subscribed: hasSub, 62 | SubscriptionValidTill: validtill, 63 | }, nil 64 | } 65 | 66 | // Params implements types.QueryServer. 67 | func (qs *QueryServer) Params(c context.Context, request *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { 68 | if request == nil { 69 | return nil, status.Error(codes.InvalidArgument, "empty request") 70 | } 71 | 72 | params, err := qs.keeper.GetParams(sdk.UnwrapSDKContext(c)) 73 | if err != nil { 74 | return nil, status.Errorf(codes.NotFound, "could not fetch the module params: %s", err.Error()) 75 | } 76 | 77 | return &types.QueryParamsResponse{ 78 | Params: params, 79 | }, nil 80 | } 81 | -------------------------------------------------------------------------------- /x/cwerrors/keeper/keeper_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | 8 | e2eTesting "github.com/dymensionxyz/rollapp-wasm/e2e/testing" 9 | ) 10 | 11 | type KeeperTestSuite struct { 12 | suite.Suite 13 | 14 | chain *e2eTesting.TestChain 15 | } 16 | 17 | func (s *KeeperTestSuite) SetupTest() { 18 | s.chain = e2eTesting.NewTestChain(s.T(), 1) 19 | } 20 | 21 | func TestCWErrorsKeeper(t *testing.T) { 22 | suite.Run(t, new(KeeperTestSuite)) 23 | } 24 | -------------------------------------------------------------------------------- /x/cwerrors/keeper/msg_server.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | 8 | errorsmod "cosmossdk.io/errors" 9 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/status" 12 | 13 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 14 | ) 15 | 16 | var _ types.MsgServer = (*MsgServer)(nil) 17 | 18 | // MsgServer implements the module gRPC messaging service. 19 | type MsgServer struct { 20 | keeper Keeper 21 | } 22 | 23 | // NewMsgServer creates a new gRPC messaging server. 24 | func NewMsgServer(keeper Keeper) *MsgServer { 25 | return &MsgServer{ 26 | keeper: keeper, 27 | } 28 | } 29 | 30 | // SubscribeToError implements types.MsgServer. 31 | func (s *MsgServer) SubscribeToError(c context.Context, request *types.MsgSubscribeToError) (*types.MsgSubscribeToErrorResponse, error) { 32 | if request == nil { 33 | return nil, status.Error(codes.InvalidArgument, "empty request") 34 | } 35 | 36 | sender, err := sdk.AccAddressFromBech32(request.Sender) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | contractAddr, err := sdk.AccAddressFromBech32(request.ContractAddress) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | ctx := sdk.UnwrapSDKContext(c) 47 | subscriptionEndHeight, err := s.keeper.SetSubscription(ctx, sender, contractAddr, request.Fee) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | types.EmitSubscribedToErrorsEvent( 53 | ctx, 54 | request.Sender, 55 | request.ContractAddress, 56 | request.Fee, 57 | subscriptionEndHeight, 58 | ) 59 | return &types.MsgSubscribeToErrorResponse{ 60 | SubscriptionValidTill: subscriptionEndHeight, 61 | }, nil 62 | } 63 | 64 | func (s *MsgServer) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { 65 | ctx := sdk.UnwrapSDKContext(goCtx) 66 | 67 | if s.keeper.authority != msg.Authority { 68 | return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "invalid authority; expected %s, got %s", s.keeper.authority, msg.Authority) 69 | } 70 | 71 | err := msg.ValidateBasic() 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | err = s.keeper.SetParams(ctx, msg.Params) 77 | if err != nil { 78 | return nil, err 79 | } 80 | return &types.MsgUpdateParamsResponse{}, nil 81 | } 82 | -------------------------------------------------------------------------------- /x/cwerrors/keeper/params.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | 6 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 7 | ) 8 | 9 | // GetParams return all module parameters. 10 | func (k Keeper) GetParams(ctx sdk.Context) (params types.Params, err error) { 11 | return k.Params.Get(ctx) 12 | } 13 | 14 | // SetParams sets all module parameters. 15 | func (k Keeper) SetParams(ctx sdk.Context, params types.Params) error { 16 | return k.Params.Set(ctx, params) 17 | } 18 | -------------------------------------------------------------------------------- /x/cwerrors/spec/01_state.md: -------------------------------------------------------------------------------- 1 | # State 2 | 3 | Section describes all stored by the module objects and their storage keys 4 | 5 | ## Params 6 | 7 | [Params](../../../proto/rollapp/cwerrors/v1/params.proto) object is used to store the module params 8 | 9 | The params value can only be updated by x/gov module via a governance upgrade proposal. [More](./02_messages.md#msgupdateparams) 10 | 11 | Storage keys: 12 | 13 | * Params: `ParamsKey -> ProtocolBuffer(Params)` 14 | 15 | ```protobuf 16 | message Params { 17 | // error_stored_time is the relative block height until which error is stored 18 | int64 error_stored_time = 1; 19 | // subsciption_fee is the fee required to subscribe to error callbacks 20 | cosmos.base.v1beta1.Coin subscription_fee = 2 [ (gogoproto.nullable) = false ]; 21 | // subscription_period is the period for which the subscription is valid 22 | int64 subscription_period = 3; 23 | } 24 | ``` 25 | 26 | ## ErrorID 27 | 28 | ErrorID is a sequence number used to increment error ID. 29 | 30 | Storage keys: 31 | 32 | * ErrorID: `ErrorIDKey -> uint64` 33 | 34 | ## Contract Errors 35 | 36 | Contract Errors is a collection of all the error ids associated with a given contract address. This is used to query contract errors. 37 | 38 | Storage keys: 39 | 40 | * ContractErrors: `ContractErrorsKeyPrefix | contractAddress | errorID -> errorID` 41 | 42 | ## Errors 43 | 44 | Errors is a collections of all the [SudoErrors](../../../proto/rollapp/cwerrors/v1/cwerrors.proto) currently stored by the module which can be queried. 45 | 46 | Storage keys: 47 | 48 | * Errors: `ErrorsKeyPrefix | errorID -> protobuf(SudoError)` 49 | 50 | ```protobuf 51 | message SudoError { 52 | // module_name is the name of the module throwing the error 53 | string module_name = 1; 54 | // error_code is the module level error code 55 | uint32 error_code = 2; 56 | // contract_address is the address of the contract which will receive the error callback 57 | string contract_address = 3; 58 | // input_payload is any input which caused the error 59 | string input_payload = 4; 60 | // error_message is the error message 61 | string error_message = 5; 62 | } 63 | ``` 64 | 65 | ## Deletion Blocks 66 | 67 | Deletion Blocks is a collection of all the error ids which need to be pruned in a given block height 68 | 69 | Storage keys: 70 | 71 | * DeletionBlocks: `DeletionBlocksKeyPrefix | blockHeight | errorID -> errorID` 72 | 73 | ## Contract Subscriptions 74 | 75 | Contract Subscriptions is a map of the contract addresses which have subscriptions and the height when the subscription expires 76 | 77 | Storage keys: 78 | 79 | * Contract Subscriptions: `ContractSubscriptionsKeyPrefix | contractAddress -> deletionHeight` 80 | 81 | ## Subscription End Block 82 | 83 | Subscritption End Block is a collections of all the subscriptions which need to be cleared at the given block height 84 | 85 | Storage keys: 86 | 87 | * Subscription End Block: `SubscriptionEndBlockKeyPrefix | blockHeight | contractAddress -> contractAddress` 88 | 89 | ## Transient State 90 | 91 | The sudo errors which belong to the contracts with subscription are stored in the transient state of the block. 92 | 93 | Transient Storage keys: 94 | 95 | * SudoErrors: `ErrorsForSudoCallbackKey | errorId -> SudoError` 96 | -------------------------------------------------------------------------------- /x/cwerrors/spec/02_messages.md: -------------------------------------------------------------------------------- 1 | # Messages 2 | 3 | Section describes the processing of the module messages 4 | 5 | ## MsgSubscribeToError 6 | 7 | A contract can be subscribed to errors by using the [MsgSubscribeToError](../../../proto/rollapp/cwerrors/v1/tx.proto) message. 8 | 9 | ```protobuf 10 | message MsgSubscribeToError { 11 | option (cosmos.msg.v1.signer) = "sender"; 12 | // sender is the address of who is registering the contracts for callback on error 13 | string sender = 1; 14 | // contract_address is the contarct subscribing to the error 15 | string contract_address = 2; 16 | // fee is the subscription fee for the feature 17 | cosmos.base.v1beta1.Coin fee = 3 [ (gogoproto.nullable) = false ]; 18 | } 19 | ``` 20 | 21 | On success 22 | 23 | * A subscription is created valid for the duration as specified in the module params. 24 | * The subscription fees are sent to the fee collector 25 | * In case a subscription already exists, it is extended. 26 | 27 | This message is expected to fail if: 28 | 29 | * The sender address and contract address are not valid addresses 30 | * There is no contract with given address 31 | * The sender is not authorized to subscribe - the sender is not the contract owner/admin or the contract itself 32 | * The user does not send enough funds or doesnt have enough funds 33 | -------------------------------------------------------------------------------- /x/cwerrors/spec/03_end_block.md: -------------------------------------------------------------------------------- 1 | # End Block 2 | 3 | Section describes the module state changes on the ABCI end block call 4 | 5 | ## SudoError invoke 6 | 7 | All the errors encountered in the current block are fetched from the transient store. For each error, a contract is hit at the sudo entrypoint. The execution happens with gas limit of `150_000` to prevent abusive operations and limit the usage to error handling. 8 | 9 | In case, the execution fails, the error is stored in state such that the contract can query it. 10 | 11 | ## Prune expiring subscription 12 | 13 | All the contract subscriptions which end in the current block are pruned from state 14 | 15 | ## Prune old errors 16 | 17 | All errors which are marked for deletion for the current block height are pruned from the state. 18 | -------------------------------------------------------------------------------- /x/cwerrors/spec/04_events.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | Section describes the module events 4 | 5 | The module emits the following proto-events 6 | 7 | | Source type | Source name | Protobuf reference | 8 | | ----------- | --------------------- |--------------------------------------------------------------------------------------| 9 | | Message | `MsgUpdateParams` | [ParamsUpdatedEvent](../../../proto/rollapp/cwerrors/v1/events.proto#L12) | 10 | | Message | `MsgSubscribeToError` | [SubscribedToErrorsEvent](../../../proto/rollapp/cwerrors/v1/events.proto#L20) | 11 | | Keeper | `SetErrorInState` | [StoringErrorEvent](../../../proto/rollapp/cwerrors/v1/events.proto#L32) | 12 | | Module | `EndBlocker` | [SudoErrorCallbackFailedEvent](../../../proto/rollapp/cwerrors/v1/events.proto#L40) | 13 | -------------------------------------------------------------------------------- /x/cwerrors/spec/05_client.md: -------------------------------------------------------------------------------- 1 | # Client 2 | 3 | Section describes interaction with the module by the user 4 | 5 | ## CLI 6 | 7 | ### Query 8 | 9 | The `query` commands alllows a user to query the module state 10 | 11 | Use the `-h`/`--help` flag to get a help description of a command. 12 | 13 | `rollapp-wasm q cwerrors -h` 14 | 15 | > You can add the `-o json` for the JSON output format 16 | 17 | #### params 18 | 19 | Get the current module parameters 20 | 21 | Usage: 22 | 23 | `rollapp-wasm q cwerrors params [flags]` 24 | 25 | Example output: 26 | 27 | ```yaml 28 | error_stored_time: "302400" 29 | subscription_fee: 30 | amount: "0" 31 | denom: stake 32 | subscription_period: "302400" 33 | ``` 34 | 35 | #### errors 36 | 37 | List all the errors for the given contract 38 | 39 | Usage: 40 | 41 | `rollapp-wasm q cwerrors errors [contract-address]` 42 | 43 | Example: 44 | 45 | `rollapp-wasm q cwerrors errors cosmos1wug8sewp6cedgkmrmvhl3lf3tulagm9hnvy8p0rppz9yjw0g4wtqukxvuk` 46 | 47 | Example output: 48 | 49 | ```yaml 50 | errors: 51 | - module_name: "callback" 52 | error_code: 2 53 | contract_address: cosmos1wug8sewp6cedgkmrmvhl3lf3tulagm9hnvy8p0rppz9yjw0g4wtqukxvuk 54 | input_payload: "{'job_id':1}" 55 | error_message: "Out of gas" 56 | ``` 57 | 58 | #### is-subscribed 59 | 60 | Lists if the given contract is subscribed to error callbacks and the block height it is valid till 61 | 62 | Usage: 63 | 64 | `rollapp-wasm q cwerrors is-subscribed [contract-address]` 65 | 66 | Example: 67 | 68 | `rollapp-wasm q cwerrors is-subscribed cosmos1wug8sewp6cedgkmrmvhl3lf3tulagm9hnvy8p0rppz9yjw0g4wtqukxvuk` 69 | 70 | Example output: 71 | 72 | ```yaml 73 | subscribed: true 74 | subscription_valid_till: 1234 75 | ``` 76 | 77 | ### TX 78 | 79 | The `tx` commands allows a user to interact with the module. 80 | 81 | Use the `-h`/`--help` flag to get a help description of a command. 82 | 83 | `rollapp-wasm tx cwerrors -h` 84 | 85 | #### subscribe-to-error 86 | 87 | Create a new subscription which will register a contract for a sudo callback on errors 88 | 89 | Usage: 90 | 91 | `rollapp-wasm tx cwerrors subscribe-to-error [contract-address] [fee-amount] [flags]` 92 | 93 | Example: 94 | 95 | `rollapp-wasm tx cwerrors subscribe-to-error cosmos1wug8sewp6cedgkmrmvhl3lf3tulagm9hnvy8p0rppz9yjw0g4wtqukxvuk 7000stake --from myAccountKey` 96 | -------------------------------------------------------------------------------- /x/cwerrors/spec/06_errors.md: -------------------------------------------------------------------------------- 1 | # Errors 2 | 3 | The module exposes the following error codes which are used with the x/cwerrors module in case of error callback failures. 4 | 5 | ```proto 6 | enum ModuleErrors { 7 | // ERR_UNKNOWN is the default error code 8 | ERR_UNKNOWN = 0; 9 | // ERR_CALLBACK_EXECUTION_FAILED is the error code for when the error callback fails 10 | ERR_CALLBACK_EXECUTION_FAILED = 1; 11 | } 12 | ``` 13 | -------------------------------------------------------------------------------- /x/cwerrors/spec/README.md: -------------------------------------------------------------------------------- 1 | # CWErrors 2 | 3 | This module is the error handling mechanism for the protocol to let the contract know of errors encountered 4 | 5 | ## Concepts 6 | 7 | [callback](../../callback/spec/README.md) module and [cwica](../../cwica/spec/README.md) module both rely on protocol invoking contract execution. In case, there are any errors generated by the protocol, there needs to be a way to let the contract know of this error. The module provides a standard way of error catching from the contract side. 8 | 9 | The module provides two diffferent ways that the error is communicated. 10 | 11 | ### 1. Errors saved in state (default) 12 | 13 | Whenever a contract relevant error is encountered by the protocol, the module stores the error and associates with the contract address. This is stored in the chain state for `x` number of blocks (The `x` value is a module parameter. [See more](./01_state.md)) after which the error is permanently deleted. These stored errors are queryable by the contract using stargate queries. 14 | 15 | ### 2. Errors sudo callback 16 | 17 | If a contract is subscribed to errors, then the contract will be invoked at the sudo entrypoint like so. 18 | 19 | ```rust 20 | #[cw_serde] 21 | pub enum SudoMsg { 22 | Error { 23 | module_name: String, // The name of the module which generated the error 24 | error_code: u32, // module specific error code 25 | contract_address: String, // the contract address which is associated with the error; the contract receiving the callback 26 | input_payload: String, // any relevant input payload which caused the error 27 | error_message: String, // the relevant error message 28 | } 29 | } 30 | ``` 31 | 32 | The subscriptions are an opt-in feature where the contractadmin/owner has to subscribe to the feature by paying the relevant fees. [See more](01_state.md) The subscription is valid for `x` number of blocks where `x` is decided by the module param. The subscription cannot be cancelled but can be extended by attempting to subscribe again. 33 | 34 | ## Contents 35 | 36 | 1. [State](./01_state.md) 37 | 2. [Messages](./02_messages.md) 38 | 3. [End Block](./03_end_block.md) 39 | 4. [Events](./04_events.md) 40 | 5. [Client](./05_client.md) 41 | 6. [Module Errors](./06_errors.md) 42 | -------------------------------------------------------------------------------- /x/cwerrors/types/codec.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | "github.com/cosmos/cosmos-sdk/codec/types" 6 | cryptoCodec "github.com/cosmos/cosmos-sdk/crypto/codec" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/cosmos/cosmos-sdk/types/msgservice" 9 | ) 10 | 11 | // RegisterLegacyAminoCodec registers the necessary interfaces and concrete types on the provided LegacyAmino codec. 12 | // These types are used for Amino JSON serialization. 13 | func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { 14 | cdc.RegisterConcrete(&MsgSubscribeToError{}, "cwerrors/MsgSubscribeToError", nil) 15 | cdc.RegisterConcrete(&MsgUpdateParams{}, "cwerrors/MsgUpdateParams", nil) 16 | } 17 | 18 | // RegisterInterfaces registers interfaces types with the interface registry. 19 | func RegisterInterfaces(registry types.InterfaceRegistry) { 20 | registry.RegisterImplementations((*sdk.Msg)(nil), 21 | &MsgSubscribeToError{}, 22 | &MsgUpdateParams{}, 23 | ) 24 | 25 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 26 | } 27 | 28 | var ( 29 | ModuleCdc = codec.NewAminoCodec(amino) 30 | amino = codec.NewLegacyAmino() 31 | ) 32 | 33 | func init() { 34 | RegisterLegacyAminoCodec(amino) 35 | cryptoCodec.RegisterCrypto(amino) 36 | sdk.RegisterLegacyAminoCodec(amino) 37 | amino.Seal() 38 | } 39 | -------------------------------------------------------------------------------- /x/cwerrors/types/errors.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import errorsmod "cosmossdk.io/errors" 4 | 5 | var ( 6 | DefaultCodespace = ModuleName 7 | ErrContractNotFound = errorsmod.Register(DefaultCodespace, 2, "contract with given address not found") 8 | ErrUnauthorized = errorsmod.Register(DefaultCodespace, 3, "sender unauthorized to perform the action") 9 | ErrModuleNameMissing = errorsmod.Register(DefaultCodespace, 4, "module name missing from sudo error") 10 | ErrIncorrectSubscriptionFee = errorsmod.Register(DefaultCodespace, 5, "incorrect subscription fee") 11 | ) 12 | -------------------------------------------------------------------------------- /x/cwerrors/types/events.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | ) 8 | 9 | // EmitParamsUpdatedEvent emits an event when the params are updated 10 | func EmitParamsUpdatedEvent(ctx sdk.Context, newParams Params) { 11 | err := ctx.EventManager().EmitTypedEvent(&ParamsUpdatedEvent{ 12 | NewParams: newParams, 13 | }) 14 | if err != nil { 15 | panic(fmt.Errorf("sending ParamsUpdatedEvent event: %w", err)) 16 | } 17 | } 18 | 19 | // EmitSubscribedToErrorsEvent emits an event when a contract is subscribed to errors 20 | func EmitSubscribedToErrorsEvent(ctx sdk.Context, sender, contractAddress string, fees sdk.Coin, subValidTill int64) { 21 | err := ctx.EventManager().EmitTypedEvent(&SubscribedToErrorsEvent{ 22 | Sender: sender, 23 | ContractAddress: contractAddress, 24 | FeesPaid: fees, 25 | SubscriptionValidTill: subValidTill, 26 | }) 27 | if err != nil { 28 | panic(fmt.Errorf("sending SubscribedToErrorsEvent event: %w", err)) 29 | } 30 | } 31 | 32 | // EmitStoringErrorEvent emits an event when an error is stored 33 | func EmitStoringErrorEvent(ctx sdk.Context, sudoError SudoError, deletionBlockHeight int64) { 34 | err := ctx.EventManager().EmitTypedEvent(&StoringErrorEvent{ 35 | Error: sudoError, 36 | DeletionBlockHeight: deletionBlockHeight, 37 | }) 38 | if err != nil { 39 | panic(fmt.Errorf("sending StoringErrorEvent event: %w", err)) 40 | } 41 | } 42 | 43 | // EmitSudoErrorCallbackFailedEvent emits an event when a callback for a sudo error fails 44 | func EmitSudoErrorCallbackFailedEvent(ctx sdk.Context, sudoError SudoError, callbackErr string) { 45 | err := ctx.EventManager().EmitTypedEvent(&SudoErrorCallbackFailedEvent{ 46 | Error: sudoError, 47 | CallbackErrorMessage: callbackErr, 48 | }) 49 | if err != nil { 50 | panic(fmt.Errorf("sending SudoErrorCallbackFailedEvent event: %w", err)) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /x/cwerrors/types/expected_keepers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | wasmdtypes "github.com/CosmWasm/wasmd/x/wasm/types" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | // WasmKeeperExpected is a subset of the expected wasm keeper 9 | type WasmKeeperExpected interface { 10 | // HasContractInfo returns true if the contract exists 11 | HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool 12 | // Sudo executes a contract message as a sudoer 13 | Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) 14 | // GetContractInfo returns the contract info 15 | GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmdtypes.ContractInfo 16 | } 17 | 18 | // BankKeeperExpected is a subset of the expected bank keeper 19 | type BankKeeperExpected interface { 20 | // SendCoinsFromAccountToModule sends coins from an account to a module 21 | SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error 22 | } 23 | 24 | -------------------------------------------------------------------------------- /x/cwerrors/types/genesis.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // NewGenesisState creates a new GenesisState object. 4 | func NewGenesisState( 5 | params Params, 6 | ) *GenesisState { 7 | return &GenesisState{ 8 | Params: params, 9 | Errors: nil, 10 | } 11 | } 12 | 13 | // DefaultGenesisState returns a default genesis state. 14 | func DefaultGenesis() *GenesisState { 15 | defaultParams := DefaultParams() 16 | return NewGenesisState(defaultParams) 17 | } 18 | 19 | // Validate perform object fields validation. 20 | func (g GenesisState) Validate() error { 21 | return g.Params.Validate() 22 | } 23 | -------------------------------------------------------------------------------- /x/cwerrors/types/genesis_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 10 | ) 11 | 12 | func TestGenesisValidate(t *testing.T) { 13 | type testCase struct { 14 | name string 15 | genesis types.GenesisState 16 | errExpected bool 17 | } 18 | 19 | testCases := []testCase{ 20 | { 21 | name: "Fail: Empty values", 22 | genesis: types.GenesisState{}, 23 | errExpected: true, 24 | }, 25 | { 26 | name: "Fail: Invalid params", 27 | genesis: types.GenesisState{ 28 | Params: types.NewParams( 29 | 0, 30 | sdk.NewInt64Coin(sdk.DefaultBondDenom, 100), 31 | 100, 32 | ), 33 | }, 34 | errExpected: true, 35 | }, 36 | { 37 | name: "OK: Valid genesis state", 38 | genesis: types.GenesisState{ 39 | Params: types.DefaultParams(), 40 | }, 41 | errExpected: false, 42 | }, 43 | } 44 | 45 | for _, tc := range testCases { 46 | t.Run(tc.name, func(t *testing.T) { 47 | err := tc.genesis.Validate() 48 | if tc.errExpected { 49 | assert.Error(t, err) 50 | return 51 | } 52 | assert.NoError(t, err) 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /x/cwerrors/types/keys.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "cosmossdk.io/collections" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | ) 7 | 8 | const ( 9 | // ModuleName is the module name. 10 | ModuleName = "cwerrors" 11 | // StoreKey is the module KV storage prefix key. 12 | StoreKey = ModuleName 13 | // QuerierRoute is the querier route for the module. 14 | QuerierRoute = ModuleName 15 | // TStoreKey defines the transient store key 16 | TStoreKey = "t_" + ModuleName 17 | 18 | RouterKey = ModuleName 19 | ) 20 | 21 | // Collections 22 | var ( 23 | // ParamsKeyPrefix is the prefix for the module parameter store. 24 | ParamsKeyPrefix = collections.NewPrefix(1) 25 | // ErrorIDKey is the prefix for the count of errors 26 | ErrorIDKey = collections.NewPrefix(2) 27 | // ContractErrorsKeyPrefix is the prefix for the collection of all error ids for a given contractzs 28 | ContractErrorsKeyPrefix = collections.NewPrefix(3) 29 | // ErrorsKeyPrefix is the prefix for the collection of all errors 30 | ErrorsKeyPrefix = collections.NewPrefix(4) 31 | // DeletionBlocksKeyPrefix is the prefix for the collection of all error ids which need to be deleted in given block 32 | DeletionBlocksKeyPrefix = collections.NewPrefix(5) 33 | // ContractSubscriptionsKeyPrefix is the prefix for the collection of all contracts with subscriptions 34 | ContractSubscriptionsKeyPrefix = collections.NewPrefix(6) 35 | // SubscriptionEndBlockKeyPrefix is the prefix for the collection of all subscriptions which end at given block 36 | SubscriptionEndBlockKeyPrefix = collections.NewPrefix(7) 37 | ) 38 | 39 | // Transient Store 40 | var ( 41 | ErrorsForSudoCallbackKey = []byte{0x00} 42 | ) 43 | 44 | func GetErrorsForSudoCallStoreKey(errorID uint64) []byte { 45 | return append(ErrorsForSudoCallbackKey, sdk.Uint64ToBigEndian(errorID)...) 46 | } 47 | -------------------------------------------------------------------------------- /x/cwerrors/types/msg.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | errorsmod "cosmossdk.io/errors" 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | sdkErrors "github.com/cosmos/cosmos-sdk/types/errors" 8 | ) 9 | 10 | const ( 11 | TypeMsgUpdateParams = "update_params" 12 | ) 13 | 14 | var ( 15 | _ sdk.Msg = &MsgSubscribeToError{} 16 | _ sdk.Msg = &MsgUpdateParams{} 17 | ) 18 | 19 | // GetSigners implements the sdk.Msg interface. 20 | func (m MsgSubscribeToError) GetSigners() []sdk.AccAddress { 21 | return []sdk.AccAddress{sdk.MustAccAddressFromBech32(m.Sender)} 22 | } 23 | 24 | // ValidateBasic implements the sdk.Msg interface. 25 | func (m MsgSubscribeToError) ValidateBasic() error { 26 | if _, err := sdk.AccAddressFromBech32(m.Sender); err != nil { 27 | return errorsmod.Wrapf(sdkErrors.ErrInvalidAddress, "invalid sender address: %v", err) 28 | } 29 | if _, err := sdk.AccAddressFromBech32(m.ContractAddress); err != nil { 30 | return errorsmod.Wrapf(sdkErrors.ErrInvalidAddress, "invalid contract address: %v", err) 31 | } 32 | if err := m.Fee.Validate(); err != nil { 33 | return errorsmod.Wrapf(sdkErrors.ErrInvalidCoins, "invalid fee: %v", err) 34 | } 35 | return nil 36 | } 37 | 38 | // GetSigners implements types.Msg. 39 | func (m *MsgUpdateParams) GetSigners() []sdk.AccAddress { 40 | addr, _ := sdk.AccAddressFromBech32(m.Authority) 41 | return []sdk.AccAddress{addr} 42 | } 43 | 44 | // ValidateBasic implements types.Msg. 45 | func (m *MsgUpdateParams) ValidateBasic() error { 46 | if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { 47 | return fmt.Errorf("invalid authority address: %w", err) 48 | } 49 | 50 | if err := m.Params.Validate(); err != nil { 51 | return err 52 | } 53 | 54 | return nil 55 | } 56 | 57 | func (m *MsgUpdateParams) GetSignBytes() []byte { 58 | bz := ModuleCdc.MustMarshalJSON(m) 59 | return sdk.MustSortJSON(bz) 60 | } 61 | 62 | func (m *MsgUpdateParams) Route() string { 63 | return RouterKey 64 | } 65 | 66 | func (m *MsgUpdateParams) Type() string { 67 | return TypeMsgUpdateParams 68 | } -------------------------------------------------------------------------------- /x/cwerrors/types/params.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | fmt "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | ) 8 | 9 | var ( 10 | DefaultErrorStoredTime = int64(302400) // roughly 21 days 11 | DefaultSubscriptionFee = sdk.NewInt64Coin(sdk.DefaultBondDenom, 0) // 1 ARCH (1e18 attoarch) 12 | DefaultSubscriptionPeriod = int64(302400) // roughly 21 days 13 | ) 14 | 15 | // NewParams creates a new Params instance. 16 | func NewParams( 17 | errorStoredTime int64, 18 | subscriptionFee sdk.Coin, 19 | subscriptionPeriod int64, 20 | ) Params { 21 | return Params{ 22 | ErrorStoredTime: errorStoredTime, 23 | SubscriptionFee: subscriptionFee, 24 | SubscriptionPeriod: subscriptionPeriod, 25 | } 26 | } 27 | 28 | // DefaultParams returns a default set of parameters. 29 | func DefaultParams() Params { 30 | return NewParams( 31 | DefaultErrorStoredTime, 32 | DefaultSubscriptionFee, 33 | DefaultSubscriptionPeriod, 34 | ) 35 | } 36 | 37 | // Validate perform object fields validation. 38 | func (p Params) Validate() error { 39 | if p.ErrorStoredTime <= 0 { 40 | return fmt.Errorf("ErrorStoredTime must be greater than 0. Current value: %d", p.ErrorStoredTime) 41 | } 42 | if !p.SubscriptionFee.IsValid() { 43 | return fmt.Errorf("SubscriptionFee is not valid. Current value: %s", p.SubscriptionFee) 44 | } 45 | if p.SubscriptionPeriod <= 0 { 46 | return fmt.Errorf("SubscriptionPeriod must be greater than 0. Current value: %d", p.SubscriptionPeriod) 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /x/cwerrors/types/params_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 10 | ) 11 | 12 | func TestParamsValidate(t *testing.T) { 13 | type testCase struct { 14 | name string 15 | params types.Params 16 | errExpected bool 17 | } 18 | 19 | testCases := []testCase{ 20 | { 21 | name: "OK: Default values", 22 | params: types.DefaultParams(), 23 | errExpected: false, 24 | }, 25 | { 26 | name: "OK: All valid values", 27 | params: types.NewParams( 28 | 100, 29 | sdk.NewInt64Coin(sdk.DefaultBondDenom, 100), 30 | 100, 31 | ), 32 | errExpected: false, 33 | }, 34 | { 35 | name: "Fail: ErrorStoredTime: zero", 36 | params: types.NewParams( 37 | 0, 38 | sdk.NewInt64Coin(sdk.DefaultBondDenom, 100), 39 | 100, 40 | ), 41 | errExpected: true, 42 | }, 43 | { 44 | name: "Fail: ErrorStoredTime: negative", 45 | params: types.NewParams( 46 | -2, 47 | sdk.NewInt64Coin(sdk.DefaultBondDenom, 100), 48 | 100, 49 | ), 50 | errExpected: true, 51 | }, 52 | { 53 | name: "Fail: SubsciptionFee: invalid", 54 | params: types.NewParams( 55 | 100, 56 | sdk.Coin{Denom: "", Amount: sdk.NewInt(100)}, 57 | 100, 58 | ), 59 | errExpected: true, 60 | }, 61 | { 62 | name: "Fail: SubscriptionPeriod: zero", 63 | params: types.NewParams( 64 | 100, 65 | sdk.NewInt64Coin(sdk.DefaultBondDenom, 100), 66 | -2, 67 | ), 68 | errExpected: true, 69 | }, 70 | } 71 | 72 | for _, tc := range testCases { 73 | t.Run(tc.name, func(t *testing.T) { 74 | err := tc.params.Validate() 75 | if tc.errExpected { 76 | assert.Error(t, err) 77 | return 78 | } 79 | assert.NoError(t, err) 80 | }) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /x/cwerrors/types/sudo_errors_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | e2eTesting "github.com/dymensionxyz/rollapp-wasm/e2e/testing" 9 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 10 | ) 11 | 12 | // TestSudoErrorValidate tests the json encoding of the sudo callback which is sent to the contract 13 | func TestSudoErrorMsgString(t *testing.T) { 14 | contractAddr := e2eTesting.GenContractAddresses(1)[0] 15 | testCases := []struct { 16 | testCase string 17 | msg types.SudoError 18 | expectedMsg string 19 | }{ 20 | { 21 | "ok", 22 | types.SudoError{ 23 | ModuleName: "callback", 24 | ContractAddress: contractAddr.String(), 25 | ErrorCode: 1, 26 | InputPayload: "hello", 27 | ErrorMessage: "world", 28 | }, 29 | `{"module_name":"callback","error_code":1,"contract_address":"cosmos1w0w8sasnut0jx0vvsnvlc8nayq0q2ej8xgrpwgel05tn6wy4r57q8wwdxx","input_payload":"hello","error_message":"world"}`, 30 | }, 31 | } 32 | 33 | for _, tc := range testCases { 34 | tc := tc 35 | t.Run(tc.testCase, func(t *testing.T) { 36 | res := tc.msg.Bytes() 37 | require.EqualValues(t, tc.expectedMsg, string(res)) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /x/cwerrors/types/sudo_msg.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "encoding/json" 4 | 5 | // SudoMsg is message sent to a contract. 6 | // This is encoded as JSON input to the contract when executing the callback 7 | type SudoMsg struct { 8 | // Error is the endpoint name at the contract which is called 9 | Error *SudoError `json:"error,omitempty"` 10 | } 11 | 12 | // NewSudoMsg creates a new SudoMsg instance. 13 | func NewSudoMsg(sudoErr SudoError) SudoMsg { 14 | return SudoMsg{ 15 | Error: &sudoErr, 16 | } 17 | } 18 | 19 | // Bytes returns the sudo message as JSON bytes 20 | func (s SudoMsg) Bytes() []byte { 21 | msgBz, err := json.Marshal(s) 22 | if err != nil { 23 | panic(err) 24 | } 25 | return msgBz 26 | } 27 | 28 | // String returns the sudo message as JSON string 29 | func (s SudoMsg) String() string { 30 | return string(s.Bytes()) 31 | } 32 | -------------------------------------------------------------------------------- /x/cwerrors/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | ) 8 | 9 | // Validate perform object fields validation. 10 | func (s SudoError) Validate() error { 11 | _, err := sdk.AccAddressFromBech32(s.ContractAddress) 12 | if err != nil { 13 | return err 14 | } 15 | if s.ModuleName == "" { 16 | return ErrModuleNameMissing 17 | } 18 | return nil 19 | } 20 | 21 | // Bytes returns the json encoding of the sudo callback which is sent to the contract 22 | func (s SudoError) Bytes() []byte { 23 | msgBz, err := json.Marshal(s) 24 | if err != nil { 25 | panic(err) 26 | } 27 | return msgBz 28 | } 29 | -------------------------------------------------------------------------------- /x/cwerrors/types/types_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | e2eTesting "github.com/dymensionxyz/rollapp-wasm/e2e/testing" 9 | "github.com/dymensionxyz/rollapp-wasm/x/cwerrors/types" 10 | ) 11 | 12 | func TestSudoErrorValidate(t *testing.T) { 13 | contractAddr := e2eTesting.GenContractAddresses(1)[0] 14 | 15 | type testCase struct { 16 | name string 17 | sudoError types.SudoError 18 | errExpected bool 19 | } 20 | 21 | testCases := []testCase{ 22 | { 23 | name: "Fail: Empty values", 24 | sudoError: types.SudoError{}, 25 | errExpected: true, 26 | }, 27 | { 28 | name: "Fail: Invalid contract address", 29 | sudoError: types.SudoError{ 30 | ContractAddress: "👻", 31 | ModuleName: "test", 32 | ErrorCode: 1, 33 | InputPayload: "test", 34 | ErrorMessage: "test", 35 | }, 36 | errExpected: true, 37 | }, 38 | { 39 | name: "Fail: Invalid module name", 40 | sudoError: types.SudoError{ 41 | ContractAddress: contractAddr.String(), 42 | ModuleName: "", 43 | ErrorCode: 1, 44 | InputPayload: "test", 45 | ErrorMessage: "test", 46 | }, 47 | errExpected: true, 48 | }, 49 | { 50 | name: "OK: Valid callback", 51 | sudoError: types.SudoError{ 52 | ContractAddress: contractAddr.String(), 53 | ModuleName: "test", 54 | ErrorCode: 1, 55 | InputPayload: "test", 56 | ErrorMessage: "test", 57 | }, 58 | errExpected: false, 59 | }, 60 | } 61 | 62 | for _, tc := range testCases { 63 | t.Run(tc.name, func(t *testing.T) { 64 | err := tc.sudoError.Validate() 65 | if tc.errExpected { 66 | assert.Error(t, err) 67 | return 68 | } 69 | assert.NoError(t, err) 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /x/wasm/authz.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | errors "errors" 5 | "fmt" 6 | "slices" 7 | 8 | wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | authztypes "github.com/cosmos/cosmos-sdk/x/authz" 11 | ) 12 | 13 | var _ authztypes.Authorization = &ContractExecutionAuthorization{} 14 | 15 | func NewContractExecutionAuthorization(contracts []string, spendLimit sdk.Coins) *ContractExecutionAuthorization { 16 | return &ContractExecutionAuthorization{ 17 | Contracts: contracts, 18 | SpendLimit: spendLimit, 19 | } 20 | } 21 | 22 | // MsgTypeURL implements Authorization.MsgTypeURL. 23 | func (a *ContractExecutionAuthorization) MsgTypeURL() string { 24 | return sdk.MsgTypeURL(&wasmtypes.MsgExecuteContract{}) 25 | } 26 | 27 | func (a *ContractExecutionAuthorization) Accept(_ sdk.Context, msg sdk.Msg) (authztypes.AcceptResponse, error) { 28 | m, ok := msg.(*wasmtypes.MsgExecuteContract) 29 | if !ok { 30 | return authztypes.AcceptResponse{}, errors.New("invalid message type") 31 | } 32 | 33 | // Check whitelisted contracts if specified 34 | if len(a.Contracts) > 0 && !slices.Contains(a.Contracts, m.Contract) { 35 | return authztypes.AcceptResponse{}, errors.New("contract not authorized") 36 | } 37 | 38 | // Check spend limits if specified 39 | if !a.SpendLimit.Empty() { 40 | if m.Funds.IsAnyGT(a.SpendLimit) { 41 | return authztypes.AcceptResponse{}, errors.New("exceeds spend limit") 42 | } 43 | 44 | // Update spend limits 45 | a.SpendLimit = a.SpendLimit.Sub(m.Funds...) 46 | if a.SpendLimit.Empty() { 47 | return authztypes.AcceptResponse{Accept: true, Delete: true}, nil 48 | } 49 | } 50 | 51 | return authztypes.AcceptResponse{Accept: true, Updated: a}, nil 52 | } 53 | 54 | func (a *ContractExecutionAuthorization) ValidateBasic() error { 55 | // Check for duplicate contracts 56 | contractSet := make(map[string]struct{}) 57 | for _, contract := range a.Contracts { 58 | if _, err := sdk.AccAddressFromBech32(contract); err != nil { 59 | return fmt.Errorf("invalid contract address: %s: %w", contract, err) 60 | } 61 | if _, exists := contractSet[contract]; exists { 62 | return fmt.Errorf("duplicate contract address: %s", contract) 63 | } 64 | contractSet[contract] = struct{}{} 65 | } 66 | 67 | if !a.SpendLimit.IsZero() { 68 | err := a.SpendLimit.Validate() 69 | if err != nil { 70 | return fmt.Errorf("validate spend limit: %s", err) 71 | } 72 | } 73 | 74 | return nil 75 | } 76 | --------------------------------------------------------------------------------