├── .github ├── dependabro.yml └── workflows │ ├── build.yml │ ├── check-docs.yml │ ├── deploy-docs.yml │ ├── integration.yml │ ├── lint.yml │ └── unit-test.yml ├── .gitignore ├── .golangci.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── constants.go └── root_cmd.go ├── docs ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── components.json ├── docs │ ├── 00-intro.md │ ├── 01-setup-env.md │ ├── 02-example-repo.md │ ├── 03-scaffold-suite.md │ ├── 04-add-contracts.md │ ├── 05-custom-tests.md │ └── images │ │ └── scaffold-prompt.png ├── docusaurus.config.ts ├── package-lock.json ├── package.json ├── sidebars.ts ├── src │ ├── components │ │ ├── HighlightBox.tsx │ │ ├── HighlightTag.tsx │ │ ├── HomepageFeatures │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ └── ui │ │ │ └── hover-card.tsx │ ├── css │ │ ├── custom.css │ │ └── globals.css │ ├── lib │ │ └── utils.ts │ ├── pages │ │ ├── index.module.css │ │ ├── index.tsx │ │ └── markdown-page.md │ └── theme │ │ └── CodeBlock │ │ └── index.tsx ├── static │ ├── .nojekyll │ └── img │ │ ├── cosmwasm-logo.svg │ │ ├── cw-ica-controller.svg │ │ ├── easy_deploy.svg │ │ ├── focus.svg │ │ ├── ibc-logo-black.svg │ │ ├── icons │ │ ├── hi-coffee-icon.svg │ │ ├── hi-info-icon.svg │ │ ├── hi-note-icon.svg │ │ ├── hi-prerequisite-icon.svg │ │ ├── hi-reading-icon.svg │ │ ├── hi-star-icon.svg │ │ ├── hi-target-icon.svg │ │ ├── hi-tip-icon.svg │ │ └── hi-warn-icon.svg │ │ ├── logo.svg │ │ ├── universal_support.svg │ │ └── white-ibc-logo.svg ├── tailwind.config.js └── tsconfig.json ├── go-codegen.svg ├── go.mod ├── go.sum ├── integration_test ├── go.mod ├── go.sum ├── integration_test.go ├── testdata │ ├── account-nft.json │ ├── axone-objectarium.json │ ├── cw-ica-controller.json │ ├── cw2981-royalties.json │ ├── cw3-fixed-multisig.json │ ├── cw4-group.json │ ├── cw721-base.json │ ├── cyberpunk.json │ ├── dao-dao-core.json │ ├── hackatom.json │ ├── ics721.json │ └── map-test.json └── tools.go ├── main.go ├── pkg ├── codegen │ ├── codegen.go │ ├── codegentests │ │ ├── cwics721_test.go │ │ └── tuple_test.go │ ├── definitions.go │ ├── execute.go │ ├── instantiate.go │ ├── oneof.go │ ├── properties.go │ ├── querier.go │ └── responses.go ├── go │ ├── cmd │ │ └── gocmd.go │ └── module │ │ └── gomodule.go ├── interchaintest │ ├── codegen.go │ ├── files.go │ └── generators.go ├── schemas │ ├── cosmwasm.go │ ├── schema.go │ ├── testdata │ │ └── cw-ica-controller.json │ └── types.go ├── types │ ├── logger.go │ └── version.go └── xgenny │ ├── cp.go │ ├── plush.go │ ├── randstr.go │ └── runner.go └── templates └── interchaintestv8 ├── chainconfig ├── embed.go ├── files │ └── chainconfig │ │ ├── chain_config.go.plush │ │ ├── encoding.go.plush │ │ └── genesis.go.plush ├── options.go └── utils.go ├── e2esuite ├── embed.go ├── files │ └── e2esuite │ │ ├── constants.go.plush │ │ ├── diagnostics.go.plush │ │ ├── grpc_query.go.plush │ │ ├── suite.go.plush │ │ └── utils.go.plush ├── options.go └── utils.go ├── embed.go ├── files ├── .gitignore.plush ├── .golangci.yml.plush ├── README.md.plush ├── basic_test.go.plush ├── contract_test.go.plush ├── go.mod.plush └── go.sum.plush ├── github ├── embed.go ├── files │ └── e2e.yml.plush └── options.go ├── options.go ├── placeholders.go ├── testvalues ├── embed.go ├── files │ └── testvalues │ │ └── values.go.plush └── options.go └── types ├── embed.go ├── files └── types │ ├── contract.go.plush │ └── {{contractName}} │ └── contract.go.plush ├── options.go └── placeholders.go /.github/dependabro.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | paths: 7 | - '**.go' 8 | - '.github/workflows/build.yml' 9 | permissions: 10 | contents: read 11 | jobs: 12 | build: 13 | name: Build 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/setup-go@v4 17 | with: 18 | go-version: "1.21" 19 | - uses: actions/checkout@v4 20 | - name: Build 21 | run: make build 22 | -------------------------------------------------------------------------------- /.github/workflows/check-docs.yml: -------------------------------------------------------------------------------- 1 | # This check-docs workflow was created based on instructions from: 2 | # https://docusaurus.io/docs/deployment 3 | name: Check docs build 4 | # This workflow runs when a PR is labeled with `docs` 5 | # This will check if the docs build successfully by running `npm run build` 6 | on: 7 | pull_request: 8 | branches: 9 | - main 10 | paths: 11 | - 'docs/**' 12 | - '.github/workflows/check-docs.yml' 13 | 14 | jobs: 15 | check-docs-build: 16 | name: Check docs build 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: 18 23 | cache: npm 24 | cache-dependency-path: docs/package-lock.json 25 | 26 | - name: Install dependencies 27 | run: cd docs && npm ci 28 | - name: Test build website 29 | run: cd docs && npm run build 30 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | # This deploy-docs workflow was created based on instructions from: 2 | # https://docusaurus.io/docs/deployment 3 | name: Deploy to GitHub Pages 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | # Review gh actions docs if you want to further define triggers, paths, etc 10 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on 11 | 12 | permissions: 13 | contents: write 14 | 15 | jobs: 16 | deploy: 17 | name: Deploy to GitHub Pages 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | - uses: actions/setup-node@v4 24 | with: 25 | node-version: 18 26 | cache: npm 27 | cache-dependency-path: docs/package-lock.json 28 | 29 | - name: Install dependencies 30 | run: cd docs && npm ci 31 | - name: Build website 32 | run: cd docs && npm run build 33 | 34 | # Popular action to deploy to GitHub Pages: 35 | # Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus 36 | - name: Deploy to GitHub Pages 37 | uses: peaceiris/actions-gh-pages@v3 38 | with: 39 | github_token: ${{ secrets.GITHUB_TOKEN }} 40 | # Build output to publish to the `gh-pages` branch: 41 | publish_dir: ./docs/build 42 | # The following lines assign commit authorship to the official 43 | # GH-Actions bot for deploys to `gh-pages` branch: 44 | # https://github.com/actions/checkout/issues/13#issuecomment-724415212 45 | # The GH actions bot is used by default if you didn't specify the two fields. 46 | # You can swap them out with your own user credentials. 47 | user_name: github-actions[bot] 48 | user_email: 41898282+github-actions[bot]@users.noreply.github.com 49 | -------------------------------------------------------------------------------- /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | name: integration-tests 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | paths: 7 | - '**.go' 8 | - '.github/workflows/integration.yml' 9 | - '**.plush' 10 | permissions: 11 | contents: read 12 | jobs: 13 | test: 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | test: 18 | # List your tests here 19 | - TestWithMySuite/TestMessageComposer 20 | - TestWithMySuite/TestQueryClient 21 | - TestWithMySuite/TestInterchaintestScaffold 22 | name: ${{ matrix.test }} 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/setup-go@v4 26 | with: 27 | go-version: "1.21" 28 | - uses: actions/checkout@v4 29 | - name: golangci-lint 30 | uses: golangci/golangci-lint-action@v3.7.0 31 | with: 32 | version: v1.54 33 | args: --timeout 5m 34 | working-directory: integration_test 35 | - name: Build 36 | run: make build 37 | - name: Run Tests 38 | run: | 39 | cd integration_test 40 | go test -v -mod=readonly . -run=${{ matrix.test }} 41 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | paths: 7 | - '**.go' 8 | - '.github/workflows/lint.yml' 9 | permissions: 10 | contents: read 11 | jobs: 12 | golangci: 13 | name: golangci-lint 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/setup-go@v4 17 | with: 18 | go-version: "1.21" 19 | - uses: actions/checkout@v4 20 | - name: golangci-lint 21 | uses: golangci/golangci-lint-action@v3.7.0 22 | with: 23 | version: v1.54 24 | args: --timeout 5m 25 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: unit-test 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | paths: 7 | - '**.go' 8 | - '.github/workflows/unit-test.yml' 9 | permissions: 10 | contents: read 11 | jobs: 12 | test: 13 | name: Unit Test go-codegen 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/setup-go@v4 17 | with: 18 | go-version: "1.21" 19 | - uses: actions/checkout@v4 20 | - name: Unit Test go-codegen 21 | run: make unit-test 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore build directory 2 | build/ 3 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | tests: true 3 | timeout: 10m 4 | 5 | linters: 6 | disable-all: true 7 | enable: 8 | - exportloopref 9 | - errcheck 10 | - gci 11 | - goconst 12 | - gocritic 13 | - gofumpt 14 | - gosec 15 | - gosimple 16 | - govet 17 | - ineffassign 18 | - misspell 19 | - nakedret 20 | - staticcheck 21 | - thelper 22 | - typecheck 23 | - stylecheck 24 | - revive 25 | - tenv 26 | - unconvert 27 | # Prefer unparam over revive's unused param. It is more thorough in its checking. 28 | - unparam 29 | - unused 30 | - misspell 31 | 32 | issues: 33 | exclude-rules: 34 | - text: 'differs only by capitalization to method' 35 | linters: 36 | - revive 37 | - text: 'Use of weak random number generator' 38 | linters: 39 | - gosec 40 | - linters: 41 | - staticcheck 42 | text: "SA1019:" # silence errors on usage of deprecated funcs 43 | 44 | max-issues-per-linter: 10000 45 | max-same-issues: 10000 46 | 47 | linters-settings: 48 | gci: 49 | sections: 50 | - standard # Standard section: captures all standard packages. 51 | - default # Default section: contains all imports that could not be matched to another section type. 52 | - blank # blank imports 53 | - dot # dot imports 54 | - prefix(github.com/srdtrk/go-codegen) 55 | custom-order: true 56 | revive: 57 | enable-all-rules: true 58 | # Do NOT whine about the following, full explanation found in: 59 | # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#description-of-available-rules 60 | rules: 61 | - name: use-any 62 | disabled: true 63 | - name: if-return 64 | disabled: true 65 | - name: max-public-structs 66 | disabled: true 67 | - name: cognitive-complexity 68 | disabled: true 69 | - name: argument-limit 70 | disabled: true 71 | - name: cyclomatic 72 | disabled: true 73 | - name: file-header 74 | disabled: true 75 | - name: function-length 76 | disabled: true 77 | - name: function-result-limit 78 | disabled: true 79 | - name: line-length-limit 80 | disabled: true 81 | - name: flag-parameter 82 | disabled: true 83 | - name: add-constant 84 | disabled: true 85 | - name: empty-lines 86 | disabled: true 87 | - name: banned-characters 88 | disabled: true 89 | - name: deep-exit 90 | disabled: true 91 | - name: confusing-results 92 | disabled: true 93 | - name: unused-parameter 94 | disabled: true 95 | - name: modifies-value-receiver 96 | disabled: true 97 | - name: early-return 98 | disabled: true 99 | - name: confusing-naming 100 | disabled: true 101 | - name: defer 102 | disabled: true 103 | # Disabled in favour of unparam. 104 | - name: unused-parameter 105 | disabled: true 106 | - name: unhandled-error 107 | disabled: false 108 | arguments: 109 | - 'fmt.Printf' 110 | - 'fmt.Print' 111 | - 'fmt.Println' 112 | - 'myFunction' 113 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ## v0.2.5 (2024-06-22) 6 | 7 | - Removed `GetModuleAddress` helper function from the generated test suite [#88](https://github.com/srdtrk/go-codegen/issues/88) 8 | - Fixed diagnostics upload in the generated test suite [#91](https://github.com/srdtrk/go-codegen/issues/91) 9 | - Depracated `--contract-name` flag to `--package-name` and `-p` in `interchaintest add-contract` command [#81](https://github.com/srdtrk/go-codegen/issues/81) 10 | - Fixed the generation of oneOf when a title is set [#92](https://github.com/srdtrk/go-codegen/pull/92) 11 | 12 | ## v0.2.4 (2024-06-07) 13 | 14 | - Fixed the geenration of some incorrectly formated query responses [#79](https://github.com/srdtrk/go-codegen/pull/79) 15 | - Allow `MigrateMsg` to be an enum [#79](https://github.com/srdtrk/go-codegen/pull/79) 16 | - Bumped go and golangci-lint versions in the autogenerated github actions. [#65](https://github.com/srdtrk/go-codegen/issues/65) and [#68](https://github.com/srdtrk/go-codegen/issues/68) 17 | - Fixed unnecessary override of `contract_test.go`. [#66](https://github.com/srdtrk/go-codegen/issues/66) 18 | - Fixed the use of incorrect package name in `contract_test.go`. [#82](https://github.com/srdtrk/go-codegen/issues/82) 19 | 20 | ## v0.2.3 (2024-06-05) 21 | 22 | - Removed custom gas from StoreCode in auto-generated test suite [#67](https://github.com/srdtrk/go-codegen/issues/67) 23 | - Allow migrate messages to be enums. [#74](https://github.com/srdtrk/go-codegen/pull/74) 24 | - Fixed a problem with generating messages in `dao-dao-core`. [#75](https://github.com/srdtrk/go-codegen/issues/75) 25 | 26 | ## v0.2.2 (2024-05-20) 27 | 28 | - Fixed issue [#61](https://github.com/srdtrk/go-codegen/issues/61) 29 | - Added the testvalues package to the generated test suite [#60](https://github.com/srdtrk/go-codegen/issues/60) 30 | 31 | ## v0.2.1 (2024-05-19) 32 | 33 | - Fixed issue [#56](https://github.com/srdtrk/go-codegen/issues/56) 34 | - Updated the `interchaintest` library to the latest version (v8.3.0) [#53](https://github.com/srdtrk/go-codegen/issues/53) 35 | 36 | ## v0.2.0 (2024-05-17) 37 | 38 | - Added support for generating a gRPC query client [#14](https://github.com/srdtrk/go-codegen/issues/14) 39 | - Added support for generating an [`interchaintest`](https://github.com/strangelove-ventures/interchaintest) based test suite [#45](https://github.com/srdtrk/go-codegen/issues/45). 40 | - Various API breaking changes on the CLI commands and go packages. 41 | 42 | ## v0.1.3 (2024-04-22) 43 | 44 | - Fixed issue [#30](https://github.com/srdtrk/go-codegen/issues/30) 45 | 46 | ## v0.1.2 (2024-04-19) 47 | 48 | - Fixed issue [#26](https://github.com/srdtrk/go-codegen/issues/26) 49 | 50 | ## v0.1.1 (2024-02-14) 51 | 52 | - Added LICENSE file 53 | - Added a `version` command to the CLI 54 | 55 | ## v0.1.0 (2024-02-14) 56 | 57 | Initial release 58 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for building go-codegen 2 | 3 | # Target directory for the build output 4 | BUILD_DIR := build 5 | 6 | .PHONY: build 7 | 8 | build: 9 | @echo "Building go-codegen..." 10 | @go build -o $(BUILD_DIR)/go-codegen main.go 11 | @echo "Build complete!" 12 | 13 | .PHONY: unit-test 14 | 15 | unit-test: 16 | @echo "Running unit tests..." 17 | @go test -v -mod=readonly ./... 18 | @echo "Unit tests complete!" 19 | 20 | .PHONY: integration-tests 21 | 22 | integration-tests: 23 | @echo "Running integration tests..." 24 | @cd integration_test && go test -v -mod=readonly -run=TestWithMySuite/TestMessageComposer 25 | @cd integration_test && go test -v -mod=readonly -run=TestWithMySuite/TestInterchaintestScaffold 26 | @echo "Integration tests complete!" 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-codegen 2 | 3 | Generate Go code for your CosmWasm smart contracts. 4 | 5 |

6 | 7 |

