├── .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 | [](https://github.com/srdtrk/go-codegen/tree/main/integration_test)
12 | [](https://github.com/srdtrk/go-codegen/releases/latest)
13 | [](https://github.com/srdtrk/go-codegen/blob/main/LICENSE)
14 | [](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 | 
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 | 
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 |
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 |
16 |
--------------------------------------------------------------------------------
/docs/static/img/easy_deploy.svg:
--------------------------------------------------------------------------------
1 |
33 |
--------------------------------------------------------------------------------
/docs/static/img/ibc-logo-black.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/docs/static/img/icons/hi-coffee-icon.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/docs/static/img/icons/hi-info-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/static/img/icons/hi-note-icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/static/img/icons/hi-prerequisite-icon.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/docs/static/img/icons/hi-reading-icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/static/img/icons/hi-star-icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/static/img/icons/hi-target-icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/static/img/icons/hi-tip-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/static/img/icons/hi-warn-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/static/img/white-ibc-logo.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------