8 | 9 |
10 | 11 | [![E2E Status](https://github.com/srdtrk/go-codegen/actions/workflows/integration.yml/badge.svg?branch=main)](https://github.com/srdtrk/go-codegen/tree/main/integration_test) 12 | [![Tag](https://img.shields.io/github/tag/srdtrk/go-codegen.svg)](https://github.com/srdtrk/go-codegen/releases/latest) 13 | [![License: Apache-2.0](https://img.shields.io/github/license/srdtrk/go-codegen.svg)](https://github.com/srdtrk/go-codegen/blob/main/LICENSE) 14 | [![Lines of Code](https://tokei.rs/b1/github/srdtrk/go-codegen)](https://github.com/srdtrk/go-codegen) 15 |
16 | 17 | ``` 18 | go install github.com/srdtrk/go-codegen@latest 19 | ``` 20 | 21 | The quickest way to generate Go code for your CosmWasm smart contracts. Currently, this tool supports generating: 22 | 23 | - Message types 24 | - A gRPC query client 25 | - An [`interchaintest`](https://github.com/strangelove-ventures/interchaintest) based test suite 26 | 27 | **You can find a tutorial on how to use this tool's interchaintest feature [here](https://srdtrk.github.io/go-codegen/).** 28 | 29 | ## Usage 30 | 31 | In order to generate Go code for your CosmWasm smart contract, you need to have its full API exported as a JSON file 32 | using [`cosmwasm-schema`](https://crates.io/crates/cosmwasm-schema). 33 | Once you have the JSON file, you can use it to generate the Go code. 34 | 35 | Note that you can learn more about how to use this tool by running `go-codegen help` or `-h` flag with any command. 36 | 37 | ### Generate messages 38 | 39 | ```sh 40 | go-codegen generate messages /path/to/contract-api.json --output /path/to/output.go --package-name mypackage 41 | ``` 42 | 43 | This will generate the Go code in the specified optional output directory, if not specified, it will generate the code in `msgs.go` in the current directory. 44 | Package name is also optional, if not specified, it will use the name of the contract. 45 | 46 | ### Generate gRPC query client 47 | 48 | ```sh 49 | go-codegen generate query-client /path/to/contract-api.json -o /path/to/output.go -p mypackage 50 | ``` 51 | 52 | This will generate the Go code in the specified optional output directory, if not specified, it will generate the code in `query.go` in the current directory. Package name is also optional, if not specified, it will use the name of the contract. The generated code depends on the generated messages, the [wasmd package](https://pkg.go.dev/github.com/CosmWasm/wasmd), and the [grpc package](https://pkg.go.dev/google.golang.org/grpc). You can install them by running `go get github.com/CosmWasm/wasmd@latest` (or `go get github.com/CosmWasm/wasmd@v0.50.0` for a specific version) and `go get google.golang.org/grpc@latest` (or `go get google.golang.org/grpc@v1.63.3` for a specific version). 53 | 54 | ### Generate interchaintest test suite 55 | 56 | ```sh 57 | go-codegen interchaintest scaffold 58 | ``` 59 | 60 | This will launch an interactive prompt to guide you through the process of generating the test suite. 61 | The scaffolded test suite will include a basic test and instructions on how to run it. This test suite will not contain any contract specific code, or tests, you will need to add them using `add-contract` command. 62 | 63 | ### Adding a contract to the interchaintest test suite 64 | 65 | ```sh 66 | go-codegen interchaintest add-contract /path/to/contract-api.json --suite-dir /path/to/suite 67 | ``` 68 | 69 | This will add a contract to the test suite. The suite directory is the directory where the test suite is located. If not specified, it will use the current directory. The contract API JSON file is the same file that was used to generate the messages and query client. 70 | 71 | Note that you can find a tutorial on how to use this tool's interchaintest feature [here](https://srdtrk.github.io/go-codegen/). 72 | 73 | ## Acknowledgements 74 | 75 | The Go Gopher mascot was created by [Renee French](https://reneefrench.blogspot.com/) and is licensed under the [Creative Commons 4.0 Attribution License](https://creativecommons.org/licenses/by/4.0/). 76 | I've modified the original image to include the logo of [`ts-codegen`](https://github.com/CosmWasm/ts-codegen). 77 | -------------------------------------------------------------------------------- /cmd/constants.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | const ( 4 | OutputFlag = "output" 5 | PackageNameFlag = "package-name" 6 | 7 | YesFlag = "yes" 8 | SuiteDirFlag = "suite-dir" 9 | ContractNameFlag = "contract-name" 10 | MessagesOnlyFlag = "messages-only" 11 | 12 | DebugFlag = "debug" 13 | ) 14 | -------------------------------------------------------------------------------- /docs/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:react/recommended" 10 | ], 11 | "overrides": [ 12 | { 13 | "env": { 14 | "node": true 15 | }, 16 | "files": [ 17 | ".eslintrc.{js,cjs}" 18 | ], 19 | "parserOptions": { 20 | "sourceType": "script" 21 | } 22 | } 23 | ], 24 | "parser": "@typescript-eslint/parser", 25 | "parserOptions": { 26 | "ecmaVersion": "latest", 27 | "sourceType": "module" 28 | }, 29 | "plugins": [ 30 | "@typescript-eslint", 31 | "react" 32 | ], 33 | "rules": { 34 | "react/prop-types": "off" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ npm i 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ npm start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ npm run build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/css/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/docs/00-intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | sidebar_label: Introduction 4 | sidebar_position: 0 5 | slug: / 6 | --- 7 | 8 | import HighlightTag from '@site/src/components/HighlightTag'; 9 | import HighlightBox from '@site/src/components/HighlightBox'; 10 | 11 | # `go-codegen` Interchaintest Tutorial 12 | 13 | 14 | 15 | This is a tutorial for writing end-to-end test suite for IBC enabled CosmWasm applications using go-codegen. 16 | Powered by [interchaintest](https://github.com/strangelove-ventures/interchaintest). 17 | 18 | 19 | 20 | - Basic knowledge of [golang](https://go.dev/) 21 | - Basic knowledge of [CosmWasm](https://cosmwasm.com/) 22 | - Basic knowledge of [github actions](https://github.com/features/actions) 23 | 24 | 25 | 26 | ## Context 27 | 28 | ### What is go-codegen? 29 | 30 | go-codegen is a tool that generates go code for CosmWasm applications. It can currently generate the following: 31 | 32 | - Message definitions for a contract. 33 | - gRPC query client for a contract. 34 | - An end-to-end test suite for a contract based on interchaintest. 35 | 36 | 37 | 38 | `interchaintest` is a framework for testing blockchain functionality and interoperability between chains, primarily with the Inter-Blockchain Communication (IBC) protocol. 39 | 40 | It can quickly spin up custom testnets and dev environments to test IBC, Relayer setup, chain infrastructure, smart contracts, etc. Interchaintest orchestrates Go tests that utilize Docker containers for multiple IBC-compatible blockchains. 41 | 42 | In order to ship production-grade software for the Interchain, we needed sophisticated developer tooling...but IBC and Web3 have a lot of moving parts, which can lead to a steep learning curve and all sorts of pain. 43 | 44 | - repeatedly building repo-specific, Docker- and shell-based testing solutions, 45 | - duplication of effort, and 46 | - difficulty in repurposing existing testing harnesses for new problem domains. 47 | 48 | Read more about interchaintest in its [README.md](https://github.com/strangelove-ventures/interchaintest/blob/main/README.md). 49 | 50 | 51 | 52 | ### Why use this over other scripting solutions? 53 | 54 | - **Go as a Scripting Language** 55 | - **Local Testing Environment:** The testing framework operates entirely locally, mirroring a production environment closely. This ensures that tests are reliable and can be seamlessly integrated into continuous integration (CI) pipelines. 56 | - **Industry Adoption:** Interchaintest, the underlying framework, is widely adopted by leading projects and core teams in the CosmWasm ecosystem, including Strangelove, the IBC team, Noble, DAODAO, and others. 57 | - **Funding and Support:** The development of Interchaintest is supported by the Interchain Foundation (ICF), ensuring ongoing improvements and stability. 58 | 59 | By using go-codegen, your CosmWasm application can benefit from a well-supported, reliable, widely-adopted testing framework. 60 | 61 | ## Scope 62 | 63 | This tutorial will cover spinning up a local testing environment with interchaintest. We will be testing [cw-ica-controller](https://github.com/srdtrk/cw-ica-controller) to control a native interchain account in the host chain. 64 | 65 | 66 | 67 | In this tutorial, you will: 68 | 69 | - Compile a CosmWasm contract into wasm and generate its JSON schema 70 | - Scaffold an end-to-end testsuite with github actions consisting of two chains using go-codegen. 71 | - Add our contract to the testsuite using go-codegen. 72 | - Create a transfer channel between the two chains. 73 | - Create an IBC channel between the contract and the counterparty go module. 74 | - Send an ICA transaction and verify it. 75 | 76 | 77 | -------------------------------------------------------------------------------- /docs/docs/01-setup-env.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Set Up Your Work Environment 3 | sidebar_label: Set Up Your Work Environment 4 | sidebar_position: 1 5 | slug: /setup-env 6 | --- 7 | 8 | import HighlightBox from '@site/src/components/HighlightBox'; 9 | 10 | # Set Up Your Work Environment 11 | 12 | On this page, you can find helpful links to set up your work environment. 13 | 14 | 15 | 16 | In this section, you can find all you need to install: 17 | 18 | - [Git](https://git-scm.com/) 19 | - [Go v1.22.3](https://go.dev/) 20 | - [go-codegen v0.2.2](https://github.com/srdtrk/go-codegen) 21 | - [Docker](https://www.docker.com/) 22 | - [Rust and Cargo](https://www.rust-lang.org/) 23 | - [just](https://just.systems/) (optional) 24 | 25 | 26 | 27 | 28 | 29 | On a general note, it is advisable to prepare a separate project folder to keep all your Cosmos exercises. 30 | 31 | 32 | 33 | ## Git 34 | 35 | Install Git following the instructions on the [Git website](https://git-scm.com/). Test if Git is installed by running the following command: 36 | 37 | ```bash 38 | git --version 39 | ``` 40 | 41 | ## Go 42 | 43 | Install the latest version of Go following the instructions on the [Go website](https://go.dev/). Test if Go is installed by running the following command: 44 | 45 | ```bash 46 | go version 47 | ``` 48 | 49 | ## go-codegen 50 | 51 | Install go-codegen by following the instructions on the [README.md](https://github.com/srdtrk/go-codegen/blob/main/README.md) or by using the command below. 52 | 53 | ```bash 54 | go install github.com/srdtrk/go-codegen@v0.2.2 55 | ``` 56 | 57 | Test if go-codegen is installed by running the following command: 58 | 59 | ```bash 60 | go-codegen version 61 | ``` 62 | 63 | ## Docker 64 | 65 | Install Docker following the instructions on the [Docker documentation](https://docs.docker.com/get-docker/). Test if Docker is installed by running the following command: 66 | 67 | ```bash 68 | docker --version 69 | ``` 70 | 71 | ## Rust and Cargo 72 | 73 | Install Rust and Cargo following the instructions on the [Rust book](https://doc.rust-lang.org/book/ch01-01-installation.html). Test if Rust is installed by running the following commands: 74 | 75 | ```bash 76 | cargo --version 77 | ``` 78 | 79 | ## just 80 | 81 | 82 | 83 | [Just](https://just.systems/) is a handy `make` alternative written in Rust. It is optional but recommended for this tutorial. Instead of writing a `Makefile`, you can write a `justfile` to automate your tasks. 84 | 85 | 86 | 87 | Install just following the instructions on the [just manual](https://just.systems/man/en/chapter_4.html) or by using the command below. 88 | 89 | ```bash 90 | cargo install just 91 | ``` 92 | 93 | Test if just is installed by running the following command: 94 | 95 | ```bash 96 | just --version 97 | ``` 98 | 99 | 100 | 101 | If you don't want to install `just`, you can simply run the commands in the `justfile` manually. 102 | 103 | 104 | -------------------------------------------------------------------------------- /docs/docs/02-example-repo.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Setup the Example Contract Repository 3 | sidebar_label: Setup the Example Contract Repository 4 | sidebar_position: 2 5 | slug: /example-repo 6 | --- 7 | 8 | import HighlightBox from '@site/src/components/HighlightBox'; 9 | 10 | # Clone the Example Contract Repository 11 | 12 | In this tutorial, we will be creating a test suite for an example CosmWasm contract using go-codegen. We first need to clone the repository containing the example contract. 13 | 14 | You may also fork the repository to your GitHub account and clone it from there. This will allow you to push changes to your forked repository and test github actions in your forked repository. 15 | 16 | ```sh 17 | git clone https://github.com/srdtrk/awesomwasm-2024-workshop 18 | cd awesomwasm-2024-workshop 19 | ``` 20 | 21 | Now, create and checkout a new branch for the tutorial: 22 | 23 | ```sh 24 | git checkout -b tutorial 25 | ``` 26 | 27 | You may take a look at the repository to familiarize yourself, but we will not be making any changes to the contract code in this tutorial. 28 | 29 | 30 | 31 | This contract contains one main contract and two testing contracts in the `testing` directory. We will be using all three contracts in our test suite. 32 | 33 | 34 | 35 | ### Build the Contracts 36 | 37 | To build the contracts, we use the [`cosmwasm/optimizer`](https://github.com/CosmWasm/optimizer) docker image version `0.15.1`. 38 | 39 | ```sh 40 | just build-optimize 41 | ``` 42 | 43 | ```sh 44 | just build-test-contracts 45 | ``` 46 | 47 | ### Generate CosmWasm Schemas 48 | 49 | To use go-codegen, we need to generate the CosmWasm schema for the contracts. We can do this by running the following command: 50 | 51 | ```sh 52 | just generate-schemas 53 | ``` 54 | 55 | 56 | 57 | At this point, you may explore go-codegen by running generate commands. For example: 58 | 59 | ```sh 60 | go-codegen generate messages schema/cw-ica-controller.json 61 | ``` 62 | 63 | And you can explore the generated `msgs.go` file in the root directory. Then remove it: 64 | 65 | ```sh 66 | rm msgs.go 67 | ``` 68 | 69 | 70 | 71 | 72 | 73 | Build and schema artifacts are not committed to the repository due to `.gitignore` settings. 74 | 75 | 76 | -------------------------------------------------------------------------------- /docs/docs/03-scaffold-suite.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Scaffold a Test Suite 3 | sidebar_label: Scaffold a Test Suite 4 | sidebar_position: 3 5 | slug: /scaffold-suite 6 | --- 7 | 8 | import HighlightBox from '@site/src/components/HighlightBox'; 9 | import CodeBlock from '@theme/CodeBlock'; 10 | 11 | # Scaffold a Test Suite with go-codegen 12 | 13 | In go-codegen, a test suite is a go project that contains test cases for an application. The test suite is independent of the application and contracts can be added to the test suite independently. This allows for testing multiple contracts in a single test suite. 14 | 15 | To scaffold the test suite, run the following command: 16 | 17 | 18 | {`go-codegen interchaintest scaffold`} 19 | 20 | 21 | This command will open an interactive prompt that will guide you through the process of creating a test suite. The prompt will ask you to provide the following information: 22 | 23 | ![Scaffold Test Suite](./images/scaffold-prompt.png) 24 | 25 | - **Go Module Name**: The name of the Go module for the test suite. 26 | - **Output Directory**: The directory where the test suite will be created. 27 | - **Number of Chains to Scaffold**: The number of chains to scaffold in the test suite. 28 | - **Github Actions**: Whether to scaffold Github Actions for the test suite. 29 | 30 | For this tutorial, we use the default values with github actions enabled. 31 | 32 | 33 | 34 | The go module name doesn't matter as it is not published to the Go module registry. It is only required to be a valid Go module name. 35 | 36 | 37 | 38 | The generated test suite will contain the following files and directories: 39 | 40 | ```text 41 | e2e/interchaintestv8 42 | ├── README.md 43 | ├── basic_test.go 44 | ├── chainconfig 45 | │   ├── chain_config.go 46 | │   ├── encoding.go 47 | │   └── genesis.go 48 | ├── e2esuite 49 | │   ├── constants.go 50 | │   ├── diagnostics.go 51 | │   ├── grpc_query.go 52 | │   ├── suite.go 53 | │   └── utils.go 54 | ├── go.mod 55 | ├── go.sum 56 | ├── testvalues 57 | │   └── values.go 58 | └── types 59 | └── contract.go 60 | .github 61 | └── workflows 62 | └── e2e.yml 63 | 64 | 7 directories, 15 files 65 | ``` 66 | 67 | The test suite comes with a `basic_test.go` which you can run already without any contracts. You can test this by running: 68 | 69 | ```sh 70 | cd e2e/interchaintestv8 71 | go test -v . -run=TestWithBasicTestSuite/TestBasic 72 | ``` 73 | 74 | The testsuite contains 4 go packages: 75 | 76 | - `chainconfig`: Contains the chain configuration for the test suite. Currently, the only autogenerated chain is `wasmd` v0.50.0 77 | - `e2esuite`: Contains the core components of the testsuite, such as setting up chains, relayer, and important utility functions. 78 | - `testvalues`: Contains the test value constants for the test suite. 79 | - `types`: Contains the contract types for the test suite. We will populate this directory with the contract types for the contracts we want to test. 80 | - `basic_test.go`: Contains a basic test suite that can be run without any contracts. 81 | 82 | 83 | 84 | Take some time to explore the test suite and `basic_test.go`. A good exercise is to bump the version of the `wasmd` chain to `v0.51.0` which uses CosmWasm v2, and see how the test suite behaves. 85 | 86 | To test github actions, you can open a pull request to your forked repository and see the actions run. 87 | 88 | 89 | 90 | 91 | 92 | Although currently the test suite only contains the `wasmd` image, you can change the chain configuration manually. We want to have options to scaffold additional chains in the future. 93 | 94 | For example, the following repositories contain the chain configurations for other chains while still using the same test suite: 95 | 96 | - [ibc-go-simd](https://github.com/srdtrk/cw-ica-controller/blob/d6b033092071e37f2dd015b58810a02257a92b6b/e2e/interchaintestv8/chainconfig/chain_config.go#L34-L55) 97 | - [osmosis](https://github.com/chelofinance/cw-icq-controller/blob/3540483852bc0de8f0166b675d2573c5571b80e1/e2e/interchaintest/chainconfig/chain_config.go#L85-L109) 98 | 99 | 100 | -------------------------------------------------------------------------------- /docs/docs/04-add-contracts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Add Contracts to the Test Suite 3 | sidebar_label: Add Contracts to the Test Suite 4 | sidebar_position: 4 5 | slug: /add-contracts 6 | --- 7 | 8 | import HighlightBox from '@site/src/components/HighlightBox'; 9 | import CodeBlock from '@theme/CodeBlock'; 10 | 11 | # Add Contracts to the Test Suite 12 | 13 | It's time to add the contracts to the test suite. It might be helpful to give a brief overview of the contracts before adding them to the test suite. 14 | 15 | ## Overview of `cw-ica-controller` 16 | 17 | CosmWasm Interchain Account Controller is a pure CosmWasm implementation of the [ICS-27 controller specifications](https://github.com/cosmos/ibc/tree/main/spec/app/ics-027-interchain-accounts). It can therefore directly create and control accounts in the counterparty `icahost` module. 18 | 19 | ![cw-ica-controller](https://raw.githubusercontent.com/srdtrk/cw-ica-controller/main/docs/static/img/cw-ica-controller.svg) 20 | 21 | This contract was designed to be instantiated by other contracts that wish to control accounts in a counterparty chain. Learn more about the contract in its repository: [srdtrk/cw-ica-controller](https://github.com/srdtrk/cw-ica-controller). 22 | 23 | 24 | 25 | There are a couple important things to note about this contract: 26 | 27 | - Since this contract was designed to be instantiated by other contracts, it does have the capability to make callbacks to an external contract on channel and packet lifecycle events. This is what the `callback-counter` test contract is for. 28 | 29 | ```rust reference 30 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/90ab7f61c743ec26c22fe3a1f3b8fac140a19a39/src/types/callbacks.rs#L15-L46 31 | ``` 32 | 33 | - This contract automatically initiates the channel opening handshake when it is instantiated and doesn't allow any relayers to initiate the handshake. 34 | 35 | ```rust reference 36 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/90ab7f61c743ec26c22fe3a1f3b8fac140a19a39/src/types/msg.rs#L8-L21 37 | ``` 38 | 39 | 40 | 41 | ## Add the Contracts 42 | 43 | In this test, we will only add the `cw-ica-controller` and `callback-counter` contracts to the test suite. To add the `cw-ica-controller` contract to the test suite, run the following command from the root of the workshop repository: 44 | 45 | 46 | {`go-codegen interchaintest add-contract schema/cw-ica-controller.json --suite-dir e2e/interchaintestv8`} 47 | 48 | 49 | This command will generate the following files: 50 | 51 | ```text 52 | e2e/interchaintestv8/ 53 | ├── contract_test.go 54 | └── types 55 | └── cwicacontroller 56 | ├── contract.go 57 | ├── msgs.go 58 | └── query.go 59 | ``` 60 | 61 | `contract_test.go` contains an example test case for instantiating the generated contract. This file is only generated if it doesn't already exist in the test suite when the `add-contract` command is run. 62 | 63 | You can also add the `callback-counter` contract to the test suite by running the following command: 64 | 65 | 66 | {`go-codegen interchaintest add-contract testing/contracts/callback-counter/schema/callback-counter.json --suite-dir e2e/interchaintestv8`} 67 | 68 | 69 | This command will generate the following files: 70 | 71 | ```text 72 | e2e/interchaintestv8/types/callbackcounter 73 | ├── contract.go 74 | ├── msgs.go 75 | └── query.go 76 | ``` 77 | 78 | This will not modify the `contract_test.go` and the 79 | 80 | 81 | 82 | Explore the generated files to understand how the contracts are added to the test suite. 83 | 84 | Run the generated test and understand why it fails. 85 | 86 | ```sh 87 | cd e2e/interchaintestv8 88 | go test -v . -run=TestWithContractTestSuite/TestContract 89 | ``` 90 | 91 | 92 | 93 | 94 | 95 | Github Actions will not run the test in the generated `contract_test.go` file. To do this, you need to add the test to the `.github/workflows/e2e.yml` file. Moreover, when running the generated test in Github Actions, you will need to build all the contracts before running the test. 96 | 97 | 98 | {`# ... 99 | build: 100 | strategy: 101 | fail-fast: false 102 | matrix: 103 | test: 104 | # List your tests here 105 | - TestWithBasicTestSuite/TestBasic 106 | # plus-diff-line 107 | + - TestWithContractTestSuite/TestContract 108 | name: \${{ matrix.test }} 109 | runs-on: ubuntu-latest 110 | steps: 111 | - name: Checkout sources 112 | uses: actions/checkout@v3 113 | // minus-diff-start 114 | - # You can build your contract here, you can either use docker or a custom cargo script: 115 | - # We've provided examples for both below: 116 | - # 117 | - # - name: Build Contracts with Docker 118 | - # run: | 119 | - # docker run --rm -v "$(pwd)":/code \ 120 | - # --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 121 | - # --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 122 | - # cosmwasm/optimizer:0.15.1 123 | - # - name: Install cargo-run-script 124 | - # uses: actions-rs/cargo@v1 125 | - # with: 126 | - # command: install 127 | - # args: cargo-run-script 128 | - # - name: Build Optimized Contract 129 | - # uses: actions-rs/cargo@v1 130 | - # with: 131 | - # command: run-script 132 | - # args: optimize 133 | // minus-diff-end 134 | // plus-diff-start 135 | + - name: Install just 136 | + uses: extractions/setup-just@v2 137 | + - name: Build Test Contracts with Docker 138 | + run: just build-test-contracts 139 | + - name: Build Optimized Contract 140 | + run: just build-optimize 141 | // plus-diff-end 142 | - name: Setup Go 143 | uses: actions/setup-go@v4 144 | \# ... 145 | `} 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /docs/docs/05-custom-tests.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Custom Tests 3 | sidebar_label: Custom Tests 4 | sidebar_position: 5 5 | slug: /custom-tests 6 | --- 7 | 8 | import HighlightBox from '@site/src/components/HighlightBox'; 9 | 10 | # Custom Tests 11 | 12 | In this section, I will give you a test for the `cw-ica-controller` contract. This test demonstrates how to create a new interchain account, fund it, and then stake some tokens with it. And then we will go over this test file line by line to understand the logic behind it. 13 | 14 | Replace the contents of `e2e/interchaintestv8/contract_test.go` with the following code: 15 | 16 | ```go reference 17 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go 18 | ``` 19 | 20 | You need to run `go mod tidy` to update the indirect dependencies. 21 | 22 | ```sh 23 | go mod tidy 24 | ``` 25 | 26 | ## Organization 27 | 28 | The tests are organized into subtests using the `s.Run` method. This method takes a test name and a test function as arguments. The test function is executed when the test is run. 29 | 30 | ```go reference 31 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go#L55 32 | ``` 33 | 34 | 35 | 36 | Using the `s.Run` method to organize your tests into subtests makes it easier to understand the purpose of each test and helps you to identify the failing test quickly. 37 | 38 | Notice that each `s.Run` call is also wrapped by a `s.Require().True` call. This is because without this, sequential subtests will be executed even if the previous subtest fails. You may abstain from doing this if you want to run the subtests even if one of them fails. 39 | 40 | 41 | 42 | ## Test Logic 43 | 44 | We will do a line-by-line analysis of the test logic. 45 | 46 | We first call the underlying test suite's setup method. This is where the chains, relayers, connections, and accounts are initialized. 47 | 48 | ```go reference 49 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go#L45 50 | ``` 51 | 52 | ### UploadAndInstantiateContracts Subtest 53 | 54 | Since contract upload and instantiation logic is inside `s.Run`, we define variables for the instantiated contracts outside of the `s.Run` method to use them in the subsequent subtests. 55 | 56 | ```go reference 57 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go#L49-L54 58 | ``` 59 | 60 | 61 | 62 | Although here we are storing the instantiated contracts in variables, it is advisable to store them in the `ContractTestSuite` struct to make them accessible to all test functions. It might also be helpful to move contract upload and instantiation logic to the `ContractTestSuite`'s `SetupTest` method. 63 | 64 | This is what is done in the `cw-ica-controller`'s actual test suite. There are a lot of test functions there, which is also a good reference for writing custom tests. 65 | 66 | ```go reference 67 | https://github.com/srdtrk/cw-ica-controller/blob/d6b033092071e37f2dd015b58810a02257a92b6b/e2e/interchaintestv8/contract_test.go#L34-L46 68 | ``` 69 | 70 | 71 | 72 | Then we open a subtest to upload and instantiate both the `cw-ica-controller` and `callback-counter` contracts. We first upload the `callback-counter` contract: 73 | 74 | ```go reference 75 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go#L57 76 | ``` 77 | 78 | Then we instantiate the `callback-counter` contract: 79 | 80 | ```go reference 81 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go#L60 82 | ``` 83 | 84 | Notice that the `callback-counter` takes no arguments in its instantiation. 85 | 86 | Then we upload and instantiate the `cw-ica-controller` contract: 87 | 88 | ```go reference 89 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go#L64-L80 90 | ``` 91 | 92 | 93 | 94 | Recall that the `cw-ica-controller` initiates the channel opening handshake upon its instantiation. This is why we wait for 5 blocks after the instantiation to ensure that the handshake is completed. 95 | 96 | ```go reference 97 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go#L83 98 | ``` 99 | 100 | 101 | 102 | ### Verify_ChannelOpenHandshake Subtest 103 | 104 | The next subtest is a set of assertions to ensure that the interchain account was created successfully: 105 | 106 | ```go reference 107 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go#L87-L108 108 | ``` 109 | 110 | 111 | 112 | Notice the use of the autogenerated query client for both the `cw-ica-controller` and `callback-counter` contracts. This is the recommended way to query the contracts although you can also use the `.Query` method on the contract instance. 113 | 114 | ```go reference 115 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go#L89 116 | ``` 117 | 118 | 119 | 120 | ### Test_CosmosMsg_Delegate Subtest 121 | 122 | In the next subtest, we fund the interchain account and stake some tokens with it. We fund the interchain account using the `s.FundAddressChainB` method, which can be found in [`e2e/interchaintestv8/e2esuite/utils.go`](https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/e2esuite/utils.go). 123 | 124 | ```go reference 125 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go#L111-L117 126 | ``` 127 | 128 | Next, we construct the execute message to stake tokens with the interchain account: 129 | 130 | ```go reference 131 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go#L118-L139 132 | ``` 133 | 134 | Then we use the `contract.Execute` method to execute the message, and we wait for 5 blocks to ensure that the IBC packet lifecycle is completed: 135 | 136 | ```go reference 137 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go#L141-L146 138 | ``` 139 | 140 | The rest of the subtest is a set of assertions to ensure that the delegation was successful: 141 | 142 | ```go reference 143 | https://github.com/srdtrk/awesomwasm-2024-workshop/blob/bbd0b9de946bc8a0680398e2dccf11e220c8c1d8/e2e/interchaintestv8/contract_test.go#L147-L165 144 | ``` 145 | 146 | Thank you for reading this tutorial. I hope you found it helpful. 147 | -------------------------------------------------------------------------------- /docs/docs/images/scaffold-prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srdtrk/go-codegen/ed67358133db1fbf79e1b307a3b64cc618d50322/docs/docs/images/scaffold-prompt.png -------------------------------------------------------------------------------- /docs/docusaurus.config.ts: -------------------------------------------------------------------------------- 1 | import { themes as prismThemes } from 'prism-react-renderer'; 2 | import type { Config } from '@docusaurus/types'; 3 | import type * as Preset from '@docusaurus/preset-classic'; 4 | 5 | const config: Config = { 6 | title: 'go-codegen', 7 | tagline: 'Generate Go code for your CosmWasm smart contracts.', 8 | favicon: 'img/logo.svg', 9 | 10 | // Set the production url of your site here 11 | url: 'https://srdtrk.github.io', 12 | // Set the // pathname under which your site is served 13 | // For GitHub pages deployment, it is often '//' 14 | baseUrl: '/go-codegen/', 15 | 16 | // GitHub pages deployment config. 17 | // If you aren't using GitHub pages, you don't need these. 18 | organizationName: 'srdtrk', // Usually your GitHub org/user name. 19 | projectName: 'go-codegen', // Usually your repo name. 20 | 21 | onBrokenLinks: 'throw', 22 | onBrokenMarkdownLinks: 'warn', 23 | 24 | // Even if you don't use internationalization, you can use this field to set 25 | // useful metadata like html lang. For example, if your site is Chinese, you 26 | // may want to replace "en" with "zh-Hans". 27 | i18n: { 28 | defaultLocale: 'en', 29 | locales: ['en'], 30 | }, 31 | 32 | presets: [ 33 | [ 34 | 'classic', 35 | { 36 | docs: { 37 | sidebarPath: './sidebars.ts', 38 | // Please change this to your repo. 39 | // Remove this to remove the "edit this page" links. 40 | editUrl: 41 | 'https://github.com/srdtrk/go-codegen/tree/main/docs', 42 | // Routed the docs to the root path 43 | routeBasePath: "/tutorial", 44 | sidebarCollapsed: false, 45 | }, 46 | theme: { 47 | customCss: './src/css/custom.css', 48 | }, 49 | } satisfies Preset.Options, 50 | ], 51 | ], 52 | 53 | themes: [ 54 | 'docusaurus-theme-github-codeblock', 55 | [ 56 | require.resolve("@easyops-cn/docusaurus-search-local"), 57 | /** @type {import("@easyops-cn/docusaurus-search-local").PluginOptions} */ 58 | ({ 59 | // ... Your options. 60 | // `hashed` is recommended as long-term-cache of index file is possible. 61 | hashed: true, 62 | // For Docs using Chinese, The `language` is recommended to set to: 63 | // ``` 64 | // language: ["en", "zh"], 65 | // ``` 66 | }), 67 | ], 68 | ], 69 | 70 | themeConfig: { 71 | // Replace with your project's social card 72 | // image: 'https://opengraph.githubassets.com/946cd03a2431502cb8cdbb579ca48c56e2c4060ca5ff0b25e13739f3fc08b512/srdtrk/cw-ica-controller', 73 | navbar: { 74 | title: 'go-codegen', 75 | logo: { 76 | alt: 'Logo', 77 | src: 'img/logo.svg', 78 | }, 79 | items: [ 80 | { 81 | type: 'docSidebar', 82 | sidebarId: 'docsSidebar', 83 | position: 'left', 84 | label: 'Tutorial', 85 | }, 86 | { 87 | href: 'https://github.com/srdtrk/go-codegen', 88 | label: 'GitHub', 89 | position: 'right', 90 | }, 91 | ], 92 | }, 93 | footer: { 94 | style: 'dark', 95 | links: [ 96 | { 97 | title: 'Tutorial', 98 | items: [ 99 | { 100 | label: 'Tutorial', 101 | to: '/tutorial', 102 | }, 103 | ], 104 | }, 105 | { 106 | title: 'Community', 107 | items: [ 108 | { 109 | label: 'Twitter', 110 | href: 'https://twitter.com/srdtrk', 111 | }, 112 | ], 113 | }, 114 | { 115 | title: 'More', 116 | items: [ 117 | { 118 | label: 'GitHub', 119 | href: 'https://github.com/srdtrk/go-codegen', 120 | }, 121 | ], 122 | }, 123 | ], 124 | copyright: `Copyright © ${new Date().getFullYear()}. Built with Docusaurus.`, 125 | }, 126 | prism: { 127 | theme: prismThemes.github, 128 | darkTheme: prismThemes.dracula, 129 | additionalLanguages: ["protobuf", "go-module", "yaml", "toml", "rust"], 130 | magicComments: [ 131 | // Remember to extend the default highlight class name as well! 132 | { 133 | className: 'theme-code-block-highlighted-line', 134 | line: 'highlight-next-line', 135 | block: { start: 'highlight-start', end: 'highlight-end' }, 136 | }, 137 | { 138 | className: 'code-block-minus-diff-line', 139 | line: 'minus-diff-line', 140 | block: { start: 'minus-diff-start', end: 'minus-diff-end' }, 141 | }, 142 | { 143 | className: 'code-block-plus-diff-line', 144 | line: 'plus-diff-line', 145 | block: { start: 'plus-diff-start', end: 'plus-diff-end' }, 146 | }, 147 | ], 148 | }, 149 | colorMode: { 150 | defaultMode: 'dark', 151 | }, 152 | // github codeblock theme configuration 153 | codeblock: { 154 | showGithubLink: true, 155 | githubLinkLabel: 'View on GitHub', 156 | showRunmeLink: false, 157 | runmeLinkLabel: 'Checkout via Runme' 158 | }, 159 | } satisfies Preset.ThemeConfig, 160 | plugins: [ 161 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 162 | async function myPlugin(context, options) { 163 | return { 164 | name: "docusaurus-tailwindcss", 165 | configurePostCss(postcssOptions) { 166 | // eslint-disable-next-line @typescript-eslint/no-var-requires 167 | postcssOptions.plugins.push(require("postcss-import")); 168 | // eslint-disable-next-line @typescript-eslint/no-var-requires 169 | postcssOptions.plugins.push(require("tailwindcss/nesting")); 170 | // eslint-disable-next-line @typescript-eslint/no-var-requires 171 | postcssOptions.plugins.push(require("tailwindcss")); 172 | // eslint-disable-next-line @typescript-eslint/no-var-requires 173 | postcssOptions.plugins.push(require("autoprefixer")); 174 | return postcssOptions; 175 | }, 176 | }; 177 | }, 178 | ], 179 | }; 180 | 181 | export default config; 182 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "^3.3.2", 19 | "@docusaurus/plugin-client-redirects": "^3.3.2", 20 | "@docusaurus/preset-classic": "^3.3.2", 21 | "@easyops-cn/docusaurus-search-local": "^0.40.1", 22 | "@mdx-js/react": "^3.0.0", 23 | "@radix-ui/react-hover-card": "^1.0.7", 24 | "class-variance-authority": "^0.7.0", 25 | "clsx": "^2.1.0", 26 | "docusaurus-theme-github-codeblock": "^2.0.2", 27 | "lucide-react": "^0.316.0", 28 | "prism-react-renderer": "^2.3.0", 29 | "react": "^18.0.0", 30 | "react-dom": "^18.0.0", 31 | "tailwind-merge": "^2.2.1", 32 | "tailwindcss-animate": "^1.0.7" 33 | }, 34 | "devDependencies": { 35 | "@docusaurus/module-type-aliases": "^3.3.2", 36 | "@docusaurus/tsconfig": "^3.3.2", 37 | "@docusaurus/types": "^3.3.2", 38 | "@typescript-eslint/eslint-plugin": "^6.20.0", 39 | "@typescript-eslint/parser": "^6.20.0", 40 | "eslint": "^8.56.0", 41 | "eslint-config-standard-with-typescript": "^43.0.1", 42 | "eslint-plugin-import": "^2.29.1", 43 | "eslint-plugin-n": "^16.6.2", 44 | "eslint-plugin-promise": "^6.1.1", 45 | "eslint-plugin-react": "^7.33.2", 46 | "tailwindcss": "^3.4.1", 47 | "typescript": "~5.2.2" 48 | }, 49 | "browserslist": { 50 | "production": [ 51 | ">0.5%", 52 | "not dead", 53 | "not op_mini all" 54 | ], 55 | "development": [ 56 | "last 3 chrome version", 57 | "last 3 firefox version", 58 | "last 5 safari version" 59 | ] 60 | }, 61 | "engines": { 62 | "node": ">=18.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docs/sidebars.ts: -------------------------------------------------------------------------------- 1 | import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'; 2 | 3 | /** 4 | * Creating a sidebar enables you to: 5 | - create an ordered group of docs 6 | - render a sidebar for each doc of that group 7 | - provide next/previous navigation 8 | 9 | The sidebars can be generated from the filesystem, or explicitly defined here. 10 | 11 | Create as many sidebars as you want. 12 | */ 13 | const sidebars: SidebarsConfig = { 14 | // By default, Docusaurus generates a sidebar from the docs folder structure 15 | docsSidebar: [{ type: 'autogenerated', dirName: '.' }], 16 | 17 | // But you can create a sidebar manually 18 | /* 19 | tutorialSidebar: [ 20 | 'intro', 21 | 'hello', 22 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['tutorial-basics/create-a-document'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | export default sidebars; 32 | -------------------------------------------------------------------------------- /docs/src/components/HighlightBox.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import ReadingIcon from "@site/static/img/icons/hi-reading-icon.svg"; 4 | import PrereqIcon from "@site/static/img/icons/hi-prerequisite-icon.svg"; 5 | import TargetIcon from "@site/static/img/icons/hi-target-icon.svg"; 6 | import StarIcon from "@site/static/img/icons/hi-star-icon.svg"; 7 | import TipIcon from "@site/static/img/icons/hi-tip-icon.svg"; 8 | import NoteIcon from "@site/static/img/icons/hi-note-icon.svg"; 9 | import CoffeeIcon from "@site/static/img/icons/hi-coffee-icon.svg"; 10 | import InfoIcon from "@site/static/img/icons/hi-info-icon.svg"; 11 | import WarnIcon from "@site/static/img/icons/hi-warn-icon.svg"; 12 | 13 | const typeToStyles = { 14 | tip: { 15 | color1: "#336667", 16 | color2: "#00B067", 17 | icon: , 18 | darkMode: true, 19 | }, 20 | reading: { 21 | color1: "#F46800", 22 | color2: "#F24CF4", 23 | icon: , 24 | darkMode: false, 25 | }, 26 | info: { 27 | color1: "#336667", 28 | color2: "#00B067", 29 | icon: , 30 | darkMode: true, 31 | }, 32 | warn: { 33 | color1: "#00B067", 34 | color2: "#FFD303", 35 | icon: , 36 | darkMode: false, 37 | }, 38 | warning: { 39 | color1: "#00B067", 40 | color2: "#FFD303", 41 | icon: , 42 | darkMode: false, 43 | }, 44 | synopsis: { 45 | color1: "#1c1c1c", 46 | color2: "#1c1c1c", 47 | icon: null, 48 | darkMode: true, 49 | }, 50 | prerequisite: { 51 | color1: "lightgray", 52 | color2: "lightgray", 53 | icon: , 54 | darkMode: false, 55 | }, 56 | learning: { 57 | color1: "#6836D0", 58 | color2: "#05BDFC", 59 | icon: , 60 | darkMode: true, 61 | }, 62 | "best-practice": { 63 | color1: "#6836D0", 64 | color2: "#6836D0", 65 | icon: , 66 | darkMode: true, 67 | }, 68 | remember: { 69 | color1: "#6D0000", 70 | color2: "#F66800", 71 | icon: , 72 | darkMode: true, 73 | }, 74 | note: { 75 | color1: "#F69900", 76 | color2: "#FFCE15", 77 | icon: , 78 | darkMode: false, 79 | }, 80 | docs: { 81 | color1: "#6836D0", 82 | color2: "#F44CF6", 83 | icon: , 84 | darkMode: true, 85 | }, 86 | // add as many types as you like 87 | }; 88 | 89 | const gradientStyles = ({ color1, color2 }) => ({ 90 | background: `linear-gradient(78.06deg, ${color1} 1.14%, ${color2} 98.88%)`, 91 | border: 0, 92 | borderRadius: 16, 93 | padding: "0 30px", 94 | display: "flex", 95 | width: "100%", 96 | // alignItems: "center", 97 | justifyContent: "start", 98 | marginBottom: 20, 99 | fontSize: 18, 100 | // flexWrap: "wrap", 101 | flexDirection: "column", 102 | }); 103 | 104 | function HighlightBox({ type, title, children }) { 105 | const styles = typeToStyles[type] || typeToStyles.info; // default to 'info' if type doesn't exist 106 | 107 | const codeStyle = { 108 | backgroundColor: 'var(--docusaurus-highlighted-code-line-bg)', 109 | }; 110 | const iconStyles = { 111 | alignSelf: "center", 112 | filter: styles.darkMode ? "brightness(0) invert(1)" : "brightness(0)", 113 | }; 114 | const titleStyle = { 115 | marginTop: "10px", 116 | marginLeft: "10px", 117 | color: styles.darkMode ? "#e6e6e6" : "black", 118 | }; 119 | const childrenStyle = { 120 | color: styles.darkMode ? "#e6e6e6" : "black", 121 | marginBottom: "10px", 122 | }; 123 | 124 | const childrenWithStyles = React.Children.map(children, child => { 125 | if (child.type === 'code') { 126 | return React.cloneElement(child, { 127 | style: codeStyle, 128 | }); 129 | } 130 | return child; 131 | }); 132 | 133 | return ( 134 | // TODO: fix linter error here 135 |
136 | 143 |
144 |
{styles.icon}
145 |

{title}

146 |
147 |
{childrenWithStyles}
148 |
149 | ); 150 | } 151 | 152 | export default HighlightBox; 153 | -------------------------------------------------------------------------------- /docs/src/components/HighlightTag.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | HoverCard, 4 | HoverCardContent, 5 | HoverCardTrigger, 6 | } from "./ui/hover-card" 7 | 8 | const tags = { 9 | concepts: { 10 | color: "#54ffe0", 11 | label: "Concepts", 12 | isBright: true, 13 | description: "Learn about the concepts behind 'go-codegen'", 14 | }, 15 | basics: { 16 | color: "#F69900", 17 | label: "Basics", 18 | isBright: true, 19 | description: "Learn the basics of 'go-codegen'", 20 | }, 21 | "ibc-go": { 22 | color: "#ff1717", 23 | label: "IBC-Go", 24 | description: "This section includes IBC-Go specific content", 25 | }, 26 | cosmjs: { 27 | color: "#6836D0", 28 | label: "CosmJS", 29 | description: "This section includes CosmJS specific content", 30 | }, 31 | cosmwasm: { 32 | color: "#05BDFC", 33 | label: "CosmWasm", 34 | description: "This section includes CosmWasm specific content", 35 | }, 36 | protocol: { 37 | color: "#00B067", 38 | label: "Protocol", 39 | description: "This section includes content about protocol specifcations", 40 | }, 41 | advanced: { 42 | color: "#f7f199", 43 | label: "Advanced", 44 | isBright: true, 45 | description: "The content in this section is for advanced users researching", 46 | }, 47 | developer: { 48 | color: "#AABAFF", 49 | label: "Developer", 50 | isBright: true, 51 | description: "This section includes content for external developers using the 'go-codegen'", 52 | }, 53 | tutorial: { 54 | color: "#F46800", 55 | label: "Tutorial", 56 | description: "This section includes a tutorial", 57 | }, 58 | "guided-coding": { 59 | color: "#F24CF4", 60 | label: "Guided Coding", 61 | description: "This section includes guided coding", 62 | }, 63 | }; 64 | 65 | const HighlightTag = ({ type, version }) => { 66 | const styles = tags[type] || tags["ibc-go"]; // default to 'ibc-go' if type doesn't exist 67 | const description = styles.description || ""; 68 | 69 | return ( 70 | 71 | 72 | 83 | {styles.label} 84 | {version ? ` ${version}` : ""} 85 | 86 | 87 | 88 | {description} 89 | 90 | 91 | ); 92 | }; 93 | 94 | export default HighlightTag; 95 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import clsx from 'clsx'; 4 | import Heading from '@theme/Heading'; 5 | import styles from './styles.module.css'; 6 | 7 | import EasyDeploySvg from "@site/static/img/easy_deploy.svg"; 8 | import UniversalSupportSvg from "@site/static/img/universal_support.svg"; 9 | import FocusSvg from "@site/static/img/focus.svg"; 10 | 11 | type FeatureItem = { 12 | title: string; 13 | Svg: React.ComponentType>; 14 | description: JSX.Element; 15 | }; 16 | 17 | const FeatureList: FeatureItem[] = [ 18 | { 19 | title: 'Generate Message Types', 20 | Svg: EasyDeploySvg, 21 | description: ( 22 | <> 23 | go-codegen generates message types in golang for you CosmWasm application. It is as easy as 24 | running a command. 25 | 26 | ), 27 | }, 28 | { 29 | title: 'Generate gRPC Clients', 30 | Svg: UniversalSupportSvg, 31 | description: ( 32 | <> 33 | go-codegen generates gRPC clients in golang for you CosmWasm application. Currently, only the 34 | gRPC query client is supported. A transaction client is on the roadmap. 35 | 36 | ), 37 | }, 38 | { 39 | title: 'A Streamlined IBC Testing Suite', 40 | Svg: FocusSvg, 41 | description: ( 42 | <> 43 | go-codegen generates an entire testing suite for your CosmWasm application. This end-to-end 44 | testing suite is designed to help you test your application in a local and realistic environment. 45 | Powered by interchaintest. 46 | 47 | ), 48 | }, 49 | ]; 50 | 51 | function Feature({ title, Svg, description }: FeatureItem) { 52 | return ( 53 |
54 |
55 | 56 |
57 |
58 | {title} 59 |

{description}

60 |
61 |
62 | ); 63 | } 64 | 65 | export default function HomepageFeatures(): JSX.Element { 66 | return ( 67 |
68 |
69 |
70 | {FeatureList.map((props, idx) => ( 71 | 72 | ))} 73 |
74 |
75 |
76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | margin: auto; 12 | } 13 | -------------------------------------------------------------------------------- /docs/src/components/ui/hover-card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card" 3 | 4 | import { cn } from "../../lib/utils" 5 | 6 | const HoverCard = HoverCardPrimitive.Root 7 | 8 | const HoverCardTrigger = HoverCardPrimitive.Trigger 9 | 10 | const HoverCardContent = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 14 | 24 | )) 25 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName 26 | 27 | export { HoverCard, HoverCardTrigger, HoverCardContent } 28 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | @import "./globals.css"; 8 | 9 | /* You can override the default Infima variables here. */ 10 | :root { 11 | --ifm-color-primary: #4193c4; 12 | --ifm-color-primary-dark: #29784c; 13 | --ifm-color-primary-darker: #277148; 14 | --ifm-color-primary-darkest: #205d3b; 15 | --ifm-color-primary-light: #33925d; 16 | --ifm-color-primary-lighter: #359962; 17 | --ifm-color-primary-lightest: #3cad6e; 18 | --ifm-code-font-size: 95%; 19 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 20 | } 21 | 22 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 23 | [data-theme='dark'] { 24 | background-color: #080808; 25 | /* Light grey color */ 26 | --ifm-color-primary: #4193c4; 27 | --ifm-color-primary-dark: #21af90; 28 | --ifm-color-primary-darker: #1fa588; 29 | --ifm-color-primary-darkest: #1a8870; 30 | --ifm-color-primary-light: #29d5b0; 31 | --ifm-color-primary-lighter: #32d8b4; 32 | --ifm-color-primary-lightest: #4fddbf; 33 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 34 | 35 | .navbar { 36 | background-color: #121212; 37 | border-bottom: 1px solid #303030; 38 | } 39 | 40 | /* CODE BLOCK */ 41 | .code-block-minus-diff-line { 42 | background-color: #ff000020; 43 | display: block; 44 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 45 | padding: 0 var(--ifm-pre-padding); 46 | border-left: 3px solid #ff000080; 47 | } 48 | 49 | .code-block-plus-diff-line { 50 | background-color: #00ff0020; 51 | display: block; 52 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 53 | padding: 0 var(--ifm-pre-padding); 54 | border-left: 3px solid #00ff0080; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /docs/src/css/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | /* --background: 0 0% 100%; */ 8 | /* --foreground: 222.2 84% 4.9%; */ 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | html[data-theme="dark"] { 39 | /* --background: 222.2 84% 4.9%; */ 40 | /* --foreground: 210 40% 98%; */ 41 | 42 | --card: 222.2 84% 4.9%; 43 | --card-foreground: 210 40% 98%; 44 | 45 | --popover: 222.2 84% 4.9%; 46 | --popover-foreground: 210 40% 98%; 47 | 48 | --primary: 210 40% 98%; 49 | --primary-foreground: 222.2 47.4% 11.2%; 50 | 51 | --secondary: 217.2 32.6% 17.5%; 52 | --secondary-foreground: 210 40% 98%; 53 | 54 | --muted: 217.2 32.6% 17.5%; 55 | --muted-foreground: 215 20.2% 65.1%; 56 | 57 | --accent: 217.2 32.6% 17.5%; 58 | --accent-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --border: 217.2 32.6% 17.5%; 64 | --input: 217.2 32.6% 17.5%; 65 | --ring: 212.7 26.8% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /docs/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | margin-right: 0.5rem; 24 | margin-left: 0.5rem; 25 | } 26 | -------------------------------------------------------------------------------- /docs/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import clsx from 'clsx'; 4 | import Link from '@docusaurus/Link'; 5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 6 | import Layout from '@theme/Layout'; 7 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 8 | import Heading from '@theme/Heading'; 9 | 10 | import styles from './index.module.css'; 11 | 12 | function HomepageHeader() { 13 | const { siteConfig } = useDocusaurusContext(); 14 | return ( 15 |
16 |
17 | 18 | {siteConfig.title} 19 | 20 |

{siteConfig.tagline}

21 |
22 |
23 | 26 | Tutorial 27 | 28 |
29 |
30 |
31 |
32 | ); 33 | } 34 | 35 | export default function Home(): JSX.Element { 36 | const { siteConfig } = useDocusaurusContext(); 37 | return ( 38 | 41 | 42 |
43 | 44 |
45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /docs/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /docs/src/theme/CodeBlock/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CodeBlock from '@theme-original/CodeBlock'; 3 | import type CodeBlockType from '@theme/CodeBlock'; 4 | import type { WrapperProps } from '@docusaurus/types'; 5 | 6 | type Props = WrapperProps & { 7 | source?: string; 8 | } 9 | 10 | export default function CodeBlockWrapper(props: Props): JSX.Element { 11 | const { source, ...codeBlockProps } = props; 12 | return ( 13 | <> 14 | 15 | {source && 16 |
17 | View Source 18 |
19 | } 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srdtrk/go-codegen/ed67358133db1fbf79e1b307a3b64cc618d50322/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/cosmwasm-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/static/img/easy_deploy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/static/img/ibc-logo-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/static/img/icons/hi-coffee-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/static/img/icons/hi-info-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/icons/hi-note-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/icons/hi-prerequisite-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/static/img/icons/hi-reading-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/icons/hi-star-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/icons/hi-target-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/icons/hi-tip-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/static/img/icons/hi-warn-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/white-ibc-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | corePlugins: { 4 | preflight: false, // disable Tailwind's reset 5 | }, 6 | darkMode: ['class', '[data-theme="dark"]'], 7 | content: [ 8 | './pages/**/*.{ts,tsx}', 9 | './components/**/*.{ts,tsx}', 10 | './app/**/*.{ts,tsx}', 11 | './src/**/*.{ts,tsx}', 12 | ], 13 | prefix: "", 14 | theme: { 15 | container: { 16 | center: true, 17 | padding: "2rem", 18 | screens: { 19 | "2xl": "1400px", 20 | }, 21 | }, 22 | extend: { 23 | colors: { 24 | gray: { 25 | 0: "#FFFFFF", 26 | 30: "rgba(0, 0, 0, 0.03)", 27 | 1000: "#000000", 28 | }, 29 | border: "hsl(var(--border))", 30 | input: "hsl(var(--input))", 31 | ring: "hsl(var(--ring))", 32 | background: "hsl(var(--background))", 33 | foreground: "hsl(var(--foreground))", 34 | primary: { 35 | DEFAULT: "hsl(var(--primary))", 36 | foreground: "hsl(var(--primary-foreground))", 37 | }, 38 | secondary: { 39 | DEFAULT: "hsl(var(--secondary))", 40 | foreground: "hsl(var(--secondary-foreground))", 41 | }, 42 | destructive: { 43 | DEFAULT: "hsl(var(--destructive))", 44 | foreground: "hsl(var(--destructive-foreground))", 45 | }, 46 | muted: { 47 | DEFAULT: "hsl(var(--muted))", 48 | foreground: "hsl(var(--muted-foreground))", 49 | }, 50 | accent: { 51 | DEFAULT: "hsl(var(--accent))", 52 | foreground: "hsl(var(--accent-foreground))", 53 | }, 54 | popover: { 55 | DEFAULT: "hsl(var(--popover))", 56 | foreground: "hsl(var(--popover-foreground))", 57 | }, 58 | card: { 59 | DEFAULT: "hsl(var(--card))", 60 | foreground: "hsl(var(--card-foreground))", 61 | }, 62 | }, 63 | borderRadius: { 64 | lg: "var(--radius)", 65 | md: "calc(var(--radius) - 2px)", 66 | sm: "calc(var(--radius) - 4px)", 67 | }, 68 | keyframes: { 69 | "accordion-down": { 70 | from: { height: "0" }, 71 | to: { height: "var(--radix-accordion-content-height)" }, 72 | }, 73 | "accordion-up": { 74 | from: { height: "var(--radix-accordion-content-height)" }, 75 | to: { height: "0" }, 76 | }, 77 | }, 78 | animation: { 79 | "accordion-down": "accordion-down 0.2s ease-out", 80 | "accordion-up": "accordion-up 0.2s ease-out", 81 | }, 82 | }, 83 | }, 84 | plugins: [require("tailwindcss-animate")], 85 | } 86 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@docusaurus/tsconfig", 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "paths": { 7 | "@site/*": ["./*"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/srdtrk/go-codegen 2 | 3 | go 1.21.5 4 | 5 | require ( 6 | github.com/atotto/clipboard v0.1.4 // indirect 7 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 8 | github.com/aymerick/douceur v0.2.0 // indirect 9 | github.com/catppuccin/go v0.2.0 // indirect 10 | github.com/charmbracelet/bubbles v0.18.0 // indirect 11 | github.com/charmbracelet/bubbletea v0.25.0 12 | github.com/charmbracelet/huh v0.3.0 13 | github.com/charmbracelet/huh/spinner v0.0.0-20240404200615-66118a2cb3cf 14 | github.com/charmbracelet/lipgloss v0.9.1 // indirect 15 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect 16 | github.com/dave/jennifer v1.7.0 17 | github.com/fatih/structs v1.1.0 // indirect 18 | github.com/gobuffalo/flect v0.3.0 // indirect 19 | github.com/gobuffalo/genny/v2 v2.1.0 20 | github.com/gobuffalo/github_flavored_markdown v1.1.4 // indirect 21 | github.com/gobuffalo/helpers v0.6.7 // indirect 22 | github.com/gobuffalo/logger v1.0.7 // indirect 23 | github.com/gobuffalo/packd v1.0.2 // indirect 24 | github.com/gobuffalo/plush/v4 v4.1.19 25 | github.com/gobuffalo/tags/v3 v3.1.4 // indirect 26 | github.com/gobuffalo/validate/v3 v3.3.3 // indirect 27 | github.com/gofrs/uuid v4.2.0+incompatible // indirect 28 | github.com/gorilla/css v1.0.0 // indirect 29 | github.com/iancoleman/strcase v0.3.0 30 | github.com/rs/zerolog v1.32.0 31 | github.com/spf13/cobra v1.8.0 32 | github.com/stretchr/testify v1.9.0 33 | golang.org/x/mod v0.17.0 34 | ) 35 | 36 | require ( 37 | github.com/davecgh/go-spew v1.1.1 // indirect 38 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 39 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 40 | github.com/mattn/go-colorable v0.1.13 // indirect 41 | github.com/mattn/go-isatty v0.0.20 // indirect 42 | github.com/mattn/go-localereader v0.0.1 // indirect 43 | github.com/mattn/go-runewidth v0.0.15 // indirect 44 | github.com/microcosm-cc/bluemonday v1.0.22 // indirect 45 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 46 | github.com/muesli/cancelreader v0.2.2 // indirect 47 | github.com/muesli/reflow v0.3.0 // indirect 48 | github.com/muesli/termenv v0.15.2 // indirect 49 | github.com/pmezard/go-difflib v1.0.0 // indirect 50 | github.com/rivo/uniseg v0.4.6 // indirect 51 | github.com/sergi/go-diff v1.3.1 // indirect 52 | github.com/sirupsen/logrus v1.9.0 // indirect 53 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect 54 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect 55 | github.com/spf13/pflag v1.0.5 // indirect 56 | golang.org/x/net v0.7.0 // indirect 57 | golang.org/x/sync v0.6.0 // indirect 58 | golang.org/x/sys v0.16.0 // indirect 59 | golang.org/x/term v0.16.0 // indirect 60 | golang.org/x/text v0.14.0 // indirect 61 | gopkg.in/yaml.v3 v3.0.1 // indirect 62 | ) 63 | -------------------------------------------------------------------------------- /integration_test/integration_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "testing" 9 | "time" 10 | 11 | "github.com/rs/zerolog" 12 | "github.com/stretchr/testify/suite" 13 | ) 14 | 15 | type MySuite struct { 16 | suite.Suite 17 | 18 | logger *zerolog.Logger 19 | goCodegenDir string 20 | } 21 | 22 | func TestWithMySuite(t *testing.T) { 23 | suite.Run(t, new(MySuite)) 24 | } 25 | 26 | func (s *MySuite) SetupSuite() { 27 | logger := zerolog.New( 28 | zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}, 29 | ).Level(zerolog.TraceLevel).With().Timestamp().Caller().Logger() 30 | 31 | s.logger = &logger 32 | 33 | s.goCodegenDir = "../build/go-codegen" 34 | } 35 | 36 | func (s *MySuite) GenerateMessageTypes(schemaDir string) { 37 | // Generate Go code 38 | // nolint:gosec 39 | _, err := exec.Command(s.goCodegenDir, "generate", "messages", schemaDir).Output() 40 | s.Require().NoError(err) 41 | } 42 | 43 | func (s *MySuite) GenerateMessageTypesTest(schemaDir string) { 44 | s.Run(fmt.Sprintf("GenerateMessageTypesTest: %s", schemaDir), func() { 45 | s.GenerateMessageTypes(schemaDir) 46 | 47 | // Run tests 48 | // nolint:gosec 49 | _, err := exec.Command("golangci-lint", "run", "msgs.go").Output() 50 | s.Require().NoError(err) 51 | 52 | defer func() { 53 | _, err := exec.Command("rm", "-rf", "msgs.go").Output() 54 | s.Require().NoError(err) 55 | }() 56 | }) 57 | } 58 | 59 | func (s *MySuite) GenerateQueryClient(schemaDir string) { 60 | // Generate Go code 61 | // nolint:gosec 62 | _, err := exec.Command(s.goCodegenDir, "generate", "query-client", schemaDir).Output() 63 | s.Require().NoError(err) 64 | } 65 | 66 | func (s *MySuite) GenerateQueryClientTest(schemaDir string) { 67 | s.Run(fmt.Sprintf("GenerateQueryClientTest: %s", schemaDir), func() { 68 | s.GenerateMessageTypes(schemaDir) 69 | s.GenerateQueryClient(schemaDir) 70 | 71 | defer func() { 72 | _, err := exec.Command("rm", "-rf", "msgs.go").Output() 73 | s.Require().NoError(err) 74 | _, err = exec.Command("rm", "-rf", "query.go").Output() 75 | s.Require().NoError(err) 76 | }() 77 | 78 | // Run tests 79 | // nolint:gosec 80 | _, err := exec.Command("golangci-lint", "run", "query.go", "msgs.go").Output() 81 | s.Require().NoError(err) 82 | }) 83 | } 84 | 85 | func (s *MySuite) TestMessageComposer() { 86 | s.GenerateMessageTypesTest("testdata/cw-ica-controller.json") 87 | s.GenerateMessageTypesTest("testdata/cw3-fixed-multisig.json") 88 | s.GenerateMessageTypesTest("testdata/account-nft.json") 89 | s.GenerateMessageTypesTest("testdata/cyberpunk.json") 90 | s.GenerateMessageTypesTest("testdata/hackatom.json") 91 | s.GenerateMessageTypesTest("testdata/cw721-base.json") 92 | s.GenerateMessageTypesTest("testdata/cw2981-royalties.json") 93 | s.GenerateMessageTypesTest("testdata/ics721.json") 94 | s.GenerateMessageTypesTest("testdata/dao-dao-core.json") 95 | s.GenerateMessageTypesTest("testdata/axone-objectarium.json") 96 | s.GenerateMessageTypesTest("testdata/map-test.json") 97 | } 98 | 99 | func (s *MySuite) TestQueryClient() { 100 | s.GenerateQueryClientTest("testdata/cw-ica-controller.json") 101 | s.GenerateQueryClientTest("testdata/cw3-fixed-multisig.json") 102 | s.GenerateQueryClientTest("testdata/account-nft.json") 103 | s.GenerateQueryClientTest("testdata/cyberpunk.json") 104 | s.GenerateQueryClientTest("testdata/hackatom.json") 105 | s.GenerateQueryClientTest("testdata/cw721-base.json") 106 | s.GenerateQueryClientTest("testdata/cw2981-royalties.json") 107 | s.GenerateQueryClientTest("testdata/ics721.json") 108 | s.GenerateQueryClientTest("testdata/dao-dao-core.json") 109 | s.GenerateQueryClientTest("testdata/axone-objectarium.json") 110 | s.GenerateQueryClientTest("testdata/map-test.json") 111 | } 112 | 113 | func (s *MySuite) TestInterchaintestScaffold() { 114 | // nolint:gosec 115 | _, err := exec.Command(s.goCodegenDir, "interchaintest", "scaffold", "-y", "--debug").Output() 116 | s.Require().NoError(err) 117 | 118 | err = os.Chdir("e2e/interchaintestv8") 119 | s.Require().NoError(err) 120 | 121 | s.T().Cleanup(func() { 122 | err = os.Chdir("../..") 123 | s.Require().NoError(err) 124 | 125 | _, err := exec.Command("rm", "-rf", "e2e").Output() 126 | s.Require().NoError(err) 127 | }) 128 | 129 | _, err = exec.Command("golangci-lint", "run").Output() 130 | s.Require().NoError(err) 131 | 132 | // nolint:gosec 133 | basicCmd := exec.Command("go", "test", "-v", "-run", "TestWithBasicTestSuite/TestBasic") 134 | 135 | stdout, err := basicCmd.StdoutPipe() 136 | s.Require().NoError(err) 137 | 138 | err = basicCmd.Start() 139 | s.Require().NoError(err) 140 | 141 | // output command stdout 142 | scanner := bufio.NewScanner(stdout) 143 | scanner.Split(bufio.ScanLines) 144 | for scanner.Scan() { 145 | m := scanner.Text() 146 | fmt.Println(m) 147 | } 148 | 149 | err = basicCmd.Wait() 150 | s.Require().NoError(err) 151 | } 152 | -------------------------------------------------------------------------------- /integration_test/testdata/map-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "contract_name": "map-test", 3 | "contract_version": "0.0.1", 4 | "idl_version": "1.0.0", 5 | "instantiate": { 6 | "$schema": "http://json-schema.org/draft-07/schema#", 7 | "title": "InstantiateMsg", 8 | "description": "Instantiate message", 9 | "type": "object", 10 | "additionalProperties": false 11 | }, 12 | "execute": { 13 | "$schema": "http://json-schema.org/draft-07/schema#", 14 | "title": "ExecuteMsg", 15 | "description": "Execute messages", 16 | "oneOf": [ 17 | { 18 | "title": "Foo", 19 | "type": "object", 20 | "required": [ 21 | "foo" 22 | ], 23 | "properties": { 24 | "foo": { 25 | "type": "object", 26 | "additionalProperties": false 27 | } 28 | }, 29 | "additionalProperties": false 30 | } 31 | ] 32 | }, 33 | "query": { 34 | "$schema": "http://json-schema.org/draft-07/schema#", 35 | "title": "QueryMsg", 36 | "description": "Query messages", 37 | "oneOf": [ 38 | { 39 | "title": "MapString", 40 | "type": "object", 41 | "required": [ 42 | "map_string" 43 | ], 44 | "properties": { 45 | "map_string": { 46 | "type": "object", 47 | "required": [ 48 | "foo" 49 | ], 50 | "properties": { 51 | "foo": { 52 | "type": "object", 53 | "additionalProperties": { 54 | "type": "string" 55 | } 56 | } 57 | }, 58 | "additionalProperties": false 59 | } 60 | }, 61 | "additionalProperties": false 62 | }, 63 | { 64 | "type": "object", 65 | "required": [ 66 | "map_with_value" 67 | ], 68 | "properties": { 69 | "map_with_value": { 70 | "type": "object", 71 | "required": [ 72 | "foo" 73 | ], 74 | "properties": { 75 | "foo": { 76 | "type": "object", 77 | "additionalProperties": { 78 | "$ref": "#/definitions/Value" 79 | } 80 | } 81 | }, 82 | "additionalProperties": false 83 | } 84 | }, 85 | "additionalProperties": false 86 | } 87 | ], 88 | "definitions": { 89 | "Value": { 90 | "type": "object", 91 | "required": [ 92 | "value" 93 | ], 94 | "properties": { 95 | "value": { 96 | "type": "string" 97 | } 98 | }, 99 | "additionalProperties": false 100 | } 101 | } 102 | }, 103 | "migrate": null, 104 | "sudo": null, 105 | "responses": { 106 | "map_string": { 107 | "$schema": "http://json-schema.org/draft-07/schema#", 108 | "title": "BarResponse", 109 | "type": "object", 110 | "required": [ 111 | "foo" 112 | ], 113 | "properties": { 114 | "foo": { 115 | "description": "The foo value", 116 | "type": "string" 117 | } 118 | }, 119 | "additionalProperties": false 120 | }, 121 | "map_with_value": { 122 | "$schema": "http://json-schema.org/draft-07/schema#", 123 | "title": "BarResponse", 124 | "type": "object", 125 | "required": [ 126 | "foo" 127 | ], 128 | "properties": { 129 | "foo": { 130 | "description": "The foo value", 131 | "type": "string" 132 | } 133 | }, 134 | "additionalProperties": false 135 | } 136 | }, 137 | "description": "# map-test", 138 | "title": "map-test" 139 | } 140 | -------------------------------------------------------------------------------- /integration_test/tools.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "github.com/CosmWasm/wasmd/x/wasm/types" 5 | _ "google.golang.org/grpc" 6 | ) 7 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/srdtrk/go-codegen/cmd" 8 | ) 9 | 10 | func main() { 11 | rootCmd := cmd.RootCmd() 12 | if err := rootCmd.Execute(); err != nil { 13 | fmt.Println(err) 14 | os.Exit(1) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pkg/codegen/codegen.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "github.com/dave/jennifer/jen" 8 | 9 | "github.com/srdtrk/go-codegen/pkg/schemas" 10 | "github.com/srdtrk/go-codegen/pkg/types" 11 | ) 12 | 13 | // GenerateCodeFromIDLSchema generates golang message types for your CosmWasm smart contracts. 14 | func GenerateCodeFromIDLSchema(schemaPath, outputPath, packageName string) error { 15 | idlSchema, err := schemas.IDLSchemaFromFile(schemaPath) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | if packageName == "" { 21 | if idlSchema.ContractName == "" { 22 | panic("no contract name") 23 | } 24 | 25 | packageName = idlSchema.ContractName 26 | } 27 | 28 | if !strings.HasSuffix(outputPath, ".go") { 29 | panic("output path must end with .go") 30 | } 31 | 32 | nonAlphanumericRegex := regexp.MustCompile(`[^a-zA-Z0-9 ]+`) 33 | packageName = nonAlphanumericRegex.ReplaceAllString(packageName, "") 34 | packageName = strings.ToLower(packageName) 35 | 36 | f := jen.NewFile(packageName) 37 | f.PackageComment("/* Code generated by github.com/srdtrk/go-codegen, DO NOT EDIT. */") 38 | 39 | if idlSchema.Instantiate == nil { 40 | panic("no InstantiateMsg schema") 41 | } 42 | 43 | GenerateInstantiateMsg(f, idlSchema.Instantiate) 44 | GenerateExecuteMsg(f, idlSchema.Execute) 45 | GenerateSudoMsg(f, idlSchema.Sudo) 46 | GenerateMigrateMsg(f, idlSchema.Migrate) 47 | GenerateQueryMsg(f, idlSchema.Query) 48 | GenerateResponses(f, idlSchema.Responses) 49 | 50 | types.DefaultLogger().Info().Msgf("Generating code to %s", outputPath) 51 | generateDefinitions(f) 52 | 53 | if err := f.Save(outputPath); err != nil { 54 | return err 55 | } 56 | 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /pkg/codegen/codegentests/tuple_test.go: -------------------------------------------------------------------------------- 1 | package codegentests_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestTuple(t *testing.T) { 11 | t.Parallel() 12 | 13 | t.Run("TestBasicTuple", func(t *testing.T) { 14 | basicTuple := Tuple_of_ClassId_and_TokenId{ 15 | F0: ClassId("myClassId"), 16 | F1: TokenId("myTokenId"), 17 | } 18 | 19 | jsonBz, err := json.Marshal(basicTuple) 20 | require.NoError(t, err) 21 | require.Equal(t, `["myClassId","myTokenId"]`, string(jsonBz)) 22 | 23 | var unmarshalledTuple Tuple_of_ClassId_and_TokenId 24 | err = json.Unmarshal(jsonBz, &unmarshalledTuple) 25 | require.NoError(t, err) 26 | require.Equal(t, basicTuple, unmarshalledTuple) 27 | }) 28 | 29 | t.Run("TestNestedTuple", func(t *testing.T) { 30 | nestedTuple := Tuple_of_Tuple_of_ClassId_and_TokenId_and_string{ 31 | F0: Tuple_of_ClassId_and_TokenId{ 32 | F0: ClassId("myClassId"), 33 | F1: TokenId("myTokenId"), 34 | }, 35 | F1: "hello", 36 | } 37 | 38 | jsonBz, err := json.Marshal(nestedTuple) 39 | require.NoError(t, err) 40 | require.Equal(t, `[["myClassId","myTokenId"],"hello"]`, string(jsonBz)) 41 | 42 | var unmarshalledTuple Tuple_of_Tuple_of_ClassId_and_TokenId_and_string 43 | err = json.Unmarshal(jsonBz, &unmarshalledTuple) 44 | require.NoError(t, err) 45 | require.Equal(t, nestedTuple, unmarshalledTuple) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /pkg/codegen/execute.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | 7 | "github.com/dave/jennifer/jen" 8 | 9 | "github.com/srdtrk/go-codegen/pkg/schemas" 10 | "github.com/srdtrk/go-codegen/pkg/types" 11 | ) 12 | 13 | // Generates the code for ExecuteMsg 14 | func GenerateExecuteMsg(f *jen.File, schema *schemas.JSONSchema) { 15 | if schema == nil { 16 | types.DefaultLogger().Info().Msg("No ExecuteMsg found. Skipping...") 17 | return 18 | } 19 | 20 | generateEnumMsg(f, schema, []string{"ExecuteMsg", "ExecuteMsg_for_Empty"}) 21 | } 22 | 23 | // Generates the code for SudoMsg 24 | func GenerateSudoMsg(f *jen.File, schema *schemas.JSONSchema) { 25 | if schema == nil { 26 | types.DefaultLogger().Info().Msg("No SudoMsg found. Skipping...") 27 | return 28 | } 29 | 30 | generateEnumMsg(f, schema, []string{"SudoMsg", "SudoMsg_for_Empty"}) 31 | } 32 | 33 | // Generates the code for QueryMsg 34 | func GenerateQueryMsg(f *jen.File, schema *schemas.JSONSchema) { 35 | if schema == nil { 36 | types.DefaultLogger().Info().Msg("No QueryMsg found. Skipping...") 37 | return 38 | } 39 | 40 | generateEnumMsg(f, schema, []string{"QueryMsg", "QueryMsg_for_Empty"}) 41 | } 42 | 43 | func generateEnumMsg(f *jen.File, schema *schemas.JSONSchema, allowedTitles []string) { 44 | if schema == nil { 45 | panic(fmt.Errorf("schema of %s is nil", allowedTitles)) 46 | } 47 | 48 | if err := validateAsEnumMsg(schema, allowedTitles); err != nil { 49 | panic(err) 50 | } 51 | 52 | f.Comment(schema.Description) 53 | f.Type().Id(schema.Title).Struct( 54 | generateFieldsFromOneOf(schema.OneOf, schema.Title+"_")..., 55 | ) 56 | 57 | RegisterDefinitions(schema.Definitions) 58 | } 59 | 60 | func validateAsEnumMsg(schema *schemas.JSONSchema, allowedTitles []string) error { 61 | if !slices.Contains(allowedTitles, schema.Title) { 62 | return fmt.Errorf("title %s is not one of %s", schema.Title, allowedTitles) 63 | } 64 | if len(schema.OneOf) == 0 { 65 | types.DefaultLogger().Warn().Msg(fmt.Sprintf("%s is an empty enum", schema.Title)) 66 | } 67 | 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /pkg/codegen/instantiate.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dave/jennifer/jen" 7 | 8 | "github.com/srdtrk/go-codegen/pkg/schemas" 9 | "github.com/srdtrk/go-codegen/pkg/types" 10 | ) 11 | 12 | func GenerateInstantiateMsg(f *jen.File, schema *schemas.JSONSchema) { 13 | if schema == nil { 14 | types.DefaultLogger().Warn().Msg("No InstantiateMsg found. Skipping...") 15 | return 16 | } 17 | 18 | generateStructMsg(f, schema, "InstantiateMsg") 19 | } 20 | 21 | func GenerateMigrateMsg(f *jen.File, schema *schemas.JSONSchema) { 22 | if schema == nil { 23 | types.DefaultLogger().Info().Msg("No MigrateMsg found. Skipping...") 24 | return 25 | } 26 | 27 | // MigrateMsg can be either a struct or an enum 28 | generateStructOrEnumMsg(f, schema, "MigrateMsg") 29 | } 30 | 31 | func generateStructMsg(f *jen.File, schema *schemas.JSONSchema, allowedTitle string) { 32 | if schema == nil { 33 | panic(fmt.Errorf("schema of %s is nil", allowedTitle)) 34 | } 35 | 36 | if err := validateAsStructMsg(schema, allowedTitle); err != nil { 37 | panic(err) 38 | } 39 | 40 | f.Comment(schema.Description) 41 | f.Type().Id(schema.Title).Struct( 42 | generateFieldsFromProperties(schema.Properties, true)..., 43 | ) 44 | 45 | RegisterDefinitions(schema.Definitions) 46 | } 47 | 48 | func validateAsStructMsg(schema *schemas.JSONSchema, allowedTitle string) error { 49 | if schema.Title != allowedTitle { 50 | return fmt.Errorf("title must be %v", allowedTitle) 51 | } 52 | 53 | if schema.Type == nil { 54 | return fmt.Errorf("%v type must be defined", allowedTitle) 55 | } 56 | 57 | if schema.Type[0] != "object" { 58 | return fmt.Errorf("%v type must be object", allowedTitle) 59 | } 60 | 61 | return nil 62 | } 63 | 64 | // Generate the msg regardless if it is a struct or enum this is mostly needed for migration msgs 65 | func generateStructOrEnumMsg(f *jen.File, schema *schemas.JSONSchema, allowedTitle string) { 66 | if schema == nil { 67 | panic(fmt.Errorf("schema of %s is nil", allowedTitle)) 68 | } 69 | 70 | if err := validateAsStructMsg(schema, allowedTitle); err == nil { 71 | generateStructMsg(f, schema, allowedTitle) 72 | return 73 | } 74 | 75 | if err := validateAsEnumMsg(schema, []string{allowedTitle, allowedTitle + "_for_Empty"}); err == nil { 76 | generateEnumMsg(f, schema, []string{allowedTitle, allowedTitle + "_for_Empty"}) 77 | return 78 | } 79 | 80 | panic(fmt.Errorf("%s schema is neither a struct nor an enum", allowedTitle)) 81 | } 82 | -------------------------------------------------------------------------------- /pkg/codegen/oneof.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dave/jennifer/jen" 7 | "github.com/iancoleman/strcase" 8 | 9 | "github.com/srdtrk/go-codegen/pkg/schemas" 10 | ) 11 | 12 | func generateFieldsFromOneOf(oneOf []*schemas.JSONSchema, typePrefix string) []jen.Code { 13 | ptrFalse := false 14 | fields := []jen.Code{} 15 | for _, schema := range oneOf { 16 | if schema.Title == "" && len(schema.Properties) != 1 { 17 | panic(fmt.Errorf("cannot determine the name of the field %v", schema)) 18 | } 19 | 20 | var ( 21 | name string 22 | jsonKey string 23 | ) 24 | // NOTE: there is only one property in this map 25 | for k, prop := range schema.Properties { 26 | name, jsonKey = validatedTitleAndJSONKey(schema.Title, k) 27 | 28 | typeName := typePrefix + strcase.ToCamel(jsonKey) 29 | 30 | RegisterDefinition(typeName, prop) 31 | } 32 | 33 | RegisterDefinitions(schema.Definitions) 34 | 35 | // add comment 36 | fields = append(fields, jen.Comment(schema.Description)) 37 | // add field 38 | fields = append(fields, generateFieldFromSchema(name, jsonKey, schema, &ptrFalse, typePrefix, true)) 39 | } 40 | return fields 41 | } 42 | 43 | func validatedTitleAndJSONKey(title, key string) (string, string) { 44 | if title == "" && key == "" { 45 | panic(fmt.Errorf("cannot determine the name of the field")) 46 | } 47 | if title == "" { 48 | title = strcase.ToCamel(key) 49 | } 50 | if key == "" { 51 | key = strcase.ToSnake(title) 52 | } 53 | return title, key 54 | } 55 | -------------------------------------------------------------------------------- /pkg/codegen/properties.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | "strings" 7 | 8 | "github.com/dave/jennifer/jen" 9 | "github.com/iancoleman/strcase" 10 | 11 | "github.com/srdtrk/go-codegen/pkg/schemas" 12 | "github.com/srdtrk/go-codegen/pkg/types" 13 | ) 14 | 15 | func generateFieldsFromProperties(props map[string]*schemas.JSONSchema, useTags bool) []jen.Code { 16 | fields := []jen.Code{} 17 | for name, schema := range props { 18 | // add comment 19 | fields = append(fields, jen.Comment(schema.Description)) 20 | // add field 21 | fields = append(fields, generateFieldFromSchema(name, name, schema, nil, "", useTags)) 22 | } 23 | return fields 24 | } 25 | 26 | func generateFieldFromSchema(name, jsonKey string, schema *schemas.JSONSchema, required *bool, typePrefix string, useTags bool) jen.Code { 27 | if name == "" || jsonKey == "" { 28 | panic(fmt.Errorf("cannot determine the name of the field for schema %v", schema)) 29 | } 30 | pascalName := strcase.ToCamel(name) 31 | 32 | typeStr, err := getType(pascalName, schema, required, typePrefix, true) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | if useTags { 38 | tags := map[string]string{} 39 | if strings.HasPrefix(typeStr, "*") { 40 | tags["json"] = jsonKey + ",omitempty" 41 | } else { 42 | tags["json"] = jsonKey 43 | } 44 | 45 | return jen.Id(pascalName).Op(typeStr).Tag(tags) 46 | } 47 | 48 | return jen.Id(pascalName).Op(typeStr) 49 | } 50 | 51 | func getType(name string, schema *schemas.JSONSchema, required *bool, typePrefix string, isField bool) (string, error) { 52 | if len(schema.Type) == 0 { 53 | var underlyingSchemas []*schemas.JSONSchema 54 | switch { 55 | case schema.AllOf != nil: 56 | if len(schema.AllOf) > 1 { 57 | return "", fmt.Errorf("length of allOf is greater than 1 in %s", name) 58 | } 59 | underlyingSchemas = schema.AllOf 60 | case schema.AnyOf != nil: 61 | switch len(schema.AnyOf) { 62 | case 0: 63 | return "", fmt.Errorf("length of anyOf is 0 in %s", name) 64 | case 1: 65 | underlyingSchemas = schema.AnyOf 66 | case 2: 67 | if slices.ContainsFunc(schema.AnyOf, func(s *schemas.JSONSchema) bool { 68 | return slices.Contains(s.Type, schemas.TypeNameNull) 69 | }) { 70 | if isField { 71 | notRequired := false 72 | return getType(name, schema.AnyOf[0], ¬Required, typePrefix, true) 73 | } 74 | 75 | required := true 76 | return getType(name, schema.AnyOf[0], &required, "Nullable_"+typePrefix, false) 77 | } 78 | default: 79 | return "", fmt.Errorf("length of anyOf is %d in %s", len(schema.AllOf), name) 80 | } 81 | case schema.Ref != nil: 82 | if !strings.HasPrefix(*schema.Ref, defPrefix) { 83 | return "", fmt.Errorf("cannot determine the type of %s: ref is not prefixed with %s", name, defPrefix) 84 | } 85 | 86 | typeStr := typePrefix + strings.TrimPrefix(*schema.Ref, defPrefix) 87 | if isField && required != nil && !*required { 88 | typeStr = "*" + typeStr 89 | } 90 | 91 | return typeStr, nil 92 | case schema.OneOf != nil: 93 | if schema.Title != "" { 94 | return schema.Title, nil 95 | } 96 | 97 | return "", fmt.Errorf("cannot determine the type of %s: title is empty", name) 98 | default: 99 | return "", fmt.Errorf("cannot determine the type of %s: type is not matched; %v", name, schema) 100 | } 101 | 102 | if len(underlyingSchemas) == 0 || len(underlyingSchemas) > 2 { 103 | return "", fmt.Errorf("cannot determine the type of %s: underlying schemas length is %d", name, len(underlyingSchemas)) 104 | } 105 | 106 | if underlyingSchemas[0].Ref == nil || len(*underlyingSchemas[0].Ref) == 0 { 107 | return "", fmt.Errorf("cannot determine the type of %s: ref is nil or empty", name) 108 | } 109 | 110 | var isOptional bool 111 | if required != nil { 112 | isOptional = !*required 113 | } else { 114 | isOptional = slices.ContainsFunc(underlyingSchemas, func(s *schemas.JSONSchema) bool { 115 | return slices.Contains(s.Type, schemas.TypeNameNull) 116 | }) 117 | } 118 | 119 | if !strings.HasPrefix(*underlyingSchemas[0].Ref, defPrefix) { 120 | return "", fmt.Errorf("cannot determine the type of %s: ref is not prefixed with %s", name, defPrefix) 121 | } 122 | 123 | typeStr := strings.TrimPrefix(*underlyingSchemas[0].Ref, defPrefix) 124 | typeStr = typePrefix + typeStr 125 | if isOptional { 126 | typeStr = "*" + typeStr 127 | } 128 | 129 | return typeStr, nil 130 | } 131 | 132 | var isOptional bool 133 | if required != nil { 134 | isOptional = !*required 135 | } else { 136 | isOptional = slices.Contains(schema.Type, schemas.TypeNameNull) 137 | } 138 | 139 | var typeStr string 140 | switch schema.Type[0] { 141 | case schemas.TypeNameString: 142 | typeStr = "string" 143 | case schemas.TypeNameInteger: 144 | typeStr = "int" 145 | case schemas.TypeNameNumber: 146 | typeStr = "float64" 147 | case schemas.TypeNameBoolean: 148 | typeStr = "bool" 149 | case schemas.TypeNameNull: 150 | typeStr = "any" 151 | types.DefaultLogger().Warn().Msgf("null type is used in %s, any is used instead", name) 152 | case schemas.TypeNameArray: 153 | if typePrefix != "" { 154 | return "", fmt.Errorf("cannot determine the type of array %s; type prefix is not supported", name) 155 | } 156 | 157 | switch { 158 | case len(schema.Items) > 1 && !slices.ContainsFunc(schema.Items, func(s schemas.JSONSchema) bool { return len(s.Type) != 1 || s.Type[0] != schema.Items[0].Type[0] }): 159 | // This case means that all items have the same type. This is similar to having an array of a single item. 160 | fallthrough 161 | case len(schema.Items) == 1 && schema.MaxItems == nil && schema.MinItems == nil: 162 | baseType, err := getType(schema.Title, &schema.Items[0], nil, "", false) 163 | if err != nil { 164 | return "", err 165 | } 166 | 167 | typeStr = "[]" + baseType 168 | 169 | isOptional = false // arrays are always nullable 170 | case schema.MaxItems != nil && schema.MinItems != nil && *schema.MaxItems == *schema.MinItems && len(schema.Items) == *schema.MaxItems: 171 | typeStr = "Tuple_of_" 172 | for i := 0; i < *schema.MaxItems; i++ { 173 | itemType, err := getType(name, &schema.Items[i], nil, "", false) 174 | if err != nil { 175 | return "", err 176 | } 177 | 178 | typeStr += itemType 179 | 180 | if i < *schema.MaxItems-1 { 181 | typeStr += "_and_" 182 | } 183 | } 184 | 185 | RegisterDefinition(typeStr, schema) 186 | default: 187 | return "", fmt.Errorf("unsupported array definition %s", name) 188 | } 189 | case schemas.TypeNameObject: 190 | switch { 191 | case schema.Title != "": 192 | typeStr = schema.Title 193 | case len(schema.Properties) == 1: 194 | for k := range schema.Properties { 195 | typeStr = strcase.ToCamel(k) 196 | } 197 | case schema.AdditionalProperties.JSONSchema != nil: 198 | if len(schema.Properties) > 0 { 199 | return "", fmt.Errorf("cannot determine the type of object %s: properties and additionalProperties are both defined", name) 200 | } 201 | if schema.AdditionalProperties.JSONSchema.Properties != nil { 202 | return "", fmt.Errorf("cannot determine the type of object %s: a sub-object is defined in 'additionalProperties', which is currently not supported, please report this issue in https://github.com/srdtrk/go-codegen", name) 203 | } 204 | 205 | itemType, err := getType(name, schema.AdditionalProperties.JSONSchema, nil, "", false) 206 | if err != nil { 207 | return "", err 208 | } 209 | 210 | typeStr = "map[string]" + itemType 211 | isOptional = false 212 | case schema.AdditionalProperties.Bool != nil && *schema.AdditionalProperties.Bool: 213 | if len(schema.Properties) > 0 { 214 | return "", fmt.Errorf("cannot determine the type of object %s: properties and additionalProperties are both defined", name) 215 | } 216 | typeStr = "map[string]any" 217 | isOptional = false 218 | default: 219 | return "", fmt.Errorf("cannot determine the type of object %s", name) 220 | } 221 | default: 222 | return "", fmt.Errorf("cannot determine the type of %s: type is not matched", name) 223 | } 224 | 225 | typeStr = typePrefix + typeStr 226 | 227 | if isOptional { 228 | typeStr = "*" + typeStr 229 | } 230 | 231 | return typeStr, nil 232 | } 233 | -------------------------------------------------------------------------------- /pkg/codegen/responses.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | 7 | "github.com/dave/jennifer/jen" 8 | 9 | "github.com/srdtrk/go-codegen/pkg/schemas" 10 | "github.com/srdtrk/go-codegen/pkg/types" 11 | ) 12 | 13 | func GenerateResponses(f *jen.File, responses map[string]*schemas.JSONSchema) { 14 | for key, schema := range responses { 15 | title := key 16 | if schema.Title != "" { 17 | title = schema.Title 18 | } else { 19 | types.DefaultLogger().Warn().Msgf("response schema for %s should have a title, please report this issue in https://github.com/srdtrk/go-codegen", key) 20 | } 21 | 22 | RegisterDefinitions(schema.Definitions) 23 | switch { 24 | case len(schema.Type) == 1: 25 | switch schema.Type[0] { 26 | case schemas.TypeNameObject: 27 | if schema.Title == "" { 28 | panic(fmt.Sprintf("response schema for %s must have a title", key)) 29 | } 30 | duplicate, found := GetDefinition(schema.Title) 31 | if found { 32 | if duplicate.Description != schema.Description || !slices.Contains([]string{key, schema.Title}, duplicate.Title) { 33 | types.DefaultLogger().Warn().Msgf("found duplicate definition `%s` with differing implementations", schema.Title) 34 | types.DefaultLogger().Warn().Msgf("renaming the duplicate definition to `%s`", schema.Title+"_2") 35 | 36 | schema.Title += "_2" 37 | } 38 | } 39 | 40 | RegisterDefinition(schema.Title, schema) 41 | case schemas.TypeNameArray: 42 | if schema.Title == "" { 43 | types.DefaultLogger().Warn().Msgf("response schema for %s should have a title, please report this issue in https://github.com/srdtrk/go-codegen/issues", key) 44 | } 45 | 46 | RegisterDefinition(title, schema) 47 | case schemas.TypeNameString: 48 | // Do nothing 49 | case schemas.TypeNameNumber: 50 | // Do nothing 51 | case schemas.TypeNameInteger: 52 | // Do nothing 53 | case schemas.TypeNameBoolean: 54 | // Do nothing 55 | case schemas.TypeNameNull: 56 | types.DefaultLogger().Warn().Msgf("response schema for %s is of type null", key) 57 | default: 58 | types.DefaultLogger().Error().Msgf("response schema for %s is of unknown type %s", key, schema.Type[0]) 59 | } 60 | case len(schema.Type) == 2 && slices.Contains(schema.Type, schemas.TypeNameNull): 61 | switch { 62 | case slices.Contains(schema.Type, schemas.TypeNameString): 63 | // Do nothing 64 | case slices.Contains(schema.Type, schemas.TypeNameNumber): 65 | // Do nothing 66 | case slices.Contains(schema.Type, schemas.TypeNameInteger): 67 | // Do nothing 68 | case slices.Contains(schema.Type, schemas.TypeNameBoolean): 69 | // Do nothing 70 | default: 71 | types.DefaultLogger().Error().Msgf("response schema for %s is not supported, skipping... Please create an issue in https://github.com/srdtrk/go-codegen", key) 72 | } 73 | case len(schema.OneOf) != 0: 74 | if schema.Title == "" { 75 | types.DefaultLogger().Warn().Msgf("response schema for %s should have a title, please report this issue in https://github.com/srdtrk/go-codegen", key) 76 | } 77 | 78 | RegisterDefinition(title, schema) 79 | case len(schema.AllOf) == 1: 80 | if schema.Title == "" { 81 | types.DefaultLogger().Warn().Msgf("response schema for %s should have a title, please report this issue in https://github.com/srdtrk/go-codegen", key) 82 | } 83 | RegisterDefinition(title, schema) 84 | case schema.Ref != nil: 85 | if schema.Title == "" { 86 | types.DefaultLogger().Warn().Msgf("response schema for %s should have a title, please report this issue in https://github.com/srdtrk/go-codegen", key) 87 | } 88 | RegisterDefinition(title, schema) 89 | case len(schema.AnyOf) != 0: 90 | if schema.Title == "" { 91 | types.DefaultLogger().Warn().Msgf("response schema for %s should have a title, please report this issue in https://github.com/srdtrk/go-codegen", key) 92 | } 93 | RegisterDefinition(title, schema) 94 | RegisterDefinitions(schema.Definitions) 95 | default: 96 | types.DefaultLogger().Error().Msgf("response schema for %s is not supported, skipping... Please create an issue in https://github.com/srdtrk/go-codegen", key) 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /pkg/go/cmd/gocmd.go: -------------------------------------------------------------------------------- 1 | //nolint:gosec 2 | package gocmd 3 | 4 | import ( 5 | "os" 6 | "os/exec" 7 | ) 8 | 9 | const ( 10 | // CommandInstall represents go "install" command. 11 | CommandInstall = "install" 12 | 13 | // CommandGet represents go "get" command. 14 | CommandGet = "get" 15 | 16 | // CommandBuild represents go "build" command. 17 | CommandBuild = "build" 18 | 19 | // CommandMod represents go "mod" command. 20 | CommandMod = "mod" 21 | 22 | // CommandModTidy represents go mod "tidy" command. 23 | CommandModTidy = "tidy" 24 | 25 | // CommandModVerify represents go mod "verify" command. 26 | CommandModVerify = "verify" 27 | 28 | // CommandModDownload represents go mod "download" command. 29 | CommandModDownload = "download" 30 | 31 | // CommandFmt represents go "fmt" command. 32 | CommandFmt = "fmt" 33 | 34 | // CommandEnv represents go "env" command. 35 | CommandEnv = "env" 36 | 37 | // CommandList represents go "list" command. 38 | CommandList = "list" 39 | 40 | // CommandTest represents go "test" command. 41 | CommandTest = "test" 42 | 43 | // EnvGOARCH represents GOARCH variable. 44 | EnvGOARCH = "GOARCH" 45 | // EnvGOMOD represents GOMOD variable. 46 | EnvGOMOD = "GOMOD" 47 | // EnvGOOS represents GOOS variable. 48 | EnvGOOS = "GOOS" 49 | 50 | // FlagGcflags represents gcflags go flag. 51 | FlagGcflags = "-gcflags" 52 | // FlagGcflagsValueDebug represents debug go flags. 53 | FlagGcflagsValueDebug = "all=-N -l" 54 | // FlagLdflags represents ldflags go flag. 55 | FlagLdflags = "-ldflags" 56 | // FlagTags represents tags go flag. 57 | FlagTags = "-tags" 58 | // FlagMod represents mod go flag. 59 | FlagMod = "-mod" 60 | // FlagModValueReadOnly represents readonly go flag. 61 | FlagModValueReadOnly = "readonly" 62 | // FlagOut represents out go flag. 63 | FlagOut = "-o" 64 | ) 65 | 66 | // Env returns the value of `go env name`. 67 | func Env(name string) (string, error) { 68 | bytes, err := exec.Command(Name(), CommandEnv, name).Output() 69 | if err != nil { 70 | return "", err 71 | } 72 | 73 | return string(bytes), nil 74 | } 75 | 76 | // Name returns the name of Go binary to use. 77 | func Name() string { 78 | custom := os.Getenv("GONAME") 79 | if custom != "" { 80 | return custom 81 | } 82 | return "go" 83 | } 84 | 85 | // Fmt runs go fmt on path. 86 | func Fmt(path string) error { 87 | cmd := exec.Command(Name(), CommandFmt, "./...") 88 | cmd.Dir = path 89 | 90 | _, err := cmd.Output() 91 | return err 92 | } 93 | 94 | // ModTidy runs go mod tidy on path with options. 95 | func ModTidy(path string) error { 96 | cmd := exec.Command(Name(), CommandMod, CommandModTidy) 97 | cmd.Dir = path 98 | 99 | _, err := cmd.Output() 100 | return err 101 | } 102 | 103 | // ModVerify runs go mod verify on path with options. 104 | func ModVerify(path string) error { 105 | cmd := exec.Command(Name(), CommandMod, CommandModVerify) 106 | cmd.Dir = path 107 | 108 | _, err := cmd.Output() 109 | return err 110 | } 111 | 112 | // ModDownload runs go mod download on a path with options. 113 | func ModDownload(path string, json bool) error { 114 | command := []string{CommandMod, CommandModDownload} 115 | if json { 116 | command = append(command, "-json") 117 | } 118 | 119 | cmd := exec.Command(Name(), command...) 120 | cmd.Dir = path 121 | 122 | _, err := cmd.Output() 123 | return err 124 | } 125 | 126 | // Get runs go get pkgs on path with options. 127 | func Get(path string, pkgs []string) error { 128 | command := []string{CommandGet} 129 | command = append(command, pkgs...) 130 | 131 | cmd := exec.Command(Name(), command...) 132 | cmd.Dir = path 133 | 134 | _, err := cmd.Output() 135 | return err 136 | } 137 | -------------------------------------------------------------------------------- /pkg/go/module/gomodule.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "errors" 5 | "io/fs" 6 | "os" 7 | "path/filepath" 8 | 9 | "golang.org/x/mod/modfile" 10 | ) 11 | 12 | // ErrGoModNotFound returned when go.mod file cannot be found for an app. 13 | var ErrGoModNotFound = errors.New("go.mod not found") 14 | 15 | // ParseAt finds and parses go.mod at app's path. 16 | func ParseAt(path string) (*modfile.File, error) { 17 | gomod, err := os.ReadFile(filepath.Join(path, "go.mod")) 18 | if err != nil { 19 | if errors.Is(err, fs.ErrNotExist) { 20 | return nil, ErrGoModNotFound 21 | } 22 | return nil, err 23 | } 24 | return modfile.Parse("", gomod, nil) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/interchaintest/codegen.go: -------------------------------------------------------------------------------- 1 | package interchaintest 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "regexp" 9 | "strings" 10 | 11 | "github.com/srdtrk/go-codegen/pkg/codegen" 12 | gomodule "github.com/srdtrk/go-codegen/pkg/go/module" 13 | "github.com/srdtrk/go-codegen/pkg/schemas" 14 | "github.com/srdtrk/go-codegen/pkg/types" 15 | "github.com/srdtrk/go-codegen/pkg/xgenny" 16 | "github.com/srdtrk/go-codegen/templates/interchaintestv8" 17 | templatetypes "github.com/srdtrk/go-codegen/templates/interchaintestv8/types" 18 | ) 19 | 20 | // GenerateTestSuite generates the interchaintest test suite 21 | func GenerateTestSuite(moduleName, outDir string, chainNum uint8, githubActions bool) error { 22 | ctx := context.Background() 23 | 24 | types.DefaultLogger().Info().Msgf("Generating test suite in %s", outDir) 25 | 26 | generators, err := getInitGenerators(moduleName, chainNum, outDir, githubActions) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | // create runners 32 | workflowsRunner := xgenny.NewRunner(ctx, ".github/workflows") 33 | testRunner := xgenny.NewRunner(ctx, outDir) 34 | 35 | err = testRunner.Run(generators...) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | if githubActions { 41 | workflowGenerators, err := getWorkflowGenerators(outDir) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | err = workflowsRunner.Run(workflowGenerators...) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | err = workflowsRunner.ApplyModifications() 52 | if err != nil { 53 | return err 54 | } 55 | } 56 | 57 | err = testRunner.ApplyModifications() 58 | if err != nil { 59 | return err 60 | } 61 | 62 | return nil 63 | } 64 | 65 | func AddContract(schemaPath, suiteDir, contractName string) error { 66 | ctx := context.Background() 67 | 68 | types.DefaultLogger().Info().Msgf("Adding contract %s", contractName) 69 | 70 | idlSchema, err := schemas.IDLSchemaFromFile(schemaPath) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | if contractName == "" { 76 | if idlSchema.ContractName == "" { 77 | panic("no contract name") 78 | } 79 | 80 | contractName = idlSchema.ContractName 81 | } 82 | 83 | if err := validateSuitePath(suiteDir); err != nil { 84 | return err 85 | } 86 | 87 | goMod, err := gomodule.ParseAt(suiteDir) 88 | if err != nil { 89 | return err 90 | } 91 | 92 | contractDir, found, err := findLine(suiteDir, templatetypes.PlaceholderContractDir) 93 | if err != nil { 94 | return err 95 | } 96 | if !found { 97 | return fmt.Errorf("could not find placeholder '%s' in %s", templatetypes.PlaceholderContractDir, suiteDir) 98 | } 99 | 100 | _, foundContractTest, err := findLine(suiteDir, interchaintestv8.PlaceholderContractSuite) 101 | if err != nil { 102 | return err 103 | } 104 | _, err = os.Stat(filepath.Join(suiteDir, "contract_test.go")) 105 | notFoundContractTestDir := os.IsNotExist(err) 106 | 107 | nonAlphanumericRegex := regexp.MustCompile(`[^a-zA-Z0-9 ]+`) 108 | packageName := nonAlphanumericRegex.ReplaceAllString(contractName, "") 109 | packageName = strings.ToLower(packageName) 110 | 111 | generators, err := getContractGenerators(idlSchema, packageName, goMod.Module.Mod.Path) 112 | if err != nil { 113 | return err 114 | } 115 | 116 | contractsDir, _ := filepath.Split(contractDir) 117 | contractRunner := xgenny.NewRunner(ctx, contractsDir) 118 | 119 | err = contractRunner.Run(generators...) 120 | if err != nil { 121 | return err 122 | } 123 | 124 | if !foundContractTest && notFoundContractTestDir { 125 | types.DefaultLogger().Info().Msg("No contract test suite found. Generating...") 126 | 127 | contractTestRunner := xgenny.NewRunner(ctx, suiteDir) 128 | 129 | relContractsDir, err := filepath.Rel(suiteDir, contractsDir) 130 | if err != nil { 131 | return err 132 | } 133 | 134 | relPackageDir := filepath.Join(relContractsDir, packageName) 135 | contractTestGenerators, err := getContractTestGenerators(relPackageDir, goMod.Module.Mod.Path, packageName) 136 | if err != nil { 137 | return err 138 | } 139 | 140 | err = contractTestRunner.Run(contractTestGenerators...) 141 | if err != nil { 142 | return err 143 | } 144 | 145 | err = contractTestRunner.ApplyModifications() 146 | if err != nil { 147 | return err 148 | } 149 | } 150 | 151 | err = contractRunner.ApplyModifications() 152 | if err != nil { 153 | return err 154 | } 155 | 156 | msgsPath := filepath.Join(contractsDir, packageName, "msgs.go") 157 | err = codegen.GenerateCodeFromIDLSchema(schemaPath, msgsPath, packageName) 158 | if err != nil { 159 | _ = os.RemoveAll(filepath.Join(contractsDir, packageName)) 160 | return err 161 | } 162 | 163 | queryPath := filepath.Join(contractsDir, packageName, "query.go") 164 | err = codegen.GenerateQueryClientFromIDLSchema(schemaPath, queryPath, packageName) 165 | if err != nil { 166 | _ = os.RemoveAll(filepath.Join(contractsDir, packageName)) 167 | return err 168 | } 169 | 170 | return nil 171 | } 172 | -------------------------------------------------------------------------------- /pkg/interchaintest/files.go: -------------------------------------------------------------------------------- 1 | package interchaintest 2 | 3 | import ( 4 | "errors" 5 | "io/fs" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/srdtrk/go-codegen/templates/interchaintestv8" 11 | ) 12 | 13 | func validateSuitePath(suitePath string) error { 14 | goModPath := filepath.Join(suitePath, "go.mod") 15 | 16 | contains, err := fileContainsLine(goModPath, interchaintestv8.PlaceholderSuiteModule) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | if !contains { 22 | return errors.New("go.mod does not contain the placeholder for suite module") 23 | } 24 | 25 | return nil 26 | } 27 | 28 | func findLine(targetDir, line string) (string, bool, error) { 29 | var ( 30 | found bool 31 | resPath string 32 | ) 33 | err := filepath.WalkDir(targetDir, func(path string, d fs.DirEntry, err error) error { 34 | if err != nil || d.IsDir() || !strings.HasSuffix(path, ".go") { 35 | return nil 36 | } 37 | 38 | contains, err := fileContainsLine(path, line) 39 | if err != nil { 40 | return nil 41 | } 42 | 43 | if contains { 44 | found = true 45 | resPath = path 46 | return fs.SkipAll 47 | } 48 | 49 | return nil 50 | }) 51 | if err != nil { 52 | return "", false, err 53 | } 54 | 55 | return resPath, found, nil 56 | } 57 | 58 | func fileContainsLine(filePath, targetLine string) (bool, error) { 59 | file, err := os.ReadFile(filePath) 60 | if err != nil { 61 | return false, err 62 | } 63 | 64 | return strings.Contains(string(file), targetLine), nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/interchaintest/generators.go: -------------------------------------------------------------------------------- 1 | package interchaintest 2 | 3 | import ( 4 | "github.com/gobuffalo/genny/v2" 5 | 6 | "github.com/srdtrk/go-codegen/pkg/schemas" 7 | "github.com/srdtrk/go-codegen/templates/interchaintestv8" 8 | "github.com/srdtrk/go-codegen/templates/interchaintestv8/chainconfig" 9 | "github.com/srdtrk/go-codegen/templates/interchaintestv8/e2esuite" 10 | "github.com/srdtrk/go-codegen/templates/interchaintestv8/github" 11 | "github.com/srdtrk/go-codegen/templates/interchaintestv8/testvalues" 12 | "github.com/srdtrk/go-codegen/templates/interchaintestv8/types" 13 | ) 14 | 15 | func getWorkflowGenerators(outDir string) ([]*genny.Generator, error) { 16 | var generators []*genny.Generator 17 | 18 | // main generator 19 | g, err := github.NewGenerator(&github.Options{ 20 | TestDir: outDir, 21 | }) 22 | if err != nil { 23 | return nil, err 24 | } 25 | generators = append(generators, g) 26 | 27 | return generators, nil 28 | } 29 | 30 | func getInitGenerators(moduleName string, chainNum uint8, outDir string, githubActions bool) ([]*genny.Generator, error) { 31 | var generators []*genny.Generator 32 | 33 | // main generator 34 | g, err := interchaintestv8.NewGenerator(&interchaintestv8.Options{ 35 | ModulePath: moduleName, 36 | Github: githubActions, 37 | ChainNum: chainNum, 38 | }) 39 | if err != nil { 40 | return nil, err 41 | } 42 | generators = append(generators, g) 43 | 44 | // suite generator 45 | sg, err := e2esuite.NewGenerator(&e2esuite.Options{ 46 | ModulePath: moduleName, 47 | ChainNum: chainNum, 48 | TestDir: outDir, 49 | }) 50 | if err != nil { 51 | return nil, err 52 | } 53 | generators = append(generators, sg) 54 | 55 | // chain config generator 56 | cg, err := chainconfig.NewGenerator(&chainconfig.Options{ 57 | ModulePath: moduleName, 58 | ChainNum: chainNum, 59 | }) 60 | if err != nil { 61 | return nil, err 62 | } 63 | generators = append(generators, cg) 64 | 65 | // types generator 66 | tg, err := types.NewGenerator() 67 | if err != nil { 68 | return nil, err 69 | } 70 | generators = append(generators, tg) 71 | 72 | // testvalues generator 73 | tvg, err := testvalues.NewGenerator(&testvalues.Options{}) 74 | if err != nil { 75 | return nil, err 76 | } 77 | generators = append(generators, tvg) 78 | 79 | return generators, nil 80 | } 81 | 82 | func getContractGenerators(idlSchema *schemas.IDLSchema, packageName, modulePath string) ([]*genny.Generator, error) { 83 | var generators []*genny.Generator 84 | 85 | // contract generator 86 | cg, err := types.NewContractGenerator(&types.AddContractOptions{ 87 | InstantiateMsgName: idlSchema.Instantiate.Title, 88 | ExecuteMsgName: idlSchema.Execute.Title, 89 | QueryMsgName: idlSchema.Query.Title, 90 | ContractName: idlSchema.ContractName, 91 | PackageName: packageName, 92 | ModulePath: modulePath, 93 | }) 94 | if err != nil { 95 | return nil, err 96 | } 97 | generators = append(generators, cg) 98 | 99 | return generators, nil 100 | } 101 | 102 | func getContractTestGenerators(relPackageDir, modulePath, packageName string) ([]*genny.Generator, error) { 103 | var generators []*genny.Generator 104 | 105 | // contract test generator 106 | ctg, err := interchaintestv8.NewGeneratorForContractTest(&interchaintestv8.ContractTestOptions{ 107 | ModulePath: modulePath, 108 | RelPackageDir: relPackageDir, 109 | PackageName: packageName, 110 | }) 111 | if err != nil { 112 | return nil, err 113 | } 114 | generators = append(generators, ctg) 115 | 116 | return generators, nil 117 | } 118 | -------------------------------------------------------------------------------- /pkg/schemas/cosmwasm.go: -------------------------------------------------------------------------------- 1 | package schemas 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | // IDLSchema is the schema produced by cosmwasm_schema::write_api 10 | type IDLSchema struct { 11 | ContractName string `json:"contract_name"` 12 | ContractVersion string `json:"contract_version"` 13 | IdlVersion string `json:"idl_version"` 14 | Instantiate *JSONSchema `json:"instantiate"` 15 | Execute *JSONSchema `json:"execute"` 16 | Query *JSONSchema `json:"query"` 17 | Migrate *JSONSchema `json:"migrate"` 18 | Sudo *JSONSchema `json:"sudo"` 19 | Responses map[string]*JSONSchema `json:"responses"` 20 | } 21 | 22 | // IDLSchemaFromFile reads a JSON file and returns the IDL schema 23 | func IDLSchemaFromFile(filePath string) (*IDLSchema, error) { 24 | schemaFile, err := os.ReadFile(filePath) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | var idlSchema IDLSchema 30 | err = json.Unmarshal(schemaFile, &idlSchema) 31 | if err != nil { 32 | return nil, fmt.Errorf("error unmarshalling config file: %w", err) 33 | } 34 | 35 | return &idlSchema, nil 36 | } 37 | -------------------------------------------------------------------------------- /pkg/schemas/schema.go: -------------------------------------------------------------------------------- 1 | package schemas 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | // JSONSchema represents a JSON Schema object type. 9 | // based on https://github.com/CosmWasm/ts-codegen/blob/eeba0cf1e0cbadfb6cfc103412108e763fdb9f6a/packages/wasm-ast-types/types/types.d.ts#L25 10 | type JSONSchema struct { 11 | Schema *string `json:"$schema,omitempty"` // Section 6.1. 12 | Ref *string `json:"$ref,omitempty"` // Section 7. 13 | AdditionalItems *JSONSchema `json:"additionalItems,omitempty"` // Section 5.9. 14 | Items JSONSchemaList `json:"items,omitempty"` // Section 5.9. 15 | Required []string `json:"required,omitempty"` // Section 5.15. 16 | Properties map[string]*JSONSchema `json:"properties,omitempty"` // Section 5.16. 17 | PatternProperties map[string]*JSONSchema `json:"patternProperties,omitempty"` // Section 5.17. 18 | AdditionalProperties *JSONSchemaOrBool `json:"additionalProperties,omitempty"` // Section 5.18. 19 | Type TypeList `json:"type,omitempty"` // Section 5.21. 20 | AllOf []*JSONSchema `json:"allOf,omitempty"` // Section 5.22. 21 | AnyOf []*JSONSchema `json:"anyOf,omitempty"` // Section 5.23. 22 | OneOf []*JSONSchema `json:"oneOf,omitempty"` // Section 5.24. 23 | Title string `json:"title,omitempty"` // Section 6.1. 24 | Description string `json:"description,omitempty"` // Section 6.1. 25 | Definitions map[string]*JSONSchema `json:"definitions,omitempty"` 26 | MaxItems *int `json:"maxItems,omitempty"` 27 | MinItems *int `json:"minItems,omitempty"` 28 | 29 | // Additional fields found in cosmwasm_schema 30 | Enum []string `json:"enum,omitempty"` 31 | } 32 | 33 | type JSONSchemaList []JSONSchema 34 | 35 | // UnmarshalJSON accepts list or single schema. 36 | func (l *JSONSchemaList) UnmarshalJSON(raw []byte) error { 37 | if len(raw) > 0 && raw[0] == '[' { 38 | var s []JSONSchema 39 | if err := json.Unmarshal(raw, &s); err != nil { 40 | return fmt.Errorf("failed to unmarshal type list: %w", err) 41 | } 42 | 43 | *l = s 44 | 45 | return nil 46 | } 47 | 48 | var s JSONSchema 49 | if err := json.Unmarshal(raw, &s); err != nil { 50 | return fmt.Errorf("failed to unmarshal type list: %w", err) 51 | } 52 | 53 | *l = []JSONSchema{s} 54 | 55 | return nil 56 | } 57 | 58 | type TypeList []string 59 | 60 | // UnmarshalJSON implements json.Unmarshaler. 61 | func (t *TypeList) UnmarshalJSON(b []byte) error { 62 | if len(b) > 0 && b[0] == '[' { 63 | var s []string 64 | if err := json.Unmarshal(b, &s); err != nil { 65 | return fmt.Errorf("failed to unmarshal type list: %w", err) 66 | } 67 | 68 | *t = s 69 | 70 | return nil 71 | } 72 | 73 | var s string 74 | if err := json.Unmarshal(b, &s); err != nil { 75 | return fmt.Errorf("failed to unmarshal type list: %w", err) 76 | } 77 | 78 | if s != "" { 79 | *t = []string{s} 80 | } else { 81 | *t = nil 82 | } 83 | 84 | return nil 85 | } 86 | 87 | // JSONSchemaOrBool represents a JSONSchema or a boolean value. 88 | type JSONSchemaOrBool struct { 89 | JSONSchema *JSONSchema 90 | Bool *bool 91 | } 92 | 93 | func (t *JSONSchemaOrBool) UnmarshalJSON(b []byte) error { 94 | if err := json.Unmarshal(b, &t.Bool); err == nil { 95 | return nil 96 | } 97 | t.Bool = nil 98 | return json.Unmarshal(b, &t.JSONSchema) 99 | } 100 | -------------------------------------------------------------------------------- /pkg/schemas/types.go: -------------------------------------------------------------------------------- 1 | package schemas 2 | 3 | const ( 4 | TypeNameString = "string" 5 | TypeNameArray = "array" 6 | TypeNameNumber = "number" 7 | TypeNameInteger = "integer" 8 | TypeNameObject = "object" 9 | TypeNameBoolean = "boolean" 10 | TypeNameNull = "null" 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/types/logger.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/rs/zerolog" 7 | ) 8 | 9 | func DefaultLogger() *zerolog.Logger { 10 | logger := zerolog.New( 11 | zerolog.ConsoleWriter{Out: os.Stderr}, 12 | ).Level(zerolog.TraceLevel).With().Timestamp().Logger() 13 | 14 | return &logger 15 | } 16 | -------------------------------------------------------------------------------- /pkg/types/version.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // Version is the current version of the package 4 | const Version = "0.2.5" 5 | -------------------------------------------------------------------------------- /pkg/xgenny/cp.go: -------------------------------------------------------------------------------- 1 | package xgenny 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | // CopyFolder copy the source folder to the destination folder. 10 | func CopyFolder(srcPath, dstPath string) error { 11 | return filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error { 12 | if err != nil { 13 | return err 14 | } 15 | 16 | // Skip the root folder 17 | if path == srcPath { 18 | return nil 19 | } 20 | 21 | // Get the relative path within the source folder 22 | relativePath, err := filepath.Rel(srcPath, path) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | // Create the corresponding destination path 28 | destPath := filepath.Join(dstPath, relativePath) 29 | 30 | if info.IsDir() { 31 | // Create the directory in the destination 32 | err = os.MkdirAll(destPath, 0o755) 33 | if err != nil { 34 | return err 35 | } 36 | } else { 37 | // Copy the file content 38 | err = CopyFile(path, destPath) 39 | if err != nil { 40 | return err 41 | } 42 | } 43 | return nil 44 | }) 45 | } 46 | 47 | // CopyFile copy the source file to the destination file. 48 | func CopyFile(srcPath, dstPath string) error { 49 | srcFile, err := os.OpenFile(srcPath, os.O_RDONLY, 0o666) 50 | if err != nil { 51 | return err 52 | } 53 | defer srcFile.Close() 54 | 55 | destFile, err := os.Create(dstPath) 56 | if err != nil { 57 | return err 58 | } 59 | defer destFile.Close() 60 | 61 | _, err = io.Copy(destFile, srcFile) 62 | return err 63 | } 64 | -------------------------------------------------------------------------------- /pkg/xgenny/plush.go: -------------------------------------------------------------------------------- 1 | package xgenny 2 | 3 | import ( 4 | "github.com/gobuffalo/genny/v2" 5 | "github.com/gobuffalo/plush/v4" 6 | ) 7 | 8 | // Transformer will plush-ify any file that has a ".plush" extension. 9 | func Transformer(ctx *plush.Context) genny.Transformer { 10 | t := genny.NewTransformer(".plush", func(f genny.File) (genny.File, error) { 11 | s, err := plush.RenderR(f, ctx) 12 | if err != nil { 13 | return f, err 14 | } 15 | return genny.NewFileS(f.Name(), s), nil 16 | }) 17 | t.StripExt = true 18 | return t 19 | } 20 | -------------------------------------------------------------------------------- /pkg/xgenny/randstr.go: -------------------------------------------------------------------------------- 1 | package xgenny 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz") 8 | 9 | // Runes generates a random string with n length from runes. 10 | func Runes(n int) string { 11 | b := make([]rune, n) 12 | for i := range b { 13 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 14 | } 15 | return string(b) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/xgenny/runner.go: -------------------------------------------------------------------------------- 1 | package xgenny 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/gobuffalo/genny/v2" 11 | ) 12 | 13 | type Runner struct { 14 | *genny.Runner 15 | ctx context.Context 16 | results []genny.File 17 | tmpPath string 18 | } 19 | 20 | // NewRunner is a xgenny Runner with a logger. 21 | func NewRunner(ctx context.Context, root string) *Runner { 22 | var ( 23 | runner = genny.WetRunner(ctx) 24 | tmpPath = filepath.Join(os.TempDir(), Runes(5)) 25 | ) 26 | runner.Root = root 27 | r := &Runner{ 28 | ctx: ctx, 29 | Runner: runner, 30 | tmpPath: tmpPath, 31 | results: make([]genny.File, 0), 32 | } 33 | runner.FileFn = func(f genny.File) (genny.File, error) { 34 | return wetFileFn(r, f) 35 | } 36 | return r 37 | } 38 | 39 | // ApplyModifications copy all modifications from the temporary folder to the target path. 40 | func (r *Runner) ApplyModifications() error { 41 | if _, err := os.Stat(r.tmpPath); os.IsNotExist(err) { 42 | return err 43 | } 44 | 45 | // Create the target path and copy the content from the temporary folder. 46 | if err := os.MkdirAll(r.Root, os.ModePerm); err != nil { 47 | return err 48 | } 49 | err := CopyFolder(r.tmpPath, r.Root) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | return os.RemoveAll(r.tmpPath) 55 | } 56 | 57 | // RunAndApply run the generators and apply the modifications to the target path. 58 | func (r *Runner) RunAndApply(gens ...*genny.Generator) error { 59 | if err := r.Run(gens...); err != nil { 60 | return err 61 | } 62 | return r.ApplyModifications() 63 | } 64 | 65 | // Run all generators into a temp folder for we can apply the modifications later. 66 | func (r *Runner) Run(gens ...*genny.Generator) error { 67 | // execute the modification with a wet runner 68 | for _, gen := range gens { 69 | if err := r.With(gen); err != nil { 70 | return err 71 | } 72 | if err := r.Runner.Run(); err != nil { 73 | return err 74 | } 75 | } 76 | r.results = append(r.results, r.Results().Files...) 77 | return nil 78 | } 79 | 80 | func wetFileFn(runner *Runner, f genny.File) (genny.File, error) { 81 | if d, ok := f.(genny.Dir); ok { 82 | if err := os.MkdirAll(d.Name(), d.Perm); err != nil { 83 | return f, err 84 | } 85 | return d, nil 86 | } 87 | 88 | if filepath.IsAbs(runner.Root) { 89 | return nil, errors.New("root path must be relative") 90 | } 91 | 92 | name := f.Name() 93 | if !filepath.IsAbs(name) { 94 | name = filepath.Join(runner.Root, name) 95 | } 96 | relPath, err := filepath.Rel(runner.Root, name) 97 | if err != nil { 98 | return f, err 99 | } 100 | 101 | dstPath := filepath.Join(runner.tmpPath, relPath) 102 | dir := filepath.Dir(dstPath) 103 | if err := os.MkdirAll(dir, 0o755); err != nil { 104 | return f, err 105 | } 106 | ff, err := os.Create(dstPath) 107 | if err != nil { 108 | return f, err 109 | } 110 | defer ff.Close() 111 | if _, err := io.Copy(ff, f); err != nil { 112 | return f, err 113 | } 114 | return f, nil 115 | } 116 | -------------------------------------------------------------------------------- /templates/interchaintestv8/chainconfig/embed.go: -------------------------------------------------------------------------------- 1 | package chainconfig 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "io/fs" 7 | 8 | "github.com/gobuffalo/genny/v2" 9 | 10 | "github.com/srdtrk/go-codegen/pkg/xgenny" 11 | ) 12 | 13 | //go:embed files/* files/**/* 14 | var files embed.FS 15 | 16 | func NewGenerator(opts *Options) (*genny.Generator, error) { 17 | if err := opts.Validate(); err != nil { 18 | return nil, fmt.Errorf("invalid options: %w", err) 19 | } 20 | 21 | // Remove "files/" prefix 22 | subfs, err := fs.Sub(files, "files") 23 | if err != nil { 24 | return nil, fmt.Errorf("generator sub: %w", err) 25 | } 26 | 27 | g := genny.New() 28 | 29 | err = g.SelectiveFS(subfs, nil, nil, nil, nil) 30 | if err != nil { 31 | return nil, fmt.Errorf("generator selective fs: %w", err) 32 | } 33 | 34 | g.Transformer(xgenny.Transformer(opts.plushContext())) 35 | 36 | return g, nil 37 | } 38 | -------------------------------------------------------------------------------- /templates/interchaintestv8/chainconfig/files/chainconfig/chain_config.go.plush: -------------------------------------------------------------------------------- 1 | package chainconfig 2 | 3 | import ( 4 | interchaintest "github.com/strangelove-ventures/interchaintest/v8" 5 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 6 | ) 7 | 8 | var DefaultChainSpecs = []*interchaintest.ChainSpec{ 9 | <%= for (n) in between(0, ChainNum) { %> // -- WASMD -- 10 | { 11 | ChainConfig: ibc.ChainConfig{ 12 | Type: "cosmos", 13 | Name: "wasmd-<%= n+1 %>", 14 | ChainID: "wasmd-<%= n+1 %>", 15 | Images: []ibc.DockerImage{ 16 | { 17 | Repository: "cosmwasm/wasmd", // FOR LOCAL IMAGE USE: Docker Image Name 18 | Version: "v0.50.0", // FOR LOCAL IMAGE USE: Docker Image Tag 19 | UidGid: "1025:1025", 20 | }, 21 | }, 22 | Bin: "wasmd", 23 | Bech32Prefix: "wasm", 24 | Denom: "stake", 25 | GasPrices: "0.00stake", 26 | GasAdjustment: 1.3, 27 | EncodingConfig: WasmEncodingConfig(), 28 | ModifyGenesis: defaultModifyGenesis(), 29 | TrustingPeriod: "508h", 30 | NoHostMount: false, 31 | }, 32 | }, 33 | <% } %>} 34 | -------------------------------------------------------------------------------- /templates/interchaintestv8/chainconfig/files/chainconfig/encoding.go.plush: -------------------------------------------------------------------------------- 1 | package chainconfig 2 | 3 | import ( 4 | "github.com/cosmos/gogoproto/proto" 5 | 6 | txsigning "cosmossdk.io/x/tx/signing" 7 | upgradetypes "cosmossdk.io/x/upgrade/types" 8 | 9 | "github.com/cosmos/cosmos-sdk/codec" 10 | "github.com/cosmos/cosmos-sdk/codec/address" 11 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 12 | cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" 13 | sdk "github.com/cosmos/cosmos-sdk/types" 14 | sdktestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" 15 | txtypes "github.com/cosmos/cosmos-sdk/types/tx" 16 | authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" 17 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 18 | "github.com/cosmos/cosmos-sdk/x/authz" 19 | banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" 20 | consensustypes "github.com/cosmos/cosmos-sdk/x/consensus/types" 21 | distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" 22 | govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" 23 | govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" 24 | grouptypes "github.com/cosmos/cosmos-sdk/x/group" 25 | minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" 26 | proposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" 27 | slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" 28 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 29 | 30 | ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" 31 | icacontrollertypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/types" 32 | icahosttypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host/types" 33 | feetypes "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/types" 34 | transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" 35 | v7migrations "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v7" 36 | clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" 37 | connectiontypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types" 38 | channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" 39 | solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" 40 | ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" 41 | localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" 42 | 43 | wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" 44 | ) 45 | 46 | // WasmEncodingConfig returns the global E2E encoding config for Wasm. 47 | func WasmEncodingConfig() *sdktestutil.TestEncodingConfig { 48 | return encodingConfig("wasm") 49 | } 50 | 51 | // EncodingConfig returns the global E2E encoding config. 52 | // It includes CosmosSDK, IBC, and Wasm messages 53 | func encodingConfig(bech32Prefix string) *sdktestutil.TestEncodingConfig { 54 | amino := codec.NewLegacyAmino() 55 | interfaceRegistry, err := codectypes.NewInterfaceRegistryWithOptions(codectypes.InterfaceRegistryOptions{ 56 | ProtoFiles: proto.HybridResolver, 57 | SigningOptions: txsigning.Options{ 58 | AddressCodec: address.Bech32Codec{ 59 | Bech32Prefix: bech32Prefix, 60 | }, 61 | ValidatorAddressCodec: address.Bech32Codec{ 62 | Bech32Prefix: bech32Prefix + sdk.PrefixValidator + sdk.PrefixOperator, 63 | }, 64 | }, 65 | }) 66 | if err != nil { 67 | panic(err) 68 | } 69 | 70 | // ibc types 71 | ibcwasmtypes.RegisterInterfaces(interfaceRegistry) 72 | icacontrollertypes.RegisterInterfaces(interfaceRegistry) 73 | icahosttypes.RegisterInterfaces(interfaceRegistry) 74 | feetypes.RegisterInterfaces(interfaceRegistry) 75 | transfertypes.RegisterInterfaces(interfaceRegistry) 76 | v7migrations.RegisterInterfaces(interfaceRegistry) 77 | clienttypes.RegisterInterfaces(interfaceRegistry) 78 | connectiontypes.RegisterInterfaces(interfaceRegistry) 79 | channeltypes.RegisterInterfaces(interfaceRegistry) 80 | solomachine.RegisterInterfaces(interfaceRegistry) 81 | ibctmtypes.RegisterInterfaces(interfaceRegistry) 82 | localhost.RegisterInterfaces(interfaceRegistry) 83 | 84 | // sdk types 85 | upgradetypes.RegisterInterfaces(interfaceRegistry) 86 | banktypes.RegisterInterfaces(interfaceRegistry) 87 | govv1beta1.RegisterInterfaces(interfaceRegistry) 88 | govv1.RegisterInterfaces(interfaceRegistry) 89 | authtypes.RegisterInterfaces(interfaceRegistry) 90 | cryptocodec.RegisterInterfaces(interfaceRegistry) 91 | grouptypes.RegisterInterfaces(interfaceRegistry) 92 | proposaltypes.RegisterInterfaces(interfaceRegistry) 93 | authz.RegisterInterfaces(interfaceRegistry) 94 | txtypes.RegisterInterfaces(interfaceRegistry) 95 | stakingtypes.RegisterInterfaces(interfaceRegistry) 96 | minttypes.RegisterInterfaces(interfaceRegistry) 97 | distrtypes.RegisterInterfaces(interfaceRegistry) 98 | slashingtypes.RegisterInterfaces(interfaceRegistry) 99 | consensustypes.RegisterInterfaces(interfaceRegistry) 100 | 101 | // custom module types 102 | wasmtypes.RegisterInterfaces(interfaceRegistry) 103 | 104 | cdc := codec.NewProtoCodec(interfaceRegistry) 105 | 106 | cfg := &sdktestutil.TestEncodingConfig{ 107 | InterfaceRegistry: interfaceRegistry, 108 | Codec: cdc, 109 | TxConfig: authtx.NewTxConfig(cdc, authtx.DefaultSignModes), 110 | Amino: amino, 111 | } 112 | 113 | return cfg 114 | } 115 | -------------------------------------------------------------------------------- /templates/interchaintestv8/chainconfig/files/chainconfig/genesis.go.plush: -------------------------------------------------------------------------------- 1 | package chainconfig 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" 10 | govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" 11 | govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" 12 | 13 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 14 | 15 | "<%= ModulePath %>/testvalues" 16 | ) 17 | 18 | func defaultModifyGenesis() func(ibc.ChainConfig, []byte) ([]byte, error) { 19 | return func(chainConfig ibc.ChainConfig, genBz []byte) ([]byte, error) { 20 | appGenesis, err := genutiltypes.AppGenesisFromReader(bytes.NewReader(genBz)) 21 | if err != nil { 22 | return nil, fmt.Errorf("failed to unmarshal genesis bytes: %w", err) 23 | } 24 | 25 | var appState genutiltypes.AppMap 26 | if err := json.Unmarshal(appGenesis.AppState, &appState); err != nil { 27 | return nil, fmt.Errorf("failed to unmarshal app state: %w", err) 28 | } 29 | 30 | // modify the gov v1 app state 31 | govGenBz, err := modifyGovV1AppState(chainConfig, appState[govtypes.ModuleName]) 32 | if err != nil { 33 | return nil, fmt.Errorf("failed to modify gov v1 app state: %w", err) 34 | } 35 | 36 | appState[govtypes.ModuleName] = govGenBz 37 | 38 | // marshal the app state 39 | appGenesis.AppState, err = json.Marshal(appState) 40 | if err != nil { 41 | return nil, fmt.Errorf("failed to marshal app state: %w", err) 42 | } 43 | 44 | res, err := json.MarshalIndent(appGenesis, "", " ") 45 | if err != nil { 46 | return nil, fmt.Errorf("failed to marshal app genesis: %w", err) 47 | } 48 | 49 | return res, nil 50 | } 51 | } 52 | 53 | // modifyGovV1AppState takes the existing gov app state and marshals it to a govv1 GenesisState. 54 | func modifyGovV1AppState(chainConfig ibc.ChainConfig, govAppState []byte) ([]byte, error) { 55 | cdc := WasmEncodingConfig().Codec 56 | 57 | govGenesisState := &govv1.GenesisState{} 58 | if err := cdc.UnmarshalJSON(govAppState, govGenesisState); err != nil { 59 | return nil, fmt.Errorf("failed to unmarshal genesis bytes into gov genesis state: %w", err) 60 | } 61 | 62 | if govGenesisState.Params == nil { 63 | govGenesisState.Params = &govv1.Params{} 64 | } 65 | 66 | govGenesisState.Params.MinDeposit = sdk.NewCoins(sdk.NewCoin(chainConfig.Denom, govv1.DefaultMinDepositTokens)) 67 | govGenesisState.Params.MaxDepositPeriod = &testvalues.MaxDepositPeriod 68 | govGenesisState.Params.VotingPeriod = &testvalues.VotingPeriod 69 | 70 | // govGenBz := MustProtoMarshalJSON(govGenesisState) 71 | 72 | govGenBz, err := cdc.MarshalJSON(govGenesisState) 73 | if err != nil { 74 | return nil, fmt.Errorf("failed to marshal gov genesis state: %w", err) 75 | } 76 | 77 | return govGenBz, nil 78 | } 79 | -------------------------------------------------------------------------------- /templates/interchaintestv8/chainconfig/options.go: -------------------------------------------------------------------------------- 1 | package chainconfig 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/gobuffalo/plush/v4" 7 | "golang.org/x/mod/module" 8 | ) 9 | 10 | type Options struct { 11 | ModulePath string 12 | ChainNum uint8 13 | } 14 | 15 | // Validate that options are usable. 16 | func (opts *Options) Validate() error { 17 | err := module.CheckPath(opts.ModulePath) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | if opts.ChainNum == 0 { 23 | return errors.New("ChainNum must be greater than 0") 24 | } 25 | return nil 26 | } 27 | 28 | func (opts *Options) plushContext() *plush.Context { 29 | ctx := plush.NewContext() 30 | ctx.Set("ModulePath", opts.ModulePath) 31 | ctx.Set("ChainNum", int(opts.ChainNum)) 32 | ctx.Set("between", betweenHelper) 33 | return ctx 34 | } 35 | -------------------------------------------------------------------------------- /templates/interchaintestv8/chainconfig/utils.go: -------------------------------------------------------------------------------- 1 | package chainconfig 2 | 3 | import "github.com/gobuffalo/plush/v4" 4 | 5 | type ranger struct { 6 | pos int 7 | end int 8 | } 9 | 10 | func (r *ranger) Next() interface{} { 11 | if r.pos < r.end { 12 | r.pos++ 13 | return r.pos 14 | } 15 | return nil 16 | } 17 | 18 | // returns a plush.Iterator that iterates from a (inclusive) to b (exclusive) 19 | func betweenHelper(a, b int) plush.Iterator { 20 | return &ranger{pos: a - 1, end: b - 1} 21 | } 22 | -------------------------------------------------------------------------------- /templates/interchaintestv8/e2esuite/embed.go: -------------------------------------------------------------------------------- 1 | package e2esuite 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "io/fs" 7 | 8 | "github.com/gobuffalo/genny/v2" 9 | 10 | "github.com/srdtrk/go-codegen/pkg/xgenny" 11 | ) 12 | 13 | //go:embed files/* files/**/* 14 | var files embed.FS 15 | 16 | func NewGenerator(opts *Options) (*genny.Generator, error) { 17 | if err := opts.Validate(); err != nil { 18 | return nil, fmt.Errorf("invalid options: %w", err) 19 | } 20 | 21 | // Remove "files/" prefix 22 | subfs, err := fs.Sub(files, "files") 23 | if err != nil { 24 | return nil, fmt.Errorf("generator sub: %w", err) 25 | } 26 | 27 | g := genny.New() 28 | 29 | var exclude []string 30 | if opts.ChainNum == 1 { 31 | exclude = append(exclude, "e2esuite/constants.go") 32 | } 33 | 34 | err = g.SelectiveFS(subfs, nil, nil, exclude, nil) 35 | if err != nil { 36 | return nil, fmt.Errorf("generator selective fs: %w", err) 37 | } 38 | 39 | g.Transformer(xgenny.Transformer(opts.plushContext())) 40 | 41 | return g, nil 42 | } 43 | -------------------------------------------------------------------------------- /templates/interchaintestv8/e2esuite/files/e2esuite/constants.go.plush: -------------------------------------------------------------------------------- 1 | package e2esuite 2 | 3 | const ( 4 | // hermesRelayerImage = "ghcr.io/informalsystems/hermes" 5 | // hermesRelayerTag = "v1.8.0" 6 | // hermesRelayerUidGid = "1000:1000" 7 | 8 | goRelayerImage = "ghcr.io/cosmos/relayer" 9 | goRelayerTag = "" 10 | goRelayerUidGid = "100:1000" 11 | ) 12 | -------------------------------------------------------------------------------- /templates/interchaintestv8/e2esuite/files/e2esuite/grpc_query.go.plush: -------------------------------------------------------------------------------- 1 | package e2esuite 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/cosmos/gogoproto/proto" 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/credentials/insecure" 10 | pb "google.golang.org/protobuf/proto" 11 | 12 | msgv1 "cosmossdk.io/api/cosmos/msg/v1" 13 | reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" 14 | 15 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 16 | ) 17 | 18 | var queryReqToPath = make(map[string]string) 19 | 20 | func populateQueryReqToPath(ctx context.Context, chain *cosmos.CosmosChain) error { 21 | resp, err := queryFileDescriptors(ctx, chain) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | for _, fileDescriptor := range resp.Files { 27 | for _, service := range fileDescriptor.GetService() { 28 | // Skip services that are annotated with the "cosmos.msg.v1.service" option. 29 | if ext := pb.GetExtension(service.GetOptions(), msgv1.E_Service); ext != nil && ext.(bool) { 30 | continue 31 | } 32 | 33 | for _, method := range service.GetMethod() { 34 | // trim the first character from input which is a dot 35 | queryReqToPath[method.GetInputType()[1:]] = fileDescriptor.GetPackage() + "." + service.GetName() + "/" + method.GetName() 36 | } 37 | } 38 | } 39 | 40 | return nil 41 | } 42 | 43 | // Queries the chain with a query request and deserializes the response to T 44 | func GRPCQuery[T any](ctx context.Context, chain *cosmos.CosmosChain, req proto.Message, opts ...grpc.CallOption) (*T, error) { 45 | path, ok := queryReqToPath[proto.MessageName(req)] 46 | if !ok { 47 | return nil, fmt.Errorf("no path found for %s", proto.MessageName(req)) 48 | } 49 | 50 | // Create a connection to the gRPC server. 51 | grpcConn, err := grpc.Dial( 52 | chain.GetHostGRPCAddress(), 53 | grpc.WithTransportCredentials(insecure.NewCredentials()), 54 | ) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | defer grpcConn.Close() 60 | 61 | resp := new(T) 62 | err = grpcConn.Invoke(ctx, path, req, resp, opts...) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | return resp, nil 68 | } 69 | 70 | func queryFileDescriptors(ctx context.Context, chain *cosmos.CosmosChain) (*reflectionv1.FileDescriptorsResponse, error) { 71 | // Create a connection to the gRPC server. 72 | grpcConn, err := grpc.Dial( 73 | chain.GetHostGRPCAddress(), 74 | grpc.WithTransportCredentials(insecure.NewCredentials()), 75 | ) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | defer grpcConn.Close() 81 | 82 | resp := new(reflectionv1.FileDescriptorsResponse) 83 | err = grpcConn.Invoke( 84 | ctx, reflectionv1.ReflectionService_FileDescriptors_FullMethodName, 85 | &reflectionv1.FileDescriptorsRequest{}, resp, 86 | ) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | return resp, nil 92 | } 93 | -------------------------------------------------------------------------------- /templates/interchaintestv8/e2esuite/files/e2esuite/suite.go.plush: -------------------------------------------------------------------------------- 1 | package e2esuite 2 | 3 | import ( 4 | "context" 5 | 6 | dockerclient "github.com/docker/docker/client" 7 | "github.com/stretchr/testify/suite" 8 | "go.uber.org/zap" 9 | "go.uber.org/zap/zaptest" 10 | 11 | sdkmath "cosmossdk.io/math" 12 | <%= if (ChainNum > 1) { %> 13 | ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" 14 | 15 | interchaintest "github.com/strangelove-ventures/interchaintest/v8" 16 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 17 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 18 | "github.com/strangelove-ventures/interchaintest/v8/relayer" 19 | "github.com/strangelove-ventures/interchaintest/v8/testreporter" 20 | "github.com/strangelove-ventures/interchaintest/v8/testutil" 21 | <% } else { %> 22 | interchaintest "github.com/strangelove-ventures/interchaintest/v8" 23 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 24 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 25 | <% } %> 26 | "<%= ModulePath %>/chainconfig" 27 | "<%= ModulePath %>/testvalues" 28 | ) 29 | 30 | // TestSuite is a suite of tests that require two chains and a relayer 31 | type TestSuite struct { 32 | suite.Suite 33 | <%= if (ChainNum > 1) { %> 34 | <%= for (n) in between(0, ChainNum) { %>Chain<%= numToLetter(n) %> *cosmos.CosmosChain 35 | <% } %><%= for (n) in between(0, ChainNum) { %>User<%= numToLetter(n) %> ibc.Wallet 36 | <% } %>dockerClient *dockerclient.Client 37 | Relayer ibc.Relayer 38 | network string 39 | logger *zap.Logger 40 | ExecRep *testreporter.RelayerExecReporter 41 | // Name of the path from ChainA to ChainB 42 | PathName string 43 | <% } else { %> 44 | ChainA *cosmos.CosmosChain 45 | UserA ibc.Wallet 46 | dockerClient *dockerclient.Client 47 | network string 48 | logger *zap.Logger 49 | <% } %>} 50 | 51 | // SetupSuite sets up the chains, relayer, user accounts, clients, and connections 52 | func (s *TestSuite) SetupSuite(ctx context.Context) { 53 | chainSpecs := chainconfig.DefaultChainSpecs 54 | 55 | if len(chainSpecs) != <%= ChainNum %> { 56 | panic("TestSuite requires exactly <%= ChainNum %> chain specs") 57 | } 58 | 59 | t := s.T() 60 | 61 | s.logger = zaptest.NewLogger(t) 62 | s.dockerClient, s.network = interchaintest.DockerSetup(t) 63 | 64 | cf := interchaintest.NewBuiltinChainFactory(s.logger, chainSpecs) 65 | 66 | chains, err := cf.Chains(t.Name()) 67 | s.Require().NoError(err) 68 | <%= for (n) in between(0, ChainNum) { %> s.Chain<%= numToLetter(n) %> = chains[<%= n %>].(*cosmos.CosmosChain) 69 | <% } %> 70 | <%= if (ChainNum > 1) { %> // docker run -it --rm --entrypoint echo ghcr.io/cosmos/relayer "$(id -u):$(id -g)" 71 | goRelayerImage := relayer.CustomDockerImage(goRelayerImage, goRelayerTag, goRelayerUidGid) 72 | 73 | s.Relayer = interchaintest.NewBuiltinRelayerFactory( 74 | ibc.CosmosRly, 75 | zaptest.NewLogger(t), 76 | goRelayerImage, 77 | ).Build(t, s.dockerClient, s.network) 78 | 79 | s.ExecRep = testreporter.NewNopReporter().RelayerExecReporter(t) 80 | 81 | s.PathName = s.ChainA.Config().Name + "-" + s.ChainB.Config().Name 82 | ic := interchaintest.NewInterchain(). 83 | <%= for (n) in between(0, ChainNum) { %>AddChain(s.Chain<%= numToLetter(n) %>). 84 | <% } %>AddRelayer(s.Relayer, "relayer"). 85 | AddLink(interchaintest.InterchainLink{ 86 | Chain1: s.ChainA, 87 | Chain2: s.ChainB, 88 | Relayer: s.Relayer, 89 | Path: s.PathName, 90 | }) 91 | 92 | s.Require().NoError(ic.Build(ctx, s.ExecRep, interchaintest.InterchainBuildOptions{ 93 | TestName: t.Name(), 94 | Client: s.dockerClient, 95 | NetworkID: s.network, 96 | SkipPathCreation: true, 97 | })) 98 | <% } else { %> ic := interchaintest.NewInterchain().AddChain(s.ChainA) 99 | s.Require().NoError(ic.Build(ctx, nil, interchaintest.InterchainBuildOptions{ 100 | TestName: t.Name(), 101 | Client: s.dockerClient, 102 | NetworkID: s.network, 103 | SkipPathCreation: true, 104 | })) 105 | <% } %> 106 | // map all query request types to their gRPC method paths 107 | <%= for (n) in between(0, ChainNum) { %> s.Require().NoError(populateQueryReqToPath(ctx, s.Chain<%= numToLetter(n) %>)) 108 | <% } %> 109 | // Fund a user accounts 110 | userFunds := sdkmath.NewInt(testvalues.StartingTokenAmount) 111 | users := interchaintest.GetAndFundTestUsers(t, ctx, t.Name(), userFunds<%= for (n) in between(0, ChainNum) { return ", s.Chain" + numToLetter(n) } %>) 112 | <%= for (n) in between(0, ChainNum) { %> s.User<%= numToLetter(n) %> = users[<%= n %>] 113 | <% } %><%= if (ChainNum > 1) { %> 114 | // Generate a new IBC path 115 | err = s.Relayer.GeneratePath(ctx, s.ExecRep, s.ChainA.Config().ChainID, s.ChainB.Config().ChainID, s.PathName) 116 | s.Require().NoError(err) 117 | 118 | // Create new clients 119 | err = s.Relayer.CreateClients(ctx, s.ExecRep, s.PathName, ibc.CreateClientOptions{TrustingPeriod: "330h"}) 120 | s.Require().NoError(err) 121 | 122 | err = testutil.WaitForBlocks(ctx, 2, s.ChainA, s.ChainB) 123 | s.Require().NoError(err) 124 | 125 | // Create a new connection 126 | err = s.Relayer.CreateConnections(ctx, s.ExecRep, s.PathName) 127 | s.Require().NoError(err) 128 | 129 | err = testutil.WaitForBlocks(ctx, 2, s.ChainA, s.ChainB) 130 | s.Require().NoError(err) 131 | 132 | // Query for the newly created connection in wasmd 133 | connections, err := s.Relayer.GetConnections(ctx, s.ExecRep, s.ChainA.Config().ChainID) 134 | s.Require().NoError(err) 135 | // localhost is always a connection since ibc-go v7.1+ 136 | s.Require().Equal(2, len(connections)) 137 | wasmdConnection := connections[0] 138 | s.Require().NotEqual(ibcexported.LocalhostConnectionID, wasmdConnection.ID) 139 | 140 | // Query for the newly created connection in simd 141 | connections, err = s.Relayer.GetConnections(ctx, s.ExecRep, s.ChainB.Config().ChainID) 142 | s.Require().NoError(err) 143 | // localhost is always a connection since ibc-go v7.1+ 144 | s.Require().Equal(2, len(connections)) 145 | simdConnection := connections[0] 146 | s.Require().NotEqual(ibcexported.LocalhostConnectionID, simdConnection.ID) 147 | 148 | // Start the relayer and set the cleanup function. 149 | err = s.Relayer.StartRelayer(ctx, s.ExecRep, s.PathName) 150 | s.Require().NoError(err) 151 | 152 | t.Cleanup( 153 | func() { 154 | err := s.Relayer.StopRelayer(ctx, s.ExecRep) 155 | if err != nil { 156 | t.Logf("an error occurred while stopping the relayer: %s", err) 157 | } 158 | 159 | // Collect diagnostics 160 | chains := []string{chainSpecs[0].ChainConfig.Name<%= for (n) in between(1, ChainNum) { return ", chainSpecs[" + n + "].ChainConfig.Name" } %>} 161 | collect(t, s.dockerClient, false, chains...) 162 | }, 163 | ) 164 | <% } else { %> 165 | t.Cleanup( 166 | func() { 167 | // Collect diagnostics 168 | chains := []string{chainSpecs[0].ChainConfig.Name} 169 | collect(t, s.dockerClient, false, chains...) 170 | }, 171 | ) 172 | <% } %>} 173 | -------------------------------------------------------------------------------- /templates/interchaintestv8/e2esuite/files/e2esuite/utils.go.plush: -------------------------------------------------------------------------------- 1 | package e2esuite 2 | 3 | import ( 4 | "context" 5 | 6 | "cosmossdk.io/math" 7 | 8 | "github.com/cosmos/cosmos-sdk/client" 9 | "github.com/cosmos/cosmos-sdk/client/tx" 10 | sdk "github.com/cosmos/cosmos-sdk/types" 11 | 12 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 13 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 14 | "github.com/strangelove-ventures/interchaintest/v8/testutil" 15 | ) 16 | <%= for (n) in between(0, ChainNum) { %><% let letter = numToLetter(n) %> 17 | // FundAddressChain<%= letter %> sends funds to the given address on Chain <%= letter %>. 18 | // The amount sent is 1,000,000,000 of the chain's denom. 19 | func (s *TestSuite) FundAddressChain<%= letter %>(ctx context.Context, address string) { 20 | s.fundAddress(ctx, s.Chain<%= letter %>, s.User<%= letter %>.KeyName(), address) 21 | } 22 | <% } %> 23 | // BroadcastMessages broadcasts the provided messages to the given chain and signs them on behalf of the provided user. 24 | // Once the broadcast response is returned, we wait for two blocks to be created on chain. 25 | func (s *TestSuite) BroadcastMessages(ctx context.Context, chain *cosmos.CosmosChain, user ibc.Wallet, gas uint64, msgs ...sdk.Msg) (*sdk.TxResponse, error) { 26 | sdk.GetConfig().SetBech32PrefixForAccount(chain.Config().Bech32Prefix, chain.Config().Bech32Prefix+sdk.PrefixPublic) 27 | sdk.GetConfig().SetBech32PrefixForValidator( 28 | chain.Config().Bech32Prefix+sdk.PrefixValidator+sdk.PrefixOperator, 29 | chain.Config().Bech32Prefix+sdk.PrefixValidator+sdk.PrefixOperator+sdk.PrefixPublic, 30 | ) 31 | 32 | broadcaster := cosmos.NewBroadcaster(s.T(), chain) 33 | 34 | broadcaster.ConfigureClientContextOptions(func(clientContext client.Context) client.Context { 35 | return clientContext. 36 | WithCodec(chain.Config().EncodingConfig.Codec). 37 | WithChainID(chain.Config().ChainID). 38 | WithTxConfig(chain.Config().EncodingConfig.TxConfig) 39 | }) 40 | 41 | broadcaster.ConfigureFactoryOptions(func(factory tx.Factory) tx.Factory { 42 | return factory.WithGas(gas) 43 | }) 44 | 45 | resp, err := cosmos.BroadcastTx(ctx, broadcaster, user, msgs...) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | // wait for 2 blocks for the transaction to be included 51 | s.Require().NoError(testutil.WaitForBlocks(ctx, 2, chain)) 52 | 53 | return &resp, nil 54 | } 55 | 56 | // fundAddress sends funds to the given address on the given chain 57 | func (s *TestSuite) fundAddress(ctx context.Context, chain *cosmos.CosmosChain, keyName, address string) { 58 | err := chain.SendFunds(ctx, keyName, ibc.WalletAmount{ 59 | Address: address, 60 | Denom: chain.Config().Denom, 61 | Amount: math.NewInt(1_000_000_000), 62 | }) 63 | s.Require().NoError(err) 64 | 65 | // wait for 2 blocks for the funds to be received 66 | err = testutil.WaitForBlocks(ctx, 2, chain) 67 | s.Require().NoError(err) 68 | } 69 | -------------------------------------------------------------------------------- /templates/interchaintestv8/e2esuite/options.go: -------------------------------------------------------------------------------- 1 | package e2esuite 2 | 3 | import ( 4 | "errors" 5 | "path/filepath" 6 | 7 | "golang.org/x/mod/module" 8 | ) 9 | 10 | type Options struct { 11 | ModulePath string 12 | ChainNum uint8 13 | TestDir string 14 | } 15 | 16 | // Validate that options are usable. 17 | func (opts *Options) Validate() error { 18 | err := module.CheckPath(opts.ModulePath) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | if !filepath.IsLocal(opts.TestDir) { 24 | return errors.New("TestDir must be a local path") 25 | } 26 | 27 | if opts.ChainNum == 0 { 28 | return errors.New("ChainNum must be greater than 0") 29 | } 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /templates/interchaintestv8/e2esuite/utils.go: -------------------------------------------------------------------------------- 1 | package e2esuite 2 | 3 | import "github.com/gobuffalo/plush/v4" 4 | 5 | const numToLetter = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 6 | 7 | func (opts *Options) plushContext() *plush.Context { 8 | ctx := plush.NewContext() 9 | ctx.Set("ModulePath", opts.ModulePath) 10 | ctx.Set("ChainNum", int(opts.ChainNum)) 11 | ctx.Set("TestDir", opts.TestDir) 12 | ctx.Set("between", betweenHelper) 13 | ctx.Set("numToLetter", numToLetterHelper) 14 | return ctx 15 | } 16 | 17 | type ranger struct { 18 | pos int 19 | end int 20 | } 21 | 22 | func (r *ranger) Next() interface{} { 23 | if r.pos < r.end { 24 | r.pos++ 25 | return r.pos 26 | } 27 | return nil 28 | } 29 | 30 | // returns a plush.Iterator that iterates from a (inclusive) to b (exclusive) 31 | func betweenHelper(a, b int) plush.Iterator { 32 | return &ranger{pos: a - 1, end: b - 1} 33 | } 34 | 35 | func numToLetterHelper(num int) string { 36 | return string(numToLetter[num]) 37 | } 38 | -------------------------------------------------------------------------------- /templates/interchaintestv8/embed.go: -------------------------------------------------------------------------------- 1 | package interchaintestv8 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "io/fs" 7 | 8 | "github.com/gobuffalo/genny/v2" 9 | 10 | "github.com/srdtrk/go-codegen/pkg/xgenny" 11 | ) 12 | 13 | //go:embed files/* 14 | var files embed.FS 15 | 16 | func NewGenerator(opts *Options) (*genny.Generator, error) { 17 | if err := opts.Validate(); err != nil { 18 | return nil, fmt.Errorf("invalid options: %w", err) 19 | } 20 | 21 | // Remove "files/" prefix 22 | subfs, err := fs.Sub(files, "files") 23 | if err != nil { 24 | return nil, fmt.Errorf("generator sub: %w", err) 25 | } 26 | 27 | g := genny.New() 28 | 29 | excludeList := []string{"contract_test.go"} 30 | err = g.SelectiveFS(subfs, nil, nil, excludeList, nil) 31 | if err != nil { 32 | return nil, fmt.Errorf("generator selective fs: %w", err) 33 | } 34 | 35 | g.Transformer(xgenny.Transformer(opts.plushContext())) 36 | 37 | return g, nil 38 | } 39 | 40 | func NewGeneratorForContractTest(opts *ContractTestOptions) (*genny.Generator, error) { 41 | if err := opts.Validate(); err != nil { 42 | return nil, fmt.Errorf("invalid options: %w", err) 43 | } 44 | 45 | // Remove "files/" prefix 46 | subfs, err := fs.Sub(files, "files") 47 | if err != nil { 48 | return nil, fmt.Errorf("generator sub: %w", err) 49 | } 50 | 51 | g := genny.New() 52 | 53 | includeList := []string{"contract_test.go"} 54 | err = g.SelectiveFS(subfs, includeList, nil, nil, nil) 55 | if err != nil { 56 | return nil, fmt.Errorf("generator selective fs: %w", err) 57 | } 58 | 59 | g.Transformer(xgenny.Transformer(opts.plushContext())) 60 | 61 | return g, nil 62 | } 63 | -------------------------------------------------------------------------------- /templates/interchaintestv8/files/.gitignore.plush: -------------------------------------------------------------------------------- 1 | # Ignore diagnostics directory 2 | diagnostics/ 3 | -------------------------------------------------------------------------------- /templates/interchaintestv8/files/.golangci.yml.plush: -------------------------------------------------------------------------------- 1 | run: 2 | tests: true 3 | # # timeout for analysis, e.g. 30s, 5m, default is 1m 4 | timeout: 5m 5 | 6 | linters: 7 | disable-all: true 8 | enable: 9 | - dogsled 10 | - exportloopref 11 | - errcheck 12 | - gci 13 | - goconst 14 | - gocritic 15 | - gofumpt 16 | - gosec 17 | - gosimple 18 | - govet 19 | - ineffassign 20 | - misspell 21 | - nakedret 22 | - staticcheck 23 | - thelper 24 | - stylecheck 25 | - revive 26 | - typecheck 27 | - unconvert 28 | - unused 29 | - misspell 30 | 31 | issues: 32 | exclude-rules: 33 | - text: "unused-parameter" 34 | linters: 35 | - revive 36 | - text: "SA1019:" 37 | linters: 38 | - staticcheck 39 | - text: "Use of weak random number generator" 40 | linters: 41 | - gosec 42 | - text: "ST1003:" 43 | linters: 44 | - stylecheck 45 | # FIXME: Disabled until golangci-lint updates stylecheck with this fix: 46 | # https://github.com/dominikh/go-tools/issues/389 47 | - text: "ST1016:" 48 | linters: 49 | - stylecheck 50 | max-issues-per-linter: 10000 51 | max-same-issues: 10000 52 | 53 | linters-settings: 54 | gci: 55 | sections: 56 | - standard # Standard section: captures all standard packages. 57 | - default # Default section: contains all imports that could not be matched to another section type. 58 | - blank # blank imports 59 | - dot # dot imports 60 | - prefix(cosmossdk.io) 61 | - prefix(github.com/cosmos/cosmos-sdk) 62 | - prefix(github.com/cometbft/cometbft) 63 | - prefix(github.com/cosmos/ibc-go) 64 | - prefix(github.com/CosmWasm/wasmd) 65 | - prefix(github.com/strangelove-ventures/interchaintest) 66 | - prefix(<%= ModulePath %>) 67 | 68 | custom-order: true 69 | dogsled: 70 | max-blank-identifiers: 3 71 | maligned: 72 | # print struct with more effective memory layout or not, false by default 73 | suggest-new: true 74 | nolintlint: 75 | allow-unused: false 76 | allow-leading-space: true 77 | require-explanation: false 78 | require-specific: false 79 | revive: 80 | rules: 81 | - name: if-return 82 | disabled: true 83 | -------------------------------------------------------------------------------- /templates/interchaintestv8/files/README.md.plush: -------------------------------------------------------------------------------- 1 | # End to End Testing Suite with Interchaintest 2 | 3 | The e2e tests are built using the [interchaintest](https://github.com/strangelove-ventures/interchaintest) library by Strangelove. It runs multiple docker container validators, and lets you test IBC enabled smart contracts. 4 | 5 | These end to end tests are designed to run in the ci, but you can also run them locally. 6 | 7 | ## Running the tests locally 8 | 9 | To run the tests locally, run the following commands from this directory: 10 | 11 | ```text 12 | go test -v . -run=$TEST_SUITE_FN/$TEST_NAME 13 | ``` 14 | 15 | where `$TEST_NAME` is one of the test names of the `$TEST_SUITE_FN`. For example, to run the `TestBasic` test, you would run: 16 | 17 | ```text 18 | go test -v . -run=TestWithBasicTestSuite/TestBasic 19 | ``` 20 | -------------------------------------------------------------------------------- /templates/interchaintestv8/files/basic_test.go.plush: -------------------------------------------------------------------------------- 1 | package main 2 | <%= if (ChainNum > 1) { %> 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/suite" 10 | 11 | sdk "github.com/cosmos/cosmos-sdk/types" 12 | banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" 13 | 14 | transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" 15 | channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" 16 | 17 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 18 | "github.com/strangelove-ventures/interchaintest/v8/testutil" 19 | 20 | "<%= ModulePath %>/e2esuite" 21 | ) 22 | <% } else { %> 23 | import ( 24 | "context" 25 | "testing" 26 | 27 | "github.com/stretchr/testify/suite" 28 | 29 | sdk "github.com/cosmos/cosmos-sdk/types" 30 | banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" 31 | 32 | interchaintest "github.com/strangelove-ventures/interchaintest/v8" 33 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 34 | 35 | "<%= ModulePath %>/e2esuite" 36 | ) 37 | <% } %> 38 | // BasicTestSuite is a suite of tests that wraps the TestSuite 39 | // and can provide additional functionality 40 | type BasicTestSuite struct { 41 | e2esuite.TestSuite 42 | } 43 | 44 | // SetupSuite calls the underlying BasicTestSuite's SetupSuite method 45 | func (s *BasicTestSuite) SetupSuite(ctx context.Context) { 46 | s.TestSuite.SetupSuite(ctx) 47 | } 48 | 49 | // TestWithBasicTestSuite is the boilerplate code that allows the test suite to be run 50 | func TestWithBasicTestSuite(t *testing.T) { 51 | suite.Run(t, new(BasicTestSuite)) 52 | } 53 | 54 | // TestBasic is an example test function that will be run by the test suite 55 | func (s *BasicTestSuite) TestBasic() { 56 | ctx := context.Background() 57 | 58 | s.SetupSuite(ctx) 59 | <%= if (ChainNum > 1) { %> 60 | wasmd1, wasmd2 := s.ChainA, s.ChainB 61 | 62 | // Add your test code here. For example, create a transfer channel between ChainA and ChainB: 63 | s.Run("CreateTransferChannel", func() { 64 | err := s.Relayer.CreateChannel(ctx, s.ExecRep, s.PathName, ibc.DefaultChannelOpts()) 65 | s.Require().NoError(err) 66 | 67 | // Wait for the channel to be created 68 | err = testutil.WaitForBlocks(ctx, 5, s.ChainA, s.ChainB) 69 | s.Require().NoError(err) 70 | }) 71 | 72 | // Test if the handshake was successful 73 | var ( 74 | wasmd1Channel ibc.ChannelOutput 75 | wasmd2Channel ibc.ChannelOutput 76 | ) 77 | s.Run("VerifyTransferChannel", func() { 78 | wasmd1Channels, err := s.Relayer.GetChannels(ctx, s.ExecRep, wasmd1.Config().ChainID) 79 | s.Require().NoError(err) 80 | s.Require().Equal(1, len(wasmd1Channels)) 81 | 82 | wasmd1Channel = wasmd1Channels[0] 83 | s.Require().Equal(transfertypes.PortID, wasmd1Channel.PortID) 84 | s.Require().Equal(transfertypes.PortID, wasmd1Channel.Counterparty.PortID) 85 | s.Require().Equal(transfertypes.Version, wasmd1Channel.Version) 86 | s.Require().Equal(channeltypes.OPEN.String(), wasmd1Channel.State) 87 | s.Require().Equal(channeltypes.UNORDERED.String(), wasmd1Channel.Ordering) 88 | 89 | wasmd2Channels, err := s.Relayer.GetChannels(ctx, s.ExecRep, wasmd2.Config().ChainID) 90 | s.Require().NoError(err) 91 | /* 92 | The relayer in the test suite sometimes submits multiple `ChannelOpenTry` messages, 93 | since there is no replay protection for `ChannelOpenTry` messages, there may be 94 | multiple channels in the state of the counterparty chain. However, only one of them, 95 | can reach the `OPEN` state since the other will be stuck in the `TRYOPEN` state. 96 | */ 97 | s.Require().GreaterOrEqual(len(wasmd2Channels), 1) 98 | 99 | wasmd2Channel = wasmd2Channels[0] 100 | s.Require().Equal(transfertypes.PortID, wasmd2Channel.PortID) 101 | s.Require().Equal(transfertypes.PortID, wasmd2Channel.Counterparty.PortID) 102 | s.Require().Equal(transfertypes.Version, wasmd2Channel.Version) 103 | s.Require().Equal(channeltypes.OPEN.String(), wasmd2Channel.State) 104 | s.Require().Equal(channeltypes.UNORDERED.String(), wasmd2Channel.Ordering) 105 | }) 106 | 107 | // Transfer tokens from UserA on ChainA to UserB on ChainB 108 | s.Run("TransferTokens", func() { 109 | /* 110 | Transfer funds to s.UserB on ChainB from s.UserA on ChainA. 111 | 112 | I am using the broadcaster to transfer funds to UserB to demonstrate how to use the 113 | broadcaster, but you can also use the SendIBCTransfer method from the s.ChainA instance. 114 | 115 | I used 200_000 gas to transfer the funds, but you can use any amount of gas you want. 116 | */ 117 | _, err := s.BroadcastMessages(ctx, wasmd1, s.UserA, 200_000, &transfertypes.MsgTransfer{ 118 | SourcePort: transfertypes.PortID, 119 | SourceChannel: wasmd1Channel.ChannelID, 120 | Token: sdk.NewInt64Coin(wasmd1.Config().Denom, 100_000), 121 | Sender: s.UserA.FormattedAddress(), 122 | Receiver: s.UserB.FormattedAddress(), 123 | TimeoutTimestamp: uint64(time.Now().Add(10 * time.Minute).UnixNano()), 124 | }) 125 | s.Require().NoError(err) 126 | 127 | s.Require().NoError(testutil.WaitForBlocks(ctx, 5, wasmd1, wasmd2)) // Wait 5 blocks for the packet to be relayed 128 | }) 129 | 130 | // Verify that the tokens were transferred 131 | s.Run("VerifyTokensTransferred", func() { 132 | chainBIBCDenom := transfertypes.ParseDenomTrace( 133 | fmt.Sprintf("%s/%s/%s", wasmd1Channel.PortID, wasmd1Channel.ChannelID, wasmd1.Config().Denom), 134 | ).IBCDenom() 135 | 136 | /* 137 | Query UserB's balance 138 | 139 | I am using the GRPCQuery to query the new user's balance to demonstrate how to use the GRPCQuery, 140 | but you can also use the GetBalance method from the s.ChainB instance. 141 | */ 142 | balanceResp, err := e2esuite.GRPCQuery[banktypes.QueryBalanceResponse](ctx, wasmd2, &banktypes.QueryBalanceRequest{ 143 | Address: s.UserB.FormattedAddress(), 144 | Denom: chainBIBCDenom, 145 | }) 146 | s.Require().NoError(err) 147 | s.Require().NotNil(balanceResp.Balance) 148 | 149 | // Verify the balance 150 | s.Require().Equal(int64(100_000), balanceResp.Balance.Amount.Int64()) 151 | }) 152 | <% } else { %> 153 | wasmd1 := s.ChainA 154 | 155 | // Add your test code here. For example, create a new wallet and fund it from the UserA account 156 | var newUser ibc.Wallet 157 | s.Run("CreateANewAccountAndSend", func() { 158 | var err error 159 | newUser, err = interchaintest.GetAndFundTestUserWithMnemonic(ctx, s.T().Name(), "", 1, wasmd1) 160 | s.Require().NoError(err) 161 | 162 | /* 163 | Send funds to the new user 164 | 165 | I am using the broadcaster to send funds to the new user to demonstrate how to use the broadcaster, 166 | but you can also use the s.FundAddressChainA method to send funds to the new user, or 167 | use the SendFunds method from the s.ChainA instance. 168 | 169 | I used 200_000 gas to send the funds, but you can use any amount of gas you want. 170 | */ 171 | _, err = s.BroadcastMessages(ctx, wasmd1, s.UserA, 200_000, &banktypes.MsgSend{ 172 | FromAddress: s.UserA.FormattedAddress(), 173 | ToAddress: newUser.FormattedAddress(), 174 | Amount: sdk.NewCoins(sdk.NewInt64Coin(wasmd1.Config().Denom, 100_000)), 175 | }) 176 | s.Require().NoError(err) 177 | }) 178 | 179 | // Test if the send was successful 180 | s.Run("VerifySendMessage", func() { 181 | /* 182 | Query the new user's balance 183 | 184 | I am using the GRPCQuery to query the new user's balance to demonstrate how to use the GRPCQuery, 185 | but you can also use the GetBalance method from the s.ChainA instance. 186 | */ 187 | balanceResp, err := e2esuite.GRPCQuery[banktypes.QueryBalanceResponse](ctx, wasmd1, &banktypes.QueryBalanceRequest{ 188 | Address: newUser.FormattedAddress(), 189 | Denom: wasmd1.Config().Denom, 190 | }) 191 | s.Require().NoError(err) 192 | s.Require().NotNil(balanceResp.Balance) 193 | 194 | // Verify the balance 195 | s.Require().Equal(int64(100_001), balanceResp.Balance.Amount.Int64()) 196 | }) 197 | <% } %>} 198 | -------------------------------------------------------------------------------- /templates/interchaintestv8/files/contract_test.go.plush: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/suite" 8 | 9 | "<%= ModulePath %>/e2esuite" 10 | "<%= ModulePath %>/<%= RelPackageDir %>" 11 | ) 12 | 13 | // ContactTestSuite is a suite of tests that wraps the TestSuite 14 | // and can provide additional functionality 15 | type ContractTestSuite struct { 16 | e2esuite.TestSuite 17 | 18 | // this line is used by go-codegen # suite/contract 19 | } 20 | 21 | // SetupSuite calls the underlying ContractTestSuite's SetupSuite method 22 | func (s *ContractTestSuite) SetupSuite(ctx context.Context) { 23 | s.TestSuite.SetupSuite(ctx) 24 | } 25 | 26 | // TestWithContractTestSuite is the boilerplate code that allows the test suite to be run 27 | func TestWithContractTestSuite(t *testing.T) { 28 | suite.Run(t, new(ContractTestSuite)) 29 | } 30 | 31 | // TestContract is an example test function that will be run by the test suite 32 | func (s *ContractTestSuite) TestContract() { 33 | ctx := context.Background() 34 | 35 | s.SetupSuite(ctx) 36 | 37 | wasmd1 := s.ChainA 38 | 39 | // Add your test code here. For example, upload and instantiate a contract: 40 | // This boilerplate may be moved to SetupSuite if it is common to all tests. 41 | var contract *<%= PackageName %>.Contract 42 | s.Run("UploadAndInstantiateContract", func() { 43 | // Upload the contract code to the chain. 44 | codeID, err := wasmd1.StoreContract(ctx, s.UserA.KeyName(), "./relative/path/to/your_contract.wasm") 45 | s.Require().NoError(err) 46 | 47 | // Instantiate the contract using contract helpers. 48 | // This will an error if the instantiate message is invalid. 49 | contract, err = <%= PackageName %>.Instantiate(ctx, s.UserA.KeyName(), codeID, "", wasmd1, <%= PackageName %>.InstantiateMsg{}) 50 | s.Require().NoError(err) 51 | 52 | s.Require().NotEmpty(contract.Address) 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /templates/interchaintestv8/github/embed.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "io/fs" 7 | 8 | "github.com/gobuffalo/genny/v2" 9 | "github.com/gobuffalo/plush/v4" 10 | 11 | "github.com/srdtrk/go-codegen/pkg/xgenny" 12 | ) 13 | 14 | //go:embed files/* 15 | var files embed.FS 16 | 17 | func NewGenerator(opts *Options) (*genny.Generator, error) { 18 | if err := opts.Validate(); err != nil { 19 | return nil, fmt.Errorf("invalid options: %w", err) 20 | } 21 | 22 | // Remove "files/" prefix 23 | subfs, err := fs.Sub(files, "files") 24 | if err != nil { 25 | return nil, fmt.Errorf("generator sub: %w", err) 26 | } 27 | 28 | g := genny.New() 29 | 30 | err = g.FS(subfs) 31 | if err != nil { 32 | return nil, fmt.Errorf("generator selective fs: %w", err) 33 | } 34 | 35 | ctx := plush.NewContext() 36 | ctx.Set("TestDir", opts.TestDir) 37 | 38 | g.Transformer(xgenny.Transformer(ctx)) 39 | 40 | return g, nil 41 | } 42 | -------------------------------------------------------------------------------- /templates/interchaintestv8/github/files/e2e.yml.plush: -------------------------------------------------------------------------------- 1 | name: e2e 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | paths: 7 | - '**.rs' 8 | - '**.go' 9 | - '**.toml' 10 | - '**.lock' 11 | - '**.mod' 12 | - '**.sum' 13 | - '.github/workflows/e2e.yml' 14 | permissions: 15 | contents: read 16 | jobs: 17 | golangci: 18 | name: lint 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/setup-go@v4 22 | with: 23 | go-version: "1.22" 24 | - uses: actions/checkout@v3 25 | - name: golangci-lint 26 | uses: golangci/golangci-lint-action@v3.7.0 27 | with: 28 | version: v1.59 29 | args: --timeout 5m 30 | working-directory: <%= TestDir %> 31 | build: 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | test: 36 | # List your tests here 37 | - TestWithBasicTestSuite/TestBasic 38 | name: ${{ matrix.test }} 39 | runs-on: ubuntu-latest 40 | steps: 41 | - name: Checkout sources 42 | uses: actions/checkout@v3 43 | # You can build your contract here, you can either use docker or a custom cargo script: 44 | # We've provided examples for both below: 45 | # 46 | # - name: Build Contracts with Docker 47 | # run: | 48 | # docker run --rm -v "$(pwd)":/code \ 49 | # --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 50 | # --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 51 | # cosmwasm/optimizer:0.15.1 52 | # - name: Install cargo-run-script 53 | # uses: actions-rs/cargo@v1 54 | # with: 55 | # command: install 56 | # args: cargo-run-script 57 | # - name: Build Optimized Contract 58 | # uses: actions-rs/cargo@v1 59 | # with: 60 | # command: run-script 61 | # args: optimize 62 | - name: Setup Go 63 | uses: actions/setup-go@v4 64 | with: 65 | go-version: "1.22" 66 | check-latest: true 67 | cache-dependency-path: | 68 | <%= TestDir %>/go.sum 69 | - name: Run Tests 70 | run: | 71 | cd <%= TestDir %> 72 | go test -v -mod=readonly . -run=${{ matrix.test }} 73 | - name: Upload Diagnostics on Failure 74 | uses: actions/upload-artifact@v4 75 | if: ${{ failure() }} 76 | continue-on-error: true 77 | with: 78 | path: <%= TestDir %>/diagnostics 79 | retention-days: 5 80 | -------------------------------------------------------------------------------- /templates/interchaintestv8/github/options.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "errors" 5 | "path/filepath" 6 | ) 7 | 8 | type Options struct { 9 | TestDir string 10 | } 11 | 12 | // Validate that options are usable. 13 | func (opts *Options) Validate() error { 14 | if !filepath.IsLocal(opts.TestDir) { 15 | return errors.New("TestDir must be a local path") 16 | } 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /templates/interchaintestv8/options.go: -------------------------------------------------------------------------------- 1 | package interchaintestv8 2 | 3 | import ( 4 | "errors" 5 | "path/filepath" 6 | 7 | "github.com/gobuffalo/plush/v4" 8 | "golang.org/x/mod/module" 9 | ) 10 | 11 | type Options struct { 12 | ModulePath string 13 | Github bool 14 | ChainNum uint8 15 | } 16 | 17 | type ContractTestOptions struct { 18 | ModulePath string 19 | RelPackageDir string 20 | PackageName string 21 | } 22 | 23 | // Validate that Options is usable. 24 | func (opts *Options) Validate() error { 25 | err := module.CheckPath(opts.ModulePath) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | if opts.ChainNum == 0 { 31 | return errors.New("ChainNum must be greater than 0") 32 | } 33 | 34 | return nil 35 | } 36 | 37 | // Validate that ContractTestOptions is usable. 38 | func (opts *ContractTestOptions) Validate() error { 39 | err := module.CheckPath(opts.ModulePath) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | if !filepath.IsLocal(opts.RelPackageDir) { 45 | return errors.New("RelPackageDir must be a local path") 46 | } 47 | 48 | return nil 49 | } 50 | 51 | // plushContext returns a plush context from Options. 52 | func (opts *Options) plushContext() *plush.Context { 53 | ctx := plush.NewContext() 54 | ctx.Set("ModulePath", opts.ModulePath) 55 | ctx.Set("ChainNum", int(opts.ChainNum)) 56 | return ctx 57 | } 58 | 59 | // plushContext returns a plush context from ContractTestOptions. 60 | func (opts *ContractTestOptions) plushContext() *plush.Context { 61 | ctx := plush.NewContext() 62 | ctx.Set("ModulePath", opts.ModulePath) 63 | ctx.Set("RelPackageDir", opts.RelPackageDir) 64 | ctx.Set("PackageName", opts.PackageName) 65 | return ctx 66 | } 67 | -------------------------------------------------------------------------------- /templates/interchaintestv8/placeholders.go: -------------------------------------------------------------------------------- 1 | package interchaintestv8 2 | 3 | const ( 4 | PlaceholderSuiteModule = "// this line is used by go-codegen # suite/module" 5 | PlaceholderContractSuite = "// this line is used by go-codegen # suite/contract" 6 | ) 7 | -------------------------------------------------------------------------------- /templates/interchaintestv8/testvalues/embed.go: -------------------------------------------------------------------------------- 1 | package testvalues 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "io/fs" 7 | 8 | "github.com/gobuffalo/genny/v2" 9 | 10 | "github.com/srdtrk/go-codegen/pkg/xgenny" 11 | ) 12 | 13 | //go:embed files/* files/**/* 14 | var files embed.FS 15 | 16 | func NewGenerator(opts *Options) (*genny.Generator, error) { 17 | if err := opts.Validate(); err != nil { 18 | return nil, fmt.Errorf("invalid options: %w", err) 19 | } 20 | 21 | // Remove "files/" prefix 22 | subfs, err := fs.Sub(files, "files") 23 | if err != nil { 24 | return nil, fmt.Errorf("generator sub: %w", err) 25 | } 26 | 27 | g := genny.New() 28 | 29 | err = g.SelectiveFS(subfs, nil, nil, nil, nil) 30 | if err != nil { 31 | return nil, fmt.Errorf("generator selective fs: %w", err) 32 | } 33 | 34 | g.Transformer(xgenny.Transformer(opts.plushContext())) 35 | 36 | return g, nil 37 | } 38 | -------------------------------------------------------------------------------- /templates/interchaintestv8/testvalues/files/testvalues/values.go.plush: -------------------------------------------------------------------------------- 1 | package testvalues 2 | 3 | import "time" 4 | 5 | const ( 6 | // StartingTokenAmount is the amount of tokens to give to each user at the start of the test. 7 | StartingTokenAmount int64 = 10_000_000_000 8 | ) 9 | 10 | var ( 11 | // Maximum period to deposit on a proposal. 12 | // This value overrides the default value in the gov module using the `modifyGovV1AppState` function. 13 | MaxDepositPeriod = time.Second * 10 14 | // Duration of the voting period. 15 | // This value overrides the default value in the gov module using the `modifyGovV1AppState` function. 16 | VotingPeriod = time.Second * 30 17 | ) 18 | -------------------------------------------------------------------------------- /templates/interchaintestv8/testvalues/options.go: -------------------------------------------------------------------------------- 1 | package testvalues 2 | 3 | import "github.com/gobuffalo/plush/v4" 4 | 5 | type Options struct{} 6 | 7 | // Validate that options are usable. 8 | func (*Options) Validate() error { 9 | return nil 10 | } 11 | 12 | func (*Options) plushContext() *plush.Context { 13 | ctx := plush.NewContext() 14 | return ctx 15 | } 16 | -------------------------------------------------------------------------------- /templates/interchaintestv8/types/embed.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "io/fs" 7 | 8 | "github.com/gobuffalo/genny/v2" 9 | "github.com/gobuffalo/plush/v4" 10 | 11 | "github.com/srdtrk/go-codegen/pkg/xgenny" 12 | ) 13 | 14 | var ( 15 | //go:embed files/**/*.go.plush 16 | files embed.FS 17 | 18 | //nolint:unused 19 | //go:embed files/types/{{contractName}}/* 20 | contractFiles embed.FS 21 | ) 22 | 23 | func NewGenerator() (*genny.Generator, error) { 24 | // Remove "files/" prefix 25 | subfs, err := fs.Sub(files, "files") 26 | if err != nil { 27 | return nil, fmt.Errorf("generator sub: %w", err) 28 | } 29 | 30 | g := genny.New() 31 | 32 | err = g.SelectiveFS(subfs, nil, nil, nil, nil) 33 | if err != nil { 34 | return nil, fmt.Errorf("generator selective fs: %w", err) 35 | } 36 | 37 | g.Transformer(xgenny.Transformer(plush.NewContext())) 38 | 39 | return g, nil 40 | } 41 | 42 | func NewContractGenerator(opts *AddContractOptions) (*genny.Generator, error) { 43 | if err := opts.Validate(); err != nil { 44 | return nil, fmt.Errorf("invalid options: %w", err) 45 | } 46 | 47 | // Remove "files/types" prefix 48 | subfs, err := fs.Sub(contractFiles, "files/types") 49 | if err != nil { 50 | return nil, fmt.Errorf("generator sub: %w", err) 51 | } 52 | 53 | g := genny.New() 54 | 55 | err = g.SelectiveFS(subfs, nil, nil, nil, nil) 56 | if err != nil { 57 | return nil, fmt.Errorf("generator selective fs: %w", err) 58 | } 59 | 60 | g.Transformer(xgenny.Transformer(opts.plushContext())) 61 | g.Transformer(genny.Replace("{{contractName}}", opts.PackageName)) 62 | 63 | return g, nil 64 | } 65 | -------------------------------------------------------------------------------- /templates/interchaintestv8/types/files/types/contract.go.plush: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | 9 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 10 | ) 11 | 12 | // Contract represents a smart contract on a Chain. 13 | // I is the instantiate message type 14 | // E is the execute message type 15 | // Q is the query message type 16 | // QC is the query client interface 17 | type Contract[I, E, Q any, QC comparable] struct { 18 | Address string 19 | CodeID string 20 | Chain *cosmos.CosmosChain 21 | 22 | qc QC 23 | } 24 | 25 | // newContract creates a new Contract instance 26 | func newContract[I, E, Q any, QC comparable](address string, codeId string, chain *cosmos.CosmosChain) *Contract[I, E, Q, QC] { 27 | return &Contract[I, E, Q, QC]{ 28 | Address: address, 29 | CodeID: codeId, 30 | Chain: chain, 31 | } 32 | } 33 | 34 | // Instantiate creates a new contract instance on the chain. No admin flag is set if admin is empty. 35 | // I is the instantiate message type 36 | // E is the execute message type 37 | // Q is the query message type 38 | // QC is the query client interface 39 | func Instantiate[I, E, Q any, QC comparable](ctx context.Context, callerKeyName, codeId, admin string, chain *cosmos.CosmosChain, msg I, extraExecTxArgs ...string) (*Contract[I, E, Q, QC], error) { 40 | isNoAdmin := admin == "" 41 | if !isNoAdmin { 42 | extraExecTxArgs = append(extraExecTxArgs, "--admin", admin) 43 | } 44 | 45 | contractAddr, err := chain.InstantiateContract(ctx, callerKeyName, codeId, toString(msg), isNoAdmin, extraExecTxArgs...) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | return newContract[I, E, Q, QC](contractAddr, codeId, chain), nil 51 | } 52 | 53 | // Port returns the port of the contract 54 | func (c *Contract[I, E, Q, QC]) Port() string { 55 | return "wasm." + c.Address 56 | } 57 | 58 | // Execute executes the contract with the given execute message and returns the transaction response 59 | func (c *Contract[I, E, Q, QC]) Execute(ctx context.Context, callerKeyName string, msg E, extraExecTxArgs ...string) (*sdk.TxResponse, error) { 60 | return c.Chain.ExecuteContract(ctx, callerKeyName, c.Address, toString(msg), extraExecTxArgs...) 61 | } 62 | 63 | // QueryClient returns the query client of the contract. 64 | // If the query client is not provided, it panics. 65 | func (c *Contract[I, E, Q, QC]) QueryClient() QC { 66 | var zeroQc QC 67 | if c.qc == zeroQc { 68 | panic("Query client is not provided") 69 | } 70 | 71 | return c.qc 72 | } 73 | 74 | // SetQueryClient sets the query client of the contract. 75 | func (c *Contract[I, E, Q, QC]) SetQueryClient(qc QC) { 76 | c.qc = qc 77 | } 78 | 79 | // Query queries the contract with the given query message 80 | // and unmarshals the response into the given response object. 81 | // This is meant to be used if the query client is not provided. 82 | func (c *Contract[I, E, Q, QC]) Query(ctx context.Context, queryMsg Q, resp any) error { 83 | // queryResponse is used to represent the response of a query. 84 | // It may contain different types of data, so we need to unmarshal it 85 | type queryResponse struct { 86 | Response json.RawMessage `json:"data"` 87 | } 88 | 89 | queryResp := queryResponse{} 90 | err := c.Chain.QueryContract(ctx, c.Address, queryMsg, &queryResp) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | err = json.Unmarshal(queryResp.Response, resp) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | return nil 101 | } 102 | 103 | // this line is used by go-codegen # contract/dir 104 | 105 | // toString converts the message to a string using json 106 | func toString(msg any) string { 107 | bz, err := json.Marshal(msg) 108 | if err != nil { 109 | panic(err) 110 | } 111 | 112 | return string(bz) 113 | } 114 | -------------------------------------------------------------------------------- /templates/interchaintestv8/types/files/types/{{contractName}}/contract.go.plush: -------------------------------------------------------------------------------- 1 | package <%= PackageName %> 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 7 | 8 | "<%= ModulePath %>/types" 9 | ) 10 | 11 | // Contract represents a <%= ContractName %> contract on a Chain. 12 | type Contract = types.Contract[<%= InstantiateMsgName %>, <%= ExecuteMsgName %>, <%= QueryMsgName %>, QueryClient] 13 | 14 | // Instantiate creates a new <%= ContractName %> contract instance on the chain. 15 | // It also creates a query client for the contract. 16 | // This function is useful when you want to deploy a new contract on the chain. 17 | func Instantiate(ctx context.Context, callerKeyName, codeId, admin string, chain *cosmos.CosmosChain, msg InstantiateMsg, extraExecTxArgs ...string) (*Contract, error) { 18 | contract, err := types.Instantiate[<%= InstantiateMsgName %>, <%= ExecuteMsgName %>, <%= QueryMsgName %>, QueryClient](ctx, callerKeyName, codeId, admin, chain, msg, extraExecTxArgs...) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | queryClient, err := NewQueryClient(chain.GetHostGRPCAddress(), contract.Address) 24 | if err != nil { 25 | return nil, err 26 | } 27 | contract.SetQueryClient(queryClient) 28 | 29 | return contract, nil 30 | } 31 | 32 | // NewContract creates a Contract from a given given contract address, code id and chain. 33 | // It also creates a query client for the contract. 34 | // This function is useful when you already have a contract deployed on the chain. 35 | func NewContract(address string, codeId string, chain *cosmos.CosmosChain) (*Contract, error) { 36 | contract := types.Contract[<%= InstantiateMsgName %>, <%= ExecuteMsgName %>, <%= QueryMsgName %>, QueryClient]{ 37 | Address: address, 38 | CodeID: codeId, 39 | Chain: chain, 40 | } 41 | 42 | queryClient, err := NewQueryClient(chain.GetHostGRPCAddress(), contract.Address) 43 | if err != nil { 44 | return nil, err 45 | } 46 | contract.SetQueryClient(queryClient) 47 | 48 | return &contract, nil 49 | } 50 | -------------------------------------------------------------------------------- /templates/interchaintestv8/types/options.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/gobuffalo/plush/v4" 7 | "golang.org/x/mod/module" 8 | ) 9 | 10 | type AddContractOptions struct { 11 | ModulePath string 12 | ContractName string 13 | PackageName string 14 | InstantiateMsgName string 15 | ExecuteMsgName string 16 | QueryMsgName string 17 | } 18 | 19 | // Validate that options are usable. 20 | func (opts *AddContractOptions) Validate() error { 21 | err := module.CheckPath(opts.ModulePath) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | if opts.PackageName == "" { 27 | return errors.New("PackageName must not be empty") 28 | } 29 | 30 | if opts.ContractName == "" { 31 | return errors.New("ContractName must not be empty") 32 | } 33 | 34 | if opts.InstantiateMsgName == "" { 35 | return errors.New("InstantiateMsgName must not be empty") 36 | } 37 | 38 | if opts.ExecuteMsgName == "" { 39 | return errors.New("ExecuteMsgName must not be empty") 40 | } 41 | 42 | if opts.QueryMsgName == "" { 43 | return errors.New("QueryMsgName must not be empty") 44 | } 45 | 46 | return nil 47 | } 48 | 49 | // plushContext returns a plush context for the options. 50 | func (opts *AddContractOptions) plushContext() *plush.Context { 51 | ctx := plush.NewContext() 52 | ctx.Set("ModulePath", opts.ModulePath) 53 | ctx.Set("PackageName", opts.PackageName) 54 | ctx.Set("ContractName", opts.ContractName) 55 | ctx.Set("InstantiateMsgName", opts.InstantiateMsgName) 56 | ctx.Set("ExecuteMsgName", opts.ExecuteMsgName) 57 | ctx.Set("QueryMsgName", opts.QueryMsgName) 58 | return ctx 59 | } 60 | -------------------------------------------------------------------------------- /templates/interchaintestv8/types/placeholders.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const PlaceholderContractDir = "// this line is used by go-codegen # contract/dir" 4 | --------------------------------------------------------------------------------