├── local-interchain ├── rust │ ├── .gitignore │ ├── localic-std │ │ ├── src │ │ │ ├── modules │ │ │ │ ├── mod.rs │ │ │ │ ├── gov.rs │ │ │ │ └── bank.rs │ │ │ ├── lib.rs │ │ │ ├── polling.rs │ │ │ ├── filesystem.rs │ │ │ └── errors.rs │ │ ├── Makefile │ │ └── Cargo.toml │ ├── Makefile │ ├── Cargo.toml │ └── main │ │ ├── Cargo.toml │ │ └── src │ │ └── base.rs ├── python │ ├── requirements.txt │ ├── helpers │ │ ├── __init__.py │ │ ├── testing.py │ │ ├── relayer.py │ │ └── example_req.bash │ └── util_base.py ├── contracts │ └── cw_ibc_example.wasm ├── .gitignore ├── interchain │ ├── util │ │ ├── http_writer.go │ │ └── replace.go │ ├── types │ │ ├── logs.go │ │ ├── genesis.go │ │ └── types.go │ ├── faket.go │ ├── handlers │ │ ├── types.go │ │ └── uploader.go │ ├── ibc.go │ ├── router │ │ └── router.go │ ├── logs.go │ └── genesis.go ├── cmd │ └── local-ic │ │ ├── main.go │ │ ├── chains.go │ │ └── root.go ├── Makefile ├── .flake8 └── chains │ ├── eth.json │ ├── hub.yml │ ├── cosmoshub.json │ ├── stargaze.json │ └── base_ibc.json ├── .github ├── CODEOWNERS ├── mergify.yml ├── dependabot.yml └── workflows │ ├── chores.yml │ ├── unit-tests.yml │ ├── strangelove-project-management.yaml │ ├── release.yml │ ├── lint.yml │ └── e2e-tests.yml ├── go.work ├── examples ├── cosmwasm │ ├── rust-optimizer │ │ ├── contract │ │ │ ├── .gitignore │ │ │ ├── src │ │ │ │ ├── lib.rs │ │ │ │ ├── state.rs │ │ │ │ ├── error.rs │ │ │ │ ├── msg.rs │ │ │ │ └── contract.rs │ │ │ ├── .cargo │ │ │ │ └── config │ │ │ └── Cargo.toml │ │ └── README │ └── workspace-optimizer │ │ ├── workspace │ │ ├── .gitignore │ │ ├── contracts │ │ │ ├── contract1 │ │ │ │ ├── .gitignore │ │ │ │ ├── src │ │ │ │ │ ├── lib.rs │ │ │ │ │ ├── state.rs │ │ │ │ │ ├── error.rs │ │ │ │ │ ├── msg.rs │ │ │ │ │ └── contract.rs │ │ │ │ ├── .cargo │ │ │ │ │ └── config │ │ │ │ └── Cargo.toml │ │ │ └── contract2 │ │ │ │ ├── .gitignore │ │ │ │ ├── src │ │ │ │ ├── lib.rs │ │ │ │ ├── state.rs │ │ │ │ ├── error.rs │ │ │ │ ├── msg.rs │ │ │ │ └── contract.rs │ │ │ │ ├── .cargo │ │ │ │ └── config │ │ │ │ └── Cargo.toml │ │ └── Cargo.toml │ │ └── README ├── polkadot │ └── ics10_grandpa_cw.wasm ├── ibc │ ├── wasm │ │ └── sample_contracts │ │ │ ├── icq.wasm │ │ │ ├── reflect.wasm │ │ │ ├── ibc_reflect.wasm │ │ │ └── ibc_reflect_send.wasm │ └── README.md └── cosmos │ ├── README.md │ ├── sample_contracts │ └── cw_template.wasm │ └── chain_export_test.go ├── proto ├── buf.yaml ├── buf.gen.penumbra.yaml └── buf.lock ├── dockerutil ├── doc.go ├── file.go ├── startcontainer.go ├── busybox.go ├── keyring.go ├── filewriter_test.go ├── fileretriever_test.go ├── setup_test.go ├── ports.go └── strings_test.go ├── testutil ├── doc.go ├── gzip.go └── toml.go ├── chain ├── cosmos │ ├── doc.go │ ├── module_crisis.go │ ├── 08-wasm-types │ │ ├── consensus_state.go │ │ ├── client_message.go │ │ ├── msgs.go │ │ ├── codec.go │ │ └── module.go │ ├── wasm │ │ └── wasm.go │ ├── address.go │ ├── config.go │ ├── node_test.go │ ├── query.go │ ├── wallet.go │ ├── module_slashing.go │ ├── genesis.go │ ├── osmosis.go │ ├── module_feegrant.go │ ├── codec.go │ ├── module_vesting.go │ └── module_upgrade.go ├── penumbra │ ├── doc.go │ ├── penumbra_client_node_test.go │ ├── wallet.go │ └── penumbra_node.go ├── internal │ └── tendermint │ │ ├── doc.go │ │ ├── events_test.go │ │ └── events.go ├── polkadot │ ├── wallet.go │ └── query.go └── ethereum │ ├── wallet.go │ └── unimplemented.go ├── blockdb ├── doc.go ├── tui │ ├── presenter │ │ ├── time.go │ │ ├── test_case.go │ │ ├── test_case_test.go │ │ ├── highlight.go │ │ ├── highlight_test.go │ │ ├── tx.go │ │ ├── tx_test.go │ │ └── cosmos_message.go │ ├── style.go │ ├── model_test.go │ ├── maincontent_string.go │ ├── help.go │ └── model.go ├── testdata │ └── README.md ├── migrate_test.go ├── sql.go ├── test_case.go ├── test_case_test.go └── chain.go ├── .codespellrc ├── mocktesting └── doc.go ├── version.go ├── .gitignore ├── relayer ├── doc.go ├── capability_string.go ├── hermes │ └── hermes_wallet.go ├── rly │ └── wallet.go ├── hyperspace │ ├── wallet.go │ └── hyperspace_test.go ├── capability.go └── options.go ├── tools.go ├── ibc ├── doc.go ├── tx.go ├── tx_test.go └── relayer_test.go ├── interchaintest_test.go ├── docs ├── buildBinary.md ├── envOptions.md ├── retainingDataOnFailedTests.md └── logging.md ├── scripts └── protocgen.sh ├── cmd └── interchaintest │ ├── flags_test.go │ ├── example_matrix.json │ ├── README.md │ ├── matrix_test.go │ ├── example_matrix_custom.json │ └── flags.go ├── interchaintest.go ├── Makefile ├── penumbra.go ├── testreporter └── messages_test.go ├── contract └── cosmwasm │ └── rust_optimizer.go ├── test_user.go └── tempdir_test.go /local-interchain/rust/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /local-interchain/python/requirements.txt: -------------------------------------------------------------------------------- 1 | httpx -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @strangelove-ventures/interchaintest-maintainers 2 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.22.2 2 | 3 | use ( 4 | . 5 | ./local-interchain 6 | ) 7 | -------------------------------------------------------------------------------- /examples/cosmwasm/rust-optimizer/contract/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | artifacts/ -------------------------------------------------------------------------------- /proto/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | deps: 3 | - buf.build/penumbra-zone/penumbra -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | artifacts/ -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract1/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | artifacts/ -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract2/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | artifacts/ -------------------------------------------------------------------------------- /local-interchain/rust/localic-std/src/modules/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bank; 2 | pub mod cosmwasm; 3 | pub mod gov; 4 | -------------------------------------------------------------------------------- /dockerutil/doc.go: -------------------------------------------------------------------------------- 1 | // Package dockerutil contains helpers for interacting with Docker containers. 2 | package dockerutil 3 | -------------------------------------------------------------------------------- /testutil/doc.go: -------------------------------------------------------------------------------- 1 | // Package testutil includes convenience functions and types to help with testing 2 | package testutil 3 | -------------------------------------------------------------------------------- /examples/cosmwasm/rust-optimizer/contract/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | pub mod error; 3 | pub mod msg; 4 | pub mod state; -------------------------------------------------------------------------------- /examples/cosmwasm/rust-optimizer/contract/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --target wasm32-unknown-unknown --release --lib" -------------------------------------------------------------------------------- /chain/cosmos/doc.go: -------------------------------------------------------------------------------- 1 | // Package cosmos provides an implementation of ibc.Chain backed by Cosmos-based blockchains. 2 | package cosmos 3 | -------------------------------------------------------------------------------- /chain/penumbra/doc.go: -------------------------------------------------------------------------------- 1 | // Package penumbra provides an implementation of ibc.Chain for the Penumbra blockchain. 2 | package penumbra 3 | -------------------------------------------------------------------------------- /chain/internal/tendermint/doc.go: -------------------------------------------------------------------------------- 1 | // Package tendermint provides interfaces for interacting with tendermint nodes. 2 | package tendermint 3 | -------------------------------------------------------------------------------- /examples/polkadot/ics10_grandpa_cw.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veronuka435/interchaintest/HEAD/examples/polkadot/ics10_grandpa_cw.wasm -------------------------------------------------------------------------------- /local-interchain/python/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa F401 2 | 3 | from .cosmwasm import CosmWasm 4 | from .transactions import * 5 | -------------------------------------------------------------------------------- /examples/ibc/wasm/sample_contracts/icq.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veronuka435/interchaintest/HEAD/examples/ibc/wasm/sample_contracts/icq.wasm -------------------------------------------------------------------------------- /blockdb/doc.go: -------------------------------------------------------------------------------- 1 | // Package blockdb saves chain and block data for inspection later to aid in debugging interchaintest failures. 2 | package blockdb 3 | -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract1/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | pub mod error; 3 | pub mod msg; 4 | pub mod state; -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract2/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod contract; 2 | pub mod error; 3 | pub mod msg; 4 | pub mod state; -------------------------------------------------------------------------------- /examples/cosmos/README.md: -------------------------------------------------------------------------------- 1 | # More Examples 2 | 3 | [State Sync](https://github.com/CosmosContracts/juno/blob/v21.0.0/interchaintest/state_sync_test.go) 4 | -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract1/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --target wasm32-unknown-unknown --release --lib" -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract2/.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | wasm = "build --target wasm32-unknown-unknown --release --lib" -------------------------------------------------------------------------------- /examples/ibc/wasm/sample_contracts/reflect.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veronuka435/interchaintest/HEAD/examples/ibc/wasm/sample_contracts/reflect.wasm -------------------------------------------------------------------------------- /local-interchain/contracts/cw_ibc_example.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veronuka435/interchaintest/HEAD/local-interchain/contracts/cw_ibc_example.wasm -------------------------------------------------------------------------------- /examples/cosmos/sample_contracts/cw_template.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veronuka435/interchaintest/HEAD/examples/cosmos/sample_contracts/cw_template.wasm -------------------------------------------------------------------------------- /examples/cosmwasm/rust-optimizer/contract/src/state.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::Addr; 2 | use cw_storage_plus::Item; 3 | 4 | pub const OWNER: Item = Item::new("owner"); -------------------------------------------------------------------------------- /examples/ibc/wasm/sample_contracts/ibc_reflect.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veronuka435/interchaintest/HEAD/examples/ibc/wasm/sample_contracts/ibc_reflect.wasm -------------------------------------------------------------------------------- /local-interchain/rust/Makefile: -------------------------------------------------------------------------------- 1 | build-bin: 2 | @echo "Building rust interchain..." 3 | @cargo build --release 4 | 5 | run-bin: build-bin 6 | @./target/release/localic-bin -------------------------------------------------------------------------------- /examples/ibc/wasm/sample_contracts/ibc_reflect_send.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Veronuka435/interchaintest/HEAD/examples/ibc/wasm/sample_contracts/ibc_reflect_send.wasm -------------------------------------------------------------------------------- /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | skip = *.pulsar.go,*.pb.go,*.pb.gw.go,*.json,*.git,*.bin,*.sum,*.mod,query_test.go 3 | ignore-words-list = usera,pres,crate 4 | count = 5 | quiet-level = 3 -------------------------------------------------------------------------------- /mocktesting/doc.go: -------------------------------------------------------------------------------- 1 | // Package mocktesting contains a mock instance of *testing.T 2 | // which is useful for testing interchaintest's interactions with Go tests. 3 | package mocktesting 4 | -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract1/src/state.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::Addr; 2 | use cw_storage_plus::Item; 3 | 4 | pub const OWNER: Item = Item::new("owner"); -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package interchaintest 2 | 3 | // GitSha is the git commit that produced the executable. 4 | // Set via the project Makefile `make interchaintest`. 5 | var GitSha = "unknown" 6 | -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract2/src/state.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::Addr; 2 | use cw_storage_plus::Item; 3 | 4 | pub const OWNER: Item = Item::new("owner2"); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't commit the interchaintest.test file, 2 | # regardless of where it was built. 3 | interchaintest.test 4 | .idea 5 | /bin 6 | vendor 7 | examples/hyperspace/exported_state.json 8 | 9 | go.work.sum -------------------------------------------------------------------------------- /blockdb/tui/presenter/time.go: -------------------------------------------------------------------------------- 1 | package presenter 2 | 3 | import "time" 4 | 5 | // FormatTime returns a shortened local time. 6 | func FormatTime(t time.Time) string { 7 | return t.Format("01-02 03:04PM MST") 8 | } 9 | -------------------------------------------------------------------------------- /relayer/doc.go: -------------------------------------------------------------------------------- 1 | // Package relayer contains general functionality relevant to individual relayer implementations. 2 | // 3 | // Subpackages under relayer provide implementations of the ibc.Relayer interface. 4 | package relayer 5 | -------------------------------------------------------------------------------- /local-interchain/rust/localic-std/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod errors; 2 | pub mod filesystem; 3 | pub mod polling; 4 | pub mod transactions; 5 | pub mod types; 6 | 7 | pub mod node; 8 | pub mod relayer; 9 | 10 | pub mod modules; 11 | -------------------------------------------------------------------------------- /local-interchain/rust/localic-std/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: lint 2 | lint: 3 | @echo "Running linter..." 4 | @cargo fmt --all 5 | @cargo clippy -- -D warnings 6 | @cargo clippy -- -W clippy::pedantic -W clippy::correctness -W clippy::branches_sharing_code -------------------------------------------------------------------------------- /local-interchain/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | 3 | dist/ 4 | 5 | chains/*_ignored.json 6 | chains/*_ignore.json 7 | 8 | configs/logs.json 9 | configs/contracts.json 10 | 11 | contracts/*.wasm 12 | !contracts/cw_ibc_example.wasm 13 | 14 | 15 | __pycache__/ -------------------------------------------------------------------------------- /blockdb/testdata/README.md: -------------------------------------------------------------------------------- 1 | # How to dump JSON from sqlite3. 2 | 3 | For sample_txs.json 4 | 5 | Produces an array of JSON objects: 6 | 7 | ```shell 8 | sqlite3 ~/.interchaintest/databases/block.db 'select tx_id, tx from v_tx_flattened where chain_kid = 1 order by tx_id asc' -json > /some/file/path.json 9 | ``` -------------------------------------------------------------------------------- /blockdb/tui/style.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import "github.com/gdamore/tcell/v2" 4 | 5 | const ( 6 | backgroundColor = tcell.ColorBlack 7 | textColor = tcell.ColorWhite 8 | errorTextColor = tcell.ColorRed 9 | ) 10 | 11 | var ( 12 | textStyle = tcell.Style{}.Foreground(textColor) 13 | ) 14 | -------------------------------------------------------------------------------- /examples/cosmwasm/rust-optimizer/contract/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::StdError; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum ContractError { 6 | #[error("{0}")] 7 | Std(#[from] StdError), 8 | 9 | #[error("Unauthorized")] 10 | Unauthorized {}, 11 | } 12 | -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "contracts/contract1", 4 | "contracts/contract2" 5 | ] 6 | 7 | [workspace.dependencies] 8 | cosmwasm-schema = "1.1.2" 9 | cosmwasm-std = "1.1.2" 10 | cw-storage-plus = "0.13.2" 11 | thiserror = {version = "1.0.31"} 12 | -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract1/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::StdError; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum ContractError { 6 | #[error("{0}")] 7 | Std(#[from] StdError), 8 | 9 | #[error("Unauthorized")] 10 | Unauthorized {}, 11 | } 12 | -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract2/src/error.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::StdError; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum ContractError { 6 | #[error("{0}")] 7 | Std(#[from] StdError), 8 | 9 | #[error("Unauthorized2")] 10 | Unauthorized {}, 11 | } 12 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | 3 | // This pattern of a file named tools.go, to import dependent tools, 4 | // comes from the official Go wiki: 5 | // https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 6 | 7 | package interchaintest 8 | 9 | import ( 10 | _ "golang.org/x/tools/cmd/stringer" 11 | ) 12 | -------------------------------------------------------------------------------- /local-interchain/rust/localic-std/src/modules/gov.rs: -------------------------------------------------------------------------------- 1 | use serde_json::Value; 2 | 3 | use crate::transactions::ChainRequestBuilder; 4 | 5 | #[must_use] 6 | pub fn get_proposal(req_builder: &ChainRequestBuilder, proposal_id: u64) -> Value { 7 | let cmd = format!("q gov proposal {proposal_id}"); 8 | req_builder.query(cmd.as_str(), false) 9 | } 10 | -------------------------------------------------------------------------------- /examples/cosmwasm/rust-optimizer/contract/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "intro-contract" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Strangelove developers"] 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | cosmwasm-schema = "1.1.2" 12 | cosmwasm-std = "1.1.2" 13 | cw-storage-plus = "0.13.2" 14 | thiserror = {version = "1.0.31"} -------------------------------------------------------------------------------- /ibc/doc.go: -------------------------------------------------------------------------------- 1 | // Package ibc provides chain and relayer agnostic types and interfaces for the IBC spec. 2 | // It attempts to expose the domain model of IBC. 3 | // 4 | // The official spec documentation can be found at https://github.com/cosmos/ibc/tree/master/spec. 5 | // 6 | // Currently, the interfaces may be biased towards chains built with the cosmos sdk and the cosmos/relayer. 7 | package ibc 8 | -------------------------------------------------------------------------------- /local-interchain/interchain/util/http_writer.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func Write(w http.ResponseWriter, bz []byte) { 9 | if _, err := w.Write(bz); err != nil { 10 | log.Default().Println(err) 11 | } 12 | } 13 | 14 | func WriteError(w http.ResponseWriter, err error) { 15 | Write(w, []byte(`{"error": "`+err.Error()+`"}`)) 16 | } 17 | -------------------------------------------------------------------------------- /local-interchain/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["main", "localic-std"] 3 | 4 | [workspace.dependencies] 5 | reqwest = { version = "0.11.20", features = ["blocking", "json"]} 6 | tokio = { version = "1.32.0", features = ["full"] } 7 | serde_json = {version = "1.0.107"} 8 | cosmwasm-std = "1.4.0" 9 | thiserror = { version = "1.0.31" } 10 | 11 | localic-std = { path = "./localic-std"} -------------------------------------------------------------------------------- /examples/cosmwasm/rust-optimizer/README: -------------------------------------------------------------------------------- 1 | The rust-optimizer example contains a simple contract that performs minimal functionality. 2 | The single contract uses cosmwasm/rust-optimizer to compile during test execution. 3 | The test case shows how the contract source can integrate with interchaintest: building the contract, spinning up a chain, storing it on-chain, and instantiating/querying/executing against it. -------------------------------------------------------------------------------- /chain/cosmos/module_crisis.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // CrisisInvariantBroken executes the crisis invariant broken command. 8 | func (tn *ChainNode) CrisisInvariantBroken(ctx context.Context, keyName, moduleName, route string) error { 9 | _, err := tn.ExecTx(ctx, 10 | keyName, "crisis", "invariant-broken", moduleName, route, 11 | ) 12 | return err 13 | } 14 | -------------------------------------------------------------------------------- /local-interchain/rust/localic-std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "localic-std" 3 | version = "0.0.1" 4 | edition = "2021" 5 | authors = ["Strangelove Developers"] 6 | 7 | [lib] 8 | name = "localic_std" 9 | path = "./src/lib.rs" 10 | 11 | [dependencies] 12 | reqwest.workspace = true 13 | tokio.workspace = true 14 | serde_json.workspace = true 15 | cosmwasm-std.workspace = true 16 | thiserror.workspace = true 17 | -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "contract1" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Strangelove developers"] 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | cosmwasm-schema = { workspace = true } 12 | cosmwasm-std = { workspace = true } 13 | cw-storage-plus = { workspace = true } 14 | thiserror = { workspace = true } -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "contract2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Strangelove developers"] 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | cosmwasm-schema = { workspace = true } 12 | cosmwasm-std = { workspace = true } 13 | cw-storage-plus = { workspace = true } 14 | thiserror = { workspace = true } -------------------------------------------------------------------------------- /local-interchain/cmd/local-ic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | rootCmd.AddCommand(chainsCmd) 10 | rootCmd.AddCommand(newChainCmd) 11 | rootCmd.AddCommand(startCmd) 12 | rootCmd.AddCommand(interactCmd) 13 | 14 | if err := rootCmd.Execute(); err != nil { 15 | fmt.Fprintf(os.Stderr, "error while executing your CLI. Err: %v\n", err) 16 | os.Exit(1) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/README: -------------------------------------------------------------------------------- 1 | The workspace-optimizer example contains a workspace organized with two simple contracts that performs minimal functionality. 2 | The workspace uses cosmwasm/workspace-optimizer to compile during test execution. 3 | The test case shows how the contracts source can integrate with interchaintest: building the contracts, spinning up a chain, storing it on-chain, and instantiating/querying/executing against it. -------------------------------------------------------------------------------- /local-interchain/rust/main/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "localic-bin" 3 | version = "0.0.1" 4 | edition = "2021" 5 | authors = ["Strangelove Developers"] 6 | 7 | [[bin]] 8 | name = "localic-bin" 9 | path = "src/main.rs" 10 | 11 | [dependencies] 12 | reqwest.workspace = true 13 | tokio.workspace = true 14 | serde_json.workspace = true 15 | cosmwasm-std.workspace = true 16 | thiserror.workspace = true 17 | 18 | localic-std.workspace = true -------------------------------------------------------------------------------- /chain/cosmos/08-wasm-types/consensus_state.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/ibc-go/v8/modules/core/exported" 5 | ) 6 | 7 | var _ exported.ConsensusState = (*ConsensusState)(nil) 8 | 9 | func (m ConsensusState) ClientType() string { 10 | return "" 11 | } 12 | 13 | func (m ConsensusState) GetTimestamp() uint64 { 14 | return 0 15 | } 16 | 17 | func (m ConsensusState) ValidateBasic() error { 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /blockdb/tui/model_test.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/strangelove-ventures/interchaintest/v8/blockdb" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestModel_RootView(t *testing.T) { 12 | m := NewModel(&mockQueryService{}, "test.db", "abc123", time.Now(), make([]blockdb.TestCaseResult, 1)) 13 | view := m.RootView() 14 | require.NotNil(t, view) 15 | require.Greater(t, view.GetItemCount(), 0) 16 | } 17 | -------------------------------------------------------------------------------- /interchaintest_test.go: -------------------------------------------------------------------------------- 1 | package interchaintest 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestDefaultBlockDatabaseFilepath(t *testing.T) { 12 | got := DefaultBlockDatabaseFilepath() 13 | parts := strings.Split(got, string(os.PathSeparator)) 14 | 15 | require.NotEmpty(t, parts) 16 | require.Equal(t, []string{".interchaintest", "databases", "block.db"}, parts[len(parts)-3:]) 17 | } 18 | -------------------------------------------------------------------------------- /chain/cosmos/08-wasm-types/client_message.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/ibc-go/v8/modules/core/exported" 5 | ) 6 | 7 | var _ exported.ClientMessage = &ClientMessage{} 8 | 9 | // ClientType is a Wasm light client. 10 | func (c ClientMessage) ClientType() string { 11 | return "08-wasm" 12 | } 13 | 14 | // ValidateBasic defines a basic validation for the wasm client message. 15 | func (c ClientMessage) ValidateBasic() error { 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /docs/buildBinary.md: -------------------------------------------------------------------------------- 1 | ## Building Binary 2 | 3 | While it is not necessary to build the binary, sometimes it can be more convenient, *specifically* when running conformance test with custom chain sets. 4 | 5 | Building binary: 6 | ```shell 7 | git clone https://github.com/strangelove-ventures/interchaintest.git 8 | cd interchaintest 9 | make interchaintest 10 | ``` 11 | 12 | This places the binary in `interchaintest/bin/interchaintest` 13 | 14 | Note that this is not in your Go path. -------------------------------------------------------------------------------- /chain/cosmos/wasm/wasm.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import ( 4 | wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" 5 | "github.com/cosmos/cosmos-sdk/types/module/testutil" 6 | 7 | // simappparams "github.com/cosmos/cosmos-sdk/simapp/params" 8 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 9 | ) 10 | 11 | func WasmEncoding() *testutil.TestEncodingConfig { 12 | cfg := cosmos.DefaultEncoding() 13 | 14 | wasmtypes.RegisterInterfaces(cfg.InterfaceRegistry) 15 | 16 | return &cfg 17 | } -------------------------------------------------------------------------------- /local-interchain/python/helpers/testing.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import httpx 4 | 5 | 6 | def poll_for_start(API_URL: str, waitSeconds=300): 7 | for i in range(waitSeconds + 1): 8 | try: 9 | httpx.get(API_URL) 10 | return 11 | except Exception: 12 | if i % 5 == 0: 13 | print(f"waiting for server to start (iter:{i}) ({API_URL})") 14 | 15 | time.sleep(1) 16 | 17 | raise Exception("Local-IC REST API Server did not start") 18 | -------------------------------------------------------------------------------- /proto/buf.gen.penumbra.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | managed: 3 | enabled: true 4 | go_package_prefix: 5 | default: github.com/strangelove-ventures/interchaintest/v8/chain 6 | except: 7 | - buf.build/cosmos/ibc 8 | - github.com/cometbft/cometbft 9 | - buf.build/cosmos/cosmos-sdk 10 | plugins: 11 | - name: gocosmos 12 | out: . 13 | opt: plugins=grpc,Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types,Mgoogle/protobuf/timestamp.proto=github.com/cosmos/gogoproto/types -------------------------------------------------------------------------------- /scripts/protocgen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eox pipefail 4 | 5 | buf generate --template proto/buf.gen.penumbra.yaml buf.build/penumbra-zone/penumbra 6 | 7 | # move proto files to the right places 8 | # Note: Proto files are suffixed with the current binary version. 9 | # rm -r github.com/strangelove-ventures/interchaintest/v*/chain/penumbra/client 10 | rm -r github.com/strangelove-ventures/interchaintest/v*/chain/penumbra/narsil 11 | cp -r github.com/strangelove-ventures/interchaintest/v*/* ./ 12 | rm -rf github.com -------------------------------------------------------------------------------- /testutil/gzip.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | ) 7 | 8 | // GzipIt compresses the input ([]byte) 9 | func GzipIt(input []byte) ([]byte, error) { 10 | // Create gzip writer. 11 | var b bytes.Buffer 12 | w := gzip.NewWriter(&b) 13 | _, err := w.Write(input) 14 | if err != nil { 15 | return nil, err 16 | } 17 | err = w.Close() // You must close this first to flush the bytes to the buffer. 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | return b.Bytes(), nil 23 | } 24 | -------------------------------------------------------------------------------- /cmd/interchaintest/flags_test.go: -------------------------------------------------------------------------------- 1 | package interchaintest 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestMainFlags_Logger(t *testing.T) { 10 | for _, tt := range []struct { 11 | LogFile string 12 | }{ 13 | {""}, 14 | {"stdout"}, 15 | {"stderr"}, 16 | } { 17 | flags := mainFlags{LogFile: tt.LogFile} 18 | logger, err := flags.Logger() 19 | 20 | require.NoError(t, err) 21 | require.NoError(t, logger.Close()) 22 | require.NotEmpty(t, logger.FilePath) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /local-interchain/Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | CWD := $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) 4 | 5 | ldflags = -X main.MakeFileInstallDirectory=$(CWD) 6 | ldflags := $(strip $(ldflags)) 7 | BUILD_FLAGS := -ldflags '$(ldflags)' 8 | 9 | .PHONY: build 10 | build: 11 | go build $(BUILD_FLAGS) -o ../bin/local-ic ./cmd/local-ic 12 | 13 | .PHONY: run 14 | run: 15 | go run ./cmd/local-ic $(filter-out $@,$(MAKECMDGOALS)) 16 | 17 | .PHONY: install 18 | install: 19 | go install $(BUILD_FLAGS) ./cmd/local-ic ./interchain 20 | 21 | -------------------------------------------------------------------------------- /docs/envOptions.md: -------------------------------------------------------------------------------- 1 | # Environment Variable Options 2 | 3 | - `SHOW_CONTAINER_LOGS`: Controls whether container logs are displayed. 4 | 5 | - Set to `"always"` to show logs for both pass and fail. 6 | - Set to `"never"` to never show any logs. 7 | - Leave unset to show logs only for failed tests. 8 | 9 | - `KEEP_CONTAINERS`: Prevents testnet cleanup after completion. 10 | 11 | - Set to any non-empty value to keep testnet containers alive. 12 | 13 | - `CONTAINER_LOG_TAIL`: Specifies the number of lines to display from container logs. Defaults to 50 lines. 14 | -------------------------------------------------------------------------------- /local-interchain/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | 3 | plugins = 4 | flake8-simplify 5 | flake8-bandit 6 | flake8-bugbear 7 | flake8-builtins 8 | flake8-comprehensions 9 | flake8-eradicate 10 | flake8-isort 11 | flake8-pytest-style 12 | flakehell 13 | pep8-naming 14 | 15 | spellcheck-targets=comments 16 | 17 | extend-ignore = 18 | E501 19 | ; docstrings 20 | D 21 | ; try pass 22 | S110 23 | ; dict(k=v) 24 | C408 25 | 26 | exclude = 27 | .git, 28 | __pycache__, 29 | .mypy_cache 30 | 31 | max-complexity = 10 -------------------------------------------------------------------------------- /examples/cosmwasm/rust-optimizer/contract/src/msg.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | 3 | #[cw_serde] 4 | pub struct InstantiateMsg {} 5 | 6 | #[cw_serde] 7 | pub struct MigrateMsg {} 8 | 9 | #[cw_serde] 10 | pub enum ExecuteMsg { 11 | ChangeContractOwner { 12 | new_owner: String, 13 | }, 14 | } 15 | 16 | #[cw_serde] 17 | #[derive(QueryResponses)] 18 | pub enum QueryMsg { 19 | /// Owner returns the owner of the contract. Response: OwnerResponse 20 | #[returns(OwnerResponse)] 21 | Owner {}, 22 | } 23 | 24 | #[cw_serde] 25 | pub struct OwnerResponse { 26 | pub address: String, 27 | } 28 | -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract1/src/msg.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | 3 | #[cw_serde] 4 | pub struct InstantiateMsg {} 5 | 6 | #[cw_serde] 7 | pub struct MigrateMsg {} 8 | 9 | #[cw_serde] 10 | pub enum ExecuteMsg { 11 | ChangeContractOwner { 12 | new_owner: String, 13 | }, 14 | } 15 | 16 | #[cw_serde] 17 | #[derive(QueryResponses)] 18 | pub enum QueryMsg { 19 | /// Owner returns the owner of the contract. Response: OwnerResponse 20 | #[returns(OwnerResponse)] 21 | Owner {}, 22 | } 23 | 24 | #[cw_serde] 25 | pub struct OwnerResponse { 26 | pub address: String, 27 | } 28 | -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract2/src/msg.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_schema::{cw_serde, QueryResponses}; 2 | 3 | #[cw_serde] 4 | pub struct InstantiateMsg {} 5 | 6 | #[cw_serde] 7 | pub struct MigrateMsg {} 8 | 9 | #[cw_serde] 10 | pub enum ExecuteMsg { 11 | ChangeContractOwner { 12 | new_owner: String, 13 | }, 14 | } 15 | 16 | #[cw_serde] 17 | #[derive(QueryResponses)] 18 | pub enum QueryMsg { 19 | /// Owner returns the owner of the contract. Response: OwnerResponse 20 | #[returns(OwnerResponse)] 21 | Owner {}, 22 | } 23 | 24 | #[cw_serde] 25 | pub struct OwnerResponse { 26 | pub address: String, 27 | } 28 | -------------------------------------------------------------------------------- /.github/mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: backport to maintained branches 3 | conditions: 4 | - base~=^(main|v7)$ 5 | - label=BACKPORT 6 | actions: 7 | backport: 8 | branches: 9 | - main 10 | - v7 11 | assignees: 12 | - "{{ author }}" 13 | labels: 14 | - automerge 15 | - backported 16 | title: "`[BP: {{ destination_branch }} <- #{{ number }}]` {{ title }}" 17 | 18 | - name: automerge backported PR's for maintained branches 19 | conditions: 20 | - label=automerge 21 | - base=v7 22 | actions: 23 | merge: 24 | method: squash -------------------------------------------------------------------------------- /dockerutil/file.go: -------------------------------------------------------------------------------- 1 | package dockerutil 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | ) 8 | 9 | func CopyFile(src, dst string) (int64, error) { 10 | sourceFileStat, err := os.Stat(src) 11 | if err != nil { 12 | return 0, err 13 | } 14 | 15 | if !sourceFileStat.Mode().IsRegular() { 16 | return 0, fmt.Errorf("%s is not a regular file", src) 17 | } 18 | 19 | source, err := os.Open(src) 20 | if err != nil { 21 | return 0, err 22 | } 23 | defer source.Close() 24 | 25 | destination, err := os.Create(dst) 26 | if err != nil { 27 | return 0, err 28 | } 29 | defer destination.Close() 30 | nBytes, err := io.Copy(destination, source) 31 | return nBytes, err 32 | } 33 | -------------------------------------------------------------------------------- /cmd/interchaintest/example_matrix.json: -------------------------------------------------------------------------------- 1 | { 2 | "Relayers": ["rly", "hermes"], 3 | 4 | "ChainSets": [ 5 | [ 6 | { 7 | "Name": "gaia", 8 | "Version": "latest" 9 | }, 10 | { 11 | "Name": "osmosis", 12 | "Version": "latest" 13 | } 14 | ], 15 | [ 16 | { 17 | "Name": "gaia", 18 | "Version": "latest" 19 | }, 20 | { 21 | "Name": "juno", 22 | "Version": "latest" 23 | } 24 | ], 25 | [ 26 | { 27 | "Name": "gaia", 28 | "Version": "latest" 29 | }, 30 | { 31 | "Name": "agoric", 32 | "Version": "latest" 33 | } 34 | ] 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /chain/penumbra/penumbra_client_node_test.go: -------------------------------------------------------------------------------- 1 | package penumbra 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "cosmossdk.io/math" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestBigIntDecoding(t *testing.T) { 12 | bigInt := math.NewInt(11235813) 13 | hi, lo := translateBigInt(bigInt) 14 | converted := translateHiAndLo(hi, lo) 15 | require.True(t, bigInt.Equal(converted)) 16 | 17 | b := big.NewInt(0) 18 | b.SetString("18446744073709551620", 10) // use a number that is bigger than the maximum value an uint64 can hold 19 | bInt := math.NewIntFromBigInt(b) 20 | hi, lo = translateBigInt(bInt) 21 | converted = translateHiAndLo(hi, lo) 22 | require.True(t, converted.Equal(bInt)) 23 | } 24 | -------------------------------------------------------------------------------- /cmd/interchaintest/README.md: -------------------------------------------------------------------------------- 1 | # interchaintest 2 | 3 | This directory contains a test that can be parameterized at runtime, 4 | allowing a user to pick and choose what combinations of relayers and chains to test. 5 | 6 | You can run the tests during development with `go test`, 7 | but for general distribution, you would generate the executable with `go test -c`. 8 | 9 | The test binary supports a `-matrix` flag. 10 | See `example_matrix.json` for an example of what this can look like using the test chains included in this repository. 11 | See `example_matrix_custom.json` for an example of what this can look like using full chain config customization. 12 | You may need to reference the `testMatrix` type in `ibc_test.go`. 13 | -------------------------------------------------------------------------------- /local-interchain/chains/eth.json: -------------------------------------------------------------------------------- 1 | { 2 | "chains": [ 3 | { 4 | "name": "ethereum", 5 | "chain_id": "31337", 6 | "denom": "wei", 7 | "binary": "anvil", 8 | "bech32_prefix": "n/a", 9 | "docker_image": { 10 | "repository": "ghcr.io/foundry-rs/foundry", 11 | "version": "latest" 12 | }, 13 | "gas_prices": "0", 14 | "chain_type": "ethereum", 15 | "coin_type": 60, 16 | "trusting_period": "0", 17 | "gas_adjustment": 0, 18 | "debugging": true, 19 | "host_port_override": { 20 | "8545": "8545" 21 | } 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /dockerutil/startcontainer.go: -------------------------------------------------------------------------------- 1 | package dockerutil 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/docker/docker/api/types" 8 | "github.com/docker/docker/client" 9 | ) 10 | 11 | // StartContainer attempts to start the container with the given ID. 12 | func StartContainer(ctx context.Context, cli *client.Client, id string) error { 13 | // add a deadline for the request if the calling context does not provide one 14 | if _, hasDeadline := ctx.Deadline(); !hasDeadline { 15 | var cancel func() 16 | ctx, cancel = context.WithTimeout(ctx, 30*time.Second) 17 | defer cancel() 18 | } 19 | 20 | err := cli.ContainerStart(ctx, id, types.ContainerStartOptions{}) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: gomod 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | open-pull-requests-limit: 10 13 | labels: 14 | - dependencies 15 | - package-ecosystem: github-actions 16 | directory: "/" 17 | schedule: 18 | interval: monthly 19 | open-pull-requests-limit: 10 20 | labels: 21 | - gh-action-version -------------------------------------------------------------------------------- /local-interchain/interchain/types/logs.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type MainLogs struct { 4 | StartTime uint64 `json:"start_time" yaml:"start_time"` 5 | Chains []LogOutput `json:"chains" yaml:"chains"` 6 | Channels []IBCChannel `json:"ibc_channels" yaml:"ibc_channels"` 7 | } 8 | 9 | type LogOutput struct { 10 | ChainID string `json:"chain_id" yaml:"chain_id"` 11 | ChainName string `json:"chain_name" yaml:"chain_name"` 12 | RPCAddress string `json:"rpc_address" yaml:"rpc_address"` 13 | RESTAddress string `json:"rest_address" yaml:"rest_address"` 14 | GRPCAddress string `json:"grpc_address" yaml:"grpc_address"` 15 | P2PAddress string `json:"p2p_address" yaml:"p2p_address"` 16 | IBCPath []string `json:"ibc_paths" yaml:"ibc_paths"` 17 | } 18 | -------------------------------------------------------------------------------- /chain/cosmos/08-wasm-types/msgs.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | ) 6 | 7 | var _ sdk.Msg = (*MsgStoreCode)(nil) 8 | 9 | // MsgStoreCode creates a new MsgStoreCode instance 10 | // 11 | //nolint:interfacer 12 | func NewMsgStoreCode(signer string, code []byte) *MsgStoreCode { 13 | return &MsgStoreCode{ 14 | Signer: signer, 15 | WasmByteCode: code, 16 | } 17 | } 18 | 19 | // ValidateBasic implements sdk.Msg 20 | func (m MsgStoreCode) ValidateBasic() error { 21 | return nil 22 | } 23 | 24 | // GetSigners implements sdk.Msg 25 | func (m MsgStoreCode) GetSigners() []sdk.AccAddress { 26 | signer, err := sdk.AccAddressFromBech32(m.Signer) 27 | if err != nil { 28 | panic(err) 29 | } 30 | return []sdk.AccAddress{signer} 31 | } 32 | -------------------------------------------------------------------------------- /local-interchain/interchain/types/genesis.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 4 | 5 | type GenesisAccount struct { 6 | Name string `json:"name"` 7 | Amount string `json:"amount"` 8 | Address string `json:"address"` 9 | Mnemonic string `json:"mnemonic"` 10 | } 11 | 12 | type Genesis struct { 13 | // Only apart of my fork for now. 14 | Modify []cosmos.GenesisKV `json:"modify" yaml:"modify"` // 'key' & 'val' in the config. 15 | 16 | Accounts []GenesisAccount `json:"accounts" yaml:"accounts"` 17 | 18 | // A list of commands which run after chains are good to go. 19 | // May need to move out of genesis into its own section? Seems silly though. 20 | StartupCommands []string `json:"startup_commands" yaml:"startup_commands"` 21 | } 22 | -------------------------------------------------------------------------------- /relayer/capability_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Capability"; DO NOT EDIT. 2 | 3 | package relayer 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[TimestampTimeout-0] 12 | _ = x[HeightTimeout-1] 13 | _ = x[Flush-2] 14 | } 15 | 16 | const _Capability_name = "TimestampTimeoutHeightTimeoutFlush" 17 | 18 | var _Capability_index = [...]uint8{0, 16, 29, 34} 19 | 20 | func (i Capability) String() string { 21 | if i < 0 || i >= Capability(len(_Capability_index)-1) { 22 | return "Capability(" + strconv.FormatInt(int64(i), 10) + ")" 23 | } 24 | return _Capability_name[_Capability_index[i]:_Capability_index[i+1]] 25 | } 26 | -------------------------------------------------------------------------------- /local-interchain/rust/localic-std/src/polling.rs: -------------------------------------------------------------------------------- 1 | use reqwest::blocking::Client as BClient; 2 | 3 | use crate::errors::LocalError; 4 | 5 | /// Polls the API URL for a response. If the response is successful, then the server is running. 6 | /// # Errors 7 | /// 8 | /// If the server does not start within the `wait_seconds`, then an error is returned. 9 | pub fn poll_for_start(c: &BClient, api_url: &str, wait_seconds: u32) -> Result<(), LocalError> { 10 | for i in 0..wait_seconds { 11 | if c.get(api_url).send().is_ok() { 12 | return Ok(()); 13 | } 14 | println!("waiting for server to start (iter:{i}) ({api_url})"); 15 | std::thread::sleep(std::time::Duration::from_secs(1)); 16 | } 17 | 18 | Err(LocalError::ServerDidNotStart {}) 19 | } 20 | 21 | // TODO: polling for a future block (wait_until) delta 22 | -------------------------------------------------------------------------------- /local-interchain/rust/localic-std/src/filesystem.rs: -------------------------------------------------------------------------------- 1 | use crate::{errors::LocalError, transactions::ChainRequestBuilder}; 2 | 3 | /// # Errors 4 | /// 5 | /// Returns `Err` if the files can not be found. 6 | pub fn get_files(rb: &ChainRequestBuilder, absolute_path: &str) -> Result, LocalError> { 7 | let cmd: String = format!("ls {absolute_path}"); 8 | let res = rb.exec(cmd.as_str(), true); 9 | 10 | if let Some(err) = res["error"].as_str() { 11 | return Err(LocalError::GetFilesError { 12 | error: err.to_string(), 13 | }); 14 | }; 15 | 16 | let text = res["text"].as_str(); 17 | 18 | let Some(text) = text else { return Ok(vec![]) }; 19 | 20 | Ok(text 21 | .split('\n') 22 | .filter(|s| !s.is_empty()) 23 | .map(std::string::ToString::to_string) 24 | .collect()) 25 | } 26 | -------------------------------------------------------------------------------- /ibc/tx.go: -------------------------------------------------------------------------------- 1 | package ibc 2 | 3 | import ( 4 | "errors" 5 | 6 | "go.uber.org/multierr" 7 | ) 8 | 9 | // Tx is a generalized IBC transaction. 10 | type Tx struct { 11 | // The block height. 12 | Height int64 13 | // The transaction hash. 14 | TxHash string 15 | // Amount of gas charged to the account. 16 | GasSpent int64 17 | 18 | Packet Packet 19 | } 20 | 21 | // Validate returns an error if the transaction is not well-formed. 22 | func (tx Tx) Validate() error { 23 | var err error 24 | if tx.Height == 0 { 25 | err = multierr.Append(err, errors.New("tx height cannot be 0")) 26 | } 27 | if len(tx.TxHash) == 0 { 28 | err = multierr.Append(err, errors.New("tx hash cannot be empty")) 29 | } 30 | if tx.GasSpent == 0 { 31 | err = multierr.Append(err, errors.New("tx gas spent cannot be 0")) 32 | } 33 | return multierr.Append(err, tx.Packet.Validate()) 34 | } 35 | -------------------------------------------------------------------------------- /blockdb/tui/maincontent_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=mainContent"; DO NOT EDIT. 2 | 3 | package tui 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[testCasesMain-0] 12 | _ = x[cosmosMessagesMain-1] 13 | _ = x[txDetailMain-2] 14 | _ = x[errorModalMain-3] 15 | } 16 | 17 | const _mainContent_name = "testCasesMaincosmosMessagesMaintxDetailMainerrorModalMain" 18 | 19 | var _mainContent_index = [...]uint8{0, 13, 31, 43, 57} 20 | 21 | func (i mainContent) String() string { 22 | if i < 0 || i >= mainContent(len(_mainContent_index)-1) { 23 | return "mainContent(" + strconv.FormatInt(int64(i), 10) + ")" 24 | } 25 | return _mainContent_name[_mainContent_index[i]:_mainContent_index[i+1]] 26 | } 27 | -------------------------------------------------------------------------------- /examples/ibc/README.md: -------------------------------------------------------------------------------- 1 | # More Examples 2 | 3 | Interchain Accounts 4 | 5 | * [interchain_accounts demo](https://gist.github.com/Reecepbcups/8ec46ad83f6c9c1a152c10ab25774335) 6 | * [ibc-go](https://github.com/cosmos/ibc-go/blob/main/e2e/tests/interchain_accounts/base_test.go) 7 | 8 | Interchain Queries 9 | 10 | * [interchain_queries demo](https://gist.github.com/Reecepbcups/d2a1155aaa3a95f5f6daf672e081e8b1) 11 | 12 | Packet Forward Middleware Test: 13 | 14 | * [Noble](https://github.com/noble-assets/noble/blob/v4.0.3/interchaintest/packet_forward_test.go) 15 | * [Juno](https://github.com/CosmosContracts/juno/blob/v21.0.0/interchaintest/module_pfm_test.go) 16 | 17 | IBC Client Update 18 | 19 | * [ibc-go](https://github.com/cosmos/ibc-go/blob/main/e2e/tests/core/02-client/client_test.go) 20 | 21 | IBC Transfers (ICS-20) 22 | 23 | * [ibc-go](https://github.com/cosmos/ibc-go/blob/main/e2e/tests/transfer/base_test.go) 24 | -------------------------------------------------------------------------------- /interchaintest.go: -------------------------------------------------------------------------------- 1 | package interchaintest 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | // CreateLogFile creates a file with name in dir $HOME/.interchaintest/logs/ 10 | func CreateLogFile(name string) (*os.File, error) { 11 | home, err := os.UserHomeDir() 12 | if err != nil { 13 | return nil, fmt.Errorf("user home dir: %w", err) 14 | } 15 | fpath := filepath.Join(home, ".interchaintest", "logs") 16 | err = os.MkdirAll(fpath, 0755) 17 | if err != nil { 18 | return nil, fmt.Errorf("mkdirall: %w", err) 19 | } 20 | return os.Create(filepath.Join(fpath, name)) 21 | } 22 | 23 | // DefaultBlockDatabaseFilepath is the default filepath to the sqlite database for tracking blocks and transactions. 24 | func DefaultBlockDatabaseFilepath() string { 25 | home, err := os.UserHomeDir() 26 | if err != nil { 27 | panic(err) 28 | } 29 | return filepath.Join(home, ".interchaintest", "databases", "block.db") 30 | } 31 | -------------------------------------------------------------------------------- /ibc/tx_test.go: -------------------------------------------------------------------------------- 1 | package ibc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "go.uber.org/multierr" 8 | ) 9 | 10 | func TestTx_Validate(t *testing.T) { 11 | t.Run("valid", func(t *testing.T) { 12 | tx := Tx{ 13 | Height: 1, 14 | TxHash: "abc", 15 | GasSpent: 10, 16 | Packet: validPacket(), 17 | } 18 | 19 | require.NoError(t, tx.Validate()) 20 | }) 21 | 22 | t.Run("invalid", func(t *testing.T) { 23 | var empty Tx 24 | err := empty.Validate() 25 | require.Greater(t, len(multierr.Errors(err)), 1) 26 | 27 | require.Error(t, err) 28 | require.Contains(t, err.Error(), "tx height cannot be 0") 29 | require.Contains(t, err.Error(), "tx hash cannot be empty") 30 | require.Contains(t, err.Error(), "tx gas spent cannot be 0") 31 | 32 | tx := Tx{ 33 | Height: 1, 34 | TxHash: "abc", 35 | GasSpent: 10, 36 | } 37 | require.Error(t, tx.Validate()) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/chores.yml: -------------------------------------------------------------------------------- 1 | name: chores 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | link-check: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: gaurav-nelson/github-action-markdown-link-check@1.0.15 12 | 13 | typos: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Run codespell 18 | continue-on-error: true 19 | run: | 20 | # .codespellrc is used 21 | sudo apt-get install codespell -y 22 | codespell -w --config .codespellrc 23 | exit $? 24 | 25 | pr-title-format: 26 | name: Lint PR Title 27 | permissions: 28 | pull-requests: read 29 | statuses: write 30 | contents: read 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: amannn/action-semantic-pull-request@v5.4.0 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run Unit Tests 2 | 3 | on: 4 | pull_request: 5 | 6 | # Ensures that only a single workflow per PR will run at a time. Cancels in-progress jobs if new commit is pushed. 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | test-unit: 13 | name: unit-tests 14 | runs-on: ubuntu-latest 15 | steps: 16 | # Install and setup go 17 | - name: Set up Go 1.21 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version: '1.21' 21 | 22 | - name: checkout interchaintest 23 | uses: actions/checkout@v4 24 | 25 | # run tests 26 | - name: run unit tests 27 | # -short flag purposefully omitted because there are some longer unit tests 28 | run: go test -race -timeout 10m -failfast -p 2 $(go list ./... | grep -v /cmd | grep -v /examples) 29 | -------------------------------------------------------------------------------- /relayer/hermes/hermes_wallet.go: -------------------------------------------------------------------------------- 1 | package hermes 2 | 3 | import "github.com/strangelove-ventures/interchaintest/v8/ibc" 4 | 5 | var _ ibc.Wallet = &Wallet{} 6 | 7 | type WalletModel struct { 8 | Mnemonic string `json:"mnemonic"` 9 | Address string `json:"address"` 10 | } 11 | 12 | type Wallet struct { 13 | mnemonic string 14 | address string 15 | keyName string 16 | } 17 | 18 | func NewWallet(keyname string, address string, mnemonic string) *Wallet { 19 | return &Wallet{ 20 | mnemonic: mnemonic, 21 | address: address, 22 | keyName: keyname, 23 | } 24 | } 25 | 26 | func (w *Wallet) KeyName() string { 27 | return w.keyName 28 | } 29 | 30 | func (w *Wallet) FormattedAddress() string { 31 | return w.address 32 | } 33 | 34 | // Get mnemonic, only used for relayer wallets 35 | func (w *Wallet) Mnemonic() string { 36 | return w.mnemonic 37 | } 38 | 39 | // Get Address 40 | func (w *Wallet) Address() []byte { 41 | return []byte(w.address) 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/strangelove-project-management.yaml: -------------------------------------------------------------------------------- 1 | name: Strangelove Project Management 2 | on: 3 | issues: 4 | types: 5 | - opened 6 | - reopened 7 | - closed 8 | 9 | jobs: 10 | issue_opened_or_reopened: 11 | name: issue_opened_or_reopened 12 | runs-on: ubuntu-latest 13 | if: | 14 | startsWith(github.repository,'strangelove-ventures/') && 15 | github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'reopened') 16 | steps: 17 | - name: Add Issue to "Motherboard" Project Board 18 | uses: leonsteinhaeuser/project-beta-automations@v2.2.1 19 | with: 20 | gh_app_secret_key: ${{ secrets.MB_SECRET_KEY }} 21 | gh_app_ID: ${{ secrets.MB_APP_ID }} 22 | gh_app_installation_ID: ${{ secrets.MB_INSTALLATION_ID }} 23 | organization: strangelove-ventures 24 | project_id: 4 25 | resource_node_id: ${{ github.event.issue.node_id }} -------------------------------------------------------------------------------- /chain/cosmos/08-wasm-types/codec.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | "github.com/cosmos/cosmos-sdk/types/msgservice" 7 | 8 | "github.com/cosmos/ibc-go/v8/modules/core/exported" 9 | ) 10 | 11 | // RegisterInterfaces registers the tendermint concrete client-related 12 | // implementations and interfaces. 13 | func RegisterInterfaces(registry codectypes.InterfaceRegistry) { 14 | registry.RegisterImplementations( 15 | (*exported.ClientState)(nil), 16 | &ClientState{}, 17 | ) 18 | registry.RegisterImplementations( 19 | (*exported.ConsensusState)(nil), 20 | &ConsensusState{}, 21 | ) 22 | registry.RegisterImplementations( 23 | (*exported.ClientMessage)(nil), 24 | &ClientMessage{}, 25 | ) 26 | registry.RegisterImplementations( 27 | (*sdk.Msg)(nil), 28 | &MsgStoreCode{}, 29 | ) 30 | 31 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release binary 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | env: 8 | GO_VERSION: 1.21 9 | 10 | jobs: 11 | release-static-binary: 12 | permissions: write-all 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout interchaintest 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup go ${{ env.GO_VERSION }} 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version: ${{ env.GO_VERSION }} 22 | 23 | # This must be go install vs make install as to not statically link the binary 24 | # to the worker node. 25 | - run: cd local-interchain && go mod tidy && go install ./... 26 | 27 | - run: cp $HOME/go/bin/local-ic ./local-ic 28 | - run: chmod +x ./local-ic 29 | 30 | - name: Release 31 | uses: softprops/action-gh-release@v2 32 | with: 33 | token: ${{ github.token }} 34 | files: | 35 | local-ic -------------------------------------------------------------------------------- /docs/retainingDataOnFailedTests.md: -------------------------------------------------------------------------------- 1 | # Retaining data on failed tests 2 | 3 | By default, failed tests will clean up any temporary directories they created. 4 | Sometimes when debugging a failed test, it can be more helpful to leave the directory behind 5 | for further manual inspection. 6 | 7 | Setting the environment variable `IBCTEST_SKIP_FAILURE_CLEANUP` to any non-empty value 8 | will cause the test to skip deletion of the temporary directories. 9 | Any tests that fail and skip cleanup will log a message like 10 | `Not removing temporary directory for test at: /tmp/...`. 11 | 12 | Test authors must use 13 | [`interchaintest.TempDir`](https://pkg.go.dev/github.com/strangelove-ventures/interchaintest#TempDir) 14 | instead of `(*testing.T).Cleanup` to opt in to this behavior. 15 | 16 | By default, Docker volumes associated with tests are cleaned up at the end of each test run. 17 | That same `IBCTEST_SKIP_FAILURE_CLEANUP` controls whether the volumes associated with failed tests are pruned. -------------------------------------------------------------------------------- /relayer/rly/wallet.go: -------------------------------------------------------------------------------- 1 | package rly 2 | 3 | import ( 4 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 5 | ) 6 | 7 | var _ ibc.Wallet = &RlyWallet{} 8 | 9 | type WalletModel struct { 10 | Mnemonic string `json:"mnemonic"` 11 | Address string `json:"address"` 12 | } 13 | 14 | type RlyWallet struct { 15 | mnemonic string 16 | address string 17 | keyName string 18 | } 19 | 20 | func NewWallet(keyname string, address string, mnemonic string) *RlyWallet { 21 | return &RlyWallet{ 22 | mnemonic: mnemonic, 23 | address: address, 24 | keyName: keyname, 25 | } 26 | } 27 | 28 | func (w *RlyWallet) KeyName() string { 29 | return w.keyName 30 | } 31 | 32 | func (w *RlyWallet) FormattedAddress() string { 33 | return w.address 34 | } 35 | 36 | // Get mnemonic, only used for relayer wallets 37 | func (w *RlyWallet) Mnemonic() string { 38 | return w.mnemonic 39 | } 40 | 41 | // Get Address 42 | func (w *RlyWallet) Address() []byte { 43 | return []byte(w.address) 44 | } 45 | -------------------------------------------------------------------------------- /blockdb/tui/presenter/test_case.go: -------------------------------------------------------------------------------- 1 | package presenter 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/strangelove-ventures/interchaintest/v8/blockdb" 7 | ) 8 | 9 | // TestCase presents a blockdb.TestCaseResult. 10 | type TestCase struct { 11 | Result blockdb.TestCaseResult 12 | } 13 | 14 | func (p TestCase) ID() string { return strconv.FormatInt(p.Result.ID, 10) } 15 | func (p TestCase) Date() string { return FormatTime(p.Result.CreatedAt) } 16 | func (p TestCase) Name() string { return p.Result.Name } 17 | func (p TestCase) GitSha() string { return p.Result.GitSha } 18 | func (p TestCase) ChainID() string { return p.Result.ChainID } 19 | 20 | func (p TestCase) Height() string { 21 | if !p.Result.ChainHeight.Valid { 22 | return "" 23 | } 24 | return strconv.FormatInt(p.Result.ChainHeight.Int64, 10) 25 | } 26 | 27 | func (p TestCase) TxTotal() string { 28 | if !p.Result.TxTotal.Valid { 29 | return "" 30 | } 31 | return strconv.FormatInt(p.Result.TxTotal.Int64, 10) 32 | } 33 | -------------------------------------------------------------------------------- /chain/cosmos/address.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import ( 4 | "errors" 5 | "github.com/cosmos/cosmos-sdk/types/bech32" 6 | "strings" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | ) 10 | 11 | // AccAddressFromBech32 creates an AccAddress from a Bech32 string. 12 | // https://github.com/cosmos/cosmos-sdk/blob/v0.50.2/types/address.go#L193-L212 13 | func (c *CosmosChain) AccAddressFromBech32(address string) (addr sdk.AccAddress, err error) { 14 | if len(strings.TrimSpace(address)) == 0 { 15 | return sdk.AccAddress{}, errors.New("empty address string is not allowed") 16 | } 17 | 18 | bz, err := sdk.GetFromBech32(address, c.Config().Bech32Prefix) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | err = sdk.VerifyAddressFormat(bz) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | return sdk.AccAddress(bz), nil 29 | } 30 | 31 | func (c *CosmosChain) AccAddressToBech32(addr sdk.AccAddress) (string, error) { 32 | return bech32.ConvertAndEncode(c.Config().Bech32Prefix, addr) 33 | } 34 | -------------------------------------------------------------------------------- /chain/cosmos/config.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | ) 6 | 7 | func SetSDKConfig(bech32Prefix string) *sdk.Config { 8 | var ( 9 | bech32MainPrefix = bech32Prefix 10 | bech32PrefixAccAddr = bech32MainPrefix 11 | bech32PrefixAccPub = bech32MainPrefix + sdk.PrefixPublic 12 | bech32PrefixValAddr = bech32MainPrefix + sdk.PrefixValidator + sdk.PrefixOperator 13 | bech32PrefixValPub = bech32MainPrefix + sdk.PrefixValidator + sdk.PrefixOperator + sdk.PrefixPublic 14 | bech32PrefixConsAddr = bech32MainPrefix + sdk.PrefixValidator + sdk.PrefixConsensus 15 | bech32PrefixConsPub = bech32MainPrefix + sdk.PrefixValidator + sdk.PrefixConsensus + sdk.PrefixPublic 16 | ) 17 | 18 | cfg := sdk.GetConfig() 19 | cfg.SetBech32PrefixForAccount(bech32PrefixAccAddr, bech32PrefixAccPub) 20 | cfg.SetBech32PrefixForValidator(bech32PrefixValAddr, bech32PrefixValPub) 21 | cfg.SetBech32PrefixForConsensusNode(bech32PrefixConsAddr, bech32PrefixConsPub) 22 | return cfg 23 | } 24 | -------------------------------------------------------------------------------- /chain/polkadot/wallet.go: -------------------------------------------------------------------------------- 1 | package polkadot 2 | 3 | import ( 4 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 5 | ) 6 | 7 | var _ ibc.Wallet = &PolkadotWallet{} 8 | 9 | type PolkadotWallet struct { 10 | mnemonic string 11 | address []byte 12 | keyName string 13 | chainCfg ibc.ChainConfig 14 | } 15 | 16 | func NewWallet(keyname string, address []byte, mnemonic string, chainCfg ibc.ChainConfig) *PolkadotWallet { 17 | return &PolkadotWallet{ 18 | mnemonic: mnemonic, 19 | address: address, 20 | keyName: keyname, 21 | chainCfg: chainCfg, 22 | } 23 | } 24 | 25 | func (w *PolkadotWallet) KeyName() string { 26 | return w.keyName 27 | } 28 | 29 | func (w *PolkadotWallet) FormattedAddress() string { 30 | return string(w.address) 31 | } 32 | 33 | // Get mnemonic, only used for relayer wallets 34 | func (w *PolkadotWallet) Mnemonic() string { 35 | return w.mnemonic 36 | } 37 | 38 | // Get Address 39 | // TODO Change to SS58 40 | func (w *PolkadotWallet) Address() []byte { 41 | return w.address 42 | } 43 | -------------------------------------------------------------------------------- /local-interchain/chains/hub.yml: -------------------------------------------------------------------------------- 1 | --- 2 | chains: 3 | - name: gaia 4 | chain_id: localcosmos-1 5 | denom: uatom 6 | binary: gaiad 7 | bech32_prefix: cosmos 8 | docker_image: 9 | version: v10.0.1 10 | gas_prices: 0%DENOM% 11 | chain_type: cosmos 12 | coin_type: 118 13 | trusting_period: 112h 14 | gas_adjustment: 2 15 | number_vals: 1 16 | number_node: 0 17 | debugging: true 18 | block_time: 500ms 19 | genesis: 20 | modify: 21 | - key: app_state.gov.voting_params.voting_period 22 | value: 15s 23 | - key: app_state.gov.deposit_params.max_deposit_period 24 | value: 15s 25 | - key: app_state.gov.deposit_params.min_deposit.0.denom 26 | value: uatom 27 | accounts: 28 | - name: acc0 29 | address: cosmos1hj5fveer5cjtn4wd6wstzugjfdxzl0xpxvjjvr 30 | amount: 10000000000%DENOM% 31 | mnemonic: decorate bright ozone fork gallery riot bus exhaust worth way bone 32 | indoor calm squirrel merry zero scheme cotton until shop any excess stage 33 | laundry 34 | -------------------------------------------------------------------------------- /chain/polkadot/query.go: -------------------------------------------------------------------------------- 1 | package polkadot 2 | 3 | import ( 4 | "cosmossdk.io/math" 5 | gsrpc "github.com/misko9/go-substrate-rpc-client/v4" 6 | gstypes "github.com/misko9/go-substrate-rpc-client/v4/types" 7 | ) 8 | 9 | // GetBalance fetches the current balance for a specific account address using the SubstrateAPI 10 | func GetBalance(api *gsrpc.SubstrateAPI, address string) (math.Int, error) { 11 | meta, err := api.RPC.State.GetMetadataLatest() 12 | if err != nil { 13 | return math.Int{}, err 14 | } 15 | pubKey, err := DecodeAddressSS58(address) 16 | if err != nil { 17 | return math.Int{}, err 18 | } 19 | key, err := gstypes.CreateStorageKey(meta, "System", "Account", pubKey, nil) 20 | if err != nil { 21 | return math.Int{}, err 22 | } 23 | 24 | var accountInfo AccountInfo 25 | ok, err := api.RPC.State.GetStorageLatest(key, &accountInfo) 26 | if err != nil { 27 | return math.Int{}, err 28 | } 29 | if !ok { 30 | return math.Int{}, nil 31 | } 32 | 33 | return math.NewIntFromBigInt(accountInfo.Data.Free.Int), nil 34 | } 35 | -------------------------------------------------------------------------------- /chain/cosmos/node_test.go: -------------------------------------------------------------------------------- 1 | package cosmos_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 8 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestCondenseMoniker_MiddleDetail(t *testing.T) { 13 | start := strings.Repeat("a", stakingtypes.MaxMonikerLength) 14 | end := strings.Repeat("z", stakingtypes.MaxMonikerLength) 15 | 16 | // Two monikers that have the same start and end but only differ in the middle. 17 | // The different piece will be truncated, but the condensed moniker should still differ. 18 | m1 := start + "1" + end 19 | m2 := start + "2" + end 20 | 21 | require.NotEqual(t, cosmos.CondenseMoniker(m1), cosmos.CondenseMoniker(m2)) 22 | 23 | require.LessOrEqual(t, len(cosmos.CondenseMoniker(m1)), stakingtypes.MaxMonikerLength) 24 | } 25 | 26 | func TestCondenseMoniker_Short(t *testing.T) { 27 | const m = "my_moniker" 28 | require.Equal(t, m, cosmos.CondenseMoniker(m)) 29 | } 30 | -------------------------------------------------------------------------------- /relayer/hyperspace/wallet.go: -------------------------------------------------------------------------------- 1 | package hyperspace 2 | 3 | import ( 4 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 5 | ) 6 | 7 | var _ ibc.Wallet = &HyperspaceWallet{} 8 | 9 | type WalletModel struct { 10 | Mnemonic string `json:"mnemonic"` 11 | Address string `json:"address"` 12 | } 13 | 14 | type HyperspaceWallet struct { 15 | mnemonic string 16 | address string 17 | keyName string 18 | } 19 | 20 | func NewWallet(keyname string, address string, mnemonic string) *HyperspaceWallet { 21 | return &HyperspaceWallet{ 22 | mnemonic: mnemonic, 23 | address: address, 24 | keyName: keyname, 25 | } 26 | } 27 | 28 | func (w *HyperspaceWallet) KeyName() string { 29 | return w.keyName 30 | } 31 | 32 | func (w *HyperspaceWallet) FormattedAddress() string { 33 | return w.address 34 | } 35 | 36 | // Get mnemonic, only used for relayer wallets 37 | func (w *HyperspaceWallet) Mnemonic() string { 38 | return w.mnemonic 39 | } 40 | 41 | // Get Address 42 | func (w *HyperspaceWallet) Address() []byte { 43 | return []byte(w.address) 44 | } 45 | -------------------------------------------------------------------------------- /blockdb/migrate_test.go: -------------------------------------------------------------------------------- 1 | package blockdb 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestMigrate(t *testing.T) { 10 | t.Parallel() 11 | 12 | db := emptyDB() 13 | defer db.Close() 14 | 15 | const gitSha = "abc123" 16 | err := Migrate(db, gitSha) 17 | require.NoError(t, err) 18 | 19 | // Tests idempotency. 20 | err = Migrate(db, gitSha) 21 | require.NoError(t, err) 22 | 23 | row := db.QueryRow(`select count(*) from schema_version`) 24 | var count int 25 | err = row.Scan(&count) 26 | 27 | require.NoError(t, err) 28 | require.Equal(t, 1, count) 29 | 30 | err = Migrate(db, "new-sha") 31 | require.NoError(t, err) 32 | 33 | row = db.QueryRow(`select count(*) from schema_version`) 34 | err = row.Scan(&count) 35 | 36 | require.NoError(t, err) 37 | require.Equal(t, 2, count) 38 | 39 | row = db.QueryRow(`select git_sha from schema_version order by id desc limit 1`) 40 | var gotSha string 41 | err = row.Scan(&gotSha) 42 | 43 | require.NoError(t, err) 44 | require.Equal(t, "new-sha", gotSha) 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | golangci: 7 | name: lint 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/setup-go@v5 11 | with: 12 | go-version: '1.21' 13 | cache: false 14 | - uses: actions/checkout@v4 15 | - name: golangci-lint 16 | uses: golangci/golangci-lint-action@v4 17 | with: 18 | version: v1.54 19 | only-new-issues: true 20 | args: --timeout=10m 21 | 22 | clippy-lint: 23 | defaults: 24 | run: 25 | working-directory: local-interchain/rust/localic-std 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: Install stable with clippy and rustfmt 30 | uses: actions-rs/toolchain@v1 31 | with: 32 | profile: minimal 33 | toolchain: stable 34 | components: rustfmt, clippy 35 | - name: Install clippy 36 | run: rustup component add clippy 37 | - name: Update 38 | run: cargo update 39 | - name: Run clippy 40 | run: make lint 41 | 42 | -------------------------------------------------------------------------------- /chain/internal/tendermint/events_test.go: -------------------------------------------------------------------------------- 1 | package tendermint 2 | 3 | import ( 4 | "testing" 5 | 6 | abcitypes "github.com/cometbft/cometbft/abci/types" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestAttributeValue(t *testing.T) { 11 | events := []abcitypes.Event{ 12 | {Type: "1", Attributes: []abcitypes.EventAttribute{ 13 | {Key: "ignore", Value: "should not see me"}, 14 | {Key: "key1", Value: "found1"}, 15 | }}, 16 | {Type: "2", Attributes: []abcitypes.EventAttribute{ 17 | {Key: "key2", Value: "found2"}, 18 | {Key: "ignore", Value: "should not see me"}, 19 | }}, 20 | } 21 | 22 | _, ok := AttributeValue(nil, "test", "") 23 | require.False(t, ok) 24 | 25 | _, ok = AttributeValue(events, "key_not_there", "ignored") 26 | require.False(t, ok) 27 | 28 | _, ok = AttributeValue(events, "1", "attribute not there") 29 | require.False(t, ok) 30 | 31 | found, ok := AttributeValue(events, "1", "key1") 32 | require.True(t, ok) 33 | require.Equal(t, "found1", found) 34 | 35 | found, ok = AttributeValue(events, "2", "key2") 36 | require.True(t, ok) 37 | require.Equal(t, "found2", found) 38 | } 39 | -------------------------------------------------------------------------------- /local-interchain/python/util_base.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import os 4 | 5 | current_dir = os.path.dirname(os.path.realpath(__file__)) 6 | parent_dir = os.path.dirname(current_dir) 7 | 8 | contracts_path = os.path.join(parent_dir, "contracts") 9 | 10 | contracts_json_path = os.path.join(parent_dir, "configs", "contracts.json") 11 | 12 | 13 | # create contracts folder if not already 14 | if not os.path.exists(contracts_path): 15 | os.makedirs(contracts_path, exist_ok=True) 16 | 17 | HOST = "127.0.0.1" 18 | PORT = 8080 19 | 20 | # == Setup global parsers == 21 | parser = argparse.ArgumentParser( 22 | prog="api_test.py", 23 | description="Test the rest server to ensure it functions properly.", 24 | ) 25 | 26 | parser.add_argument( 27 | "--api-address", 28 | type=str, 29 | default=HOST, 30 | help="The host/address to use for the rest server.", 31 | ) 32 | parser.add_argument( 33 | "--api-port", 34 | type=int, 35 | default=PORT, 36 | help="The port to use for the rest server.", 37 | ) 38 | args = parser.parse_args() 39 | 40 | API_URL = f"http://{args.api_address}:{args.api_port}" 41 | -------------------------------------------------------------------------------- /chain/internal/tendermint/events.go: -------------------------------------------------------------------------------- 1 | package tendermint 2 | 3 | import ( 4 | "encoding/base64" 5 | 6 | abcitypes "github.com/cometbft/cometbft/abci/types" 7 | ) 8 | 9 | // AttributeValue returns an event attribute value given the eventType and attribute key tuple. 10 | // In the event of duplicate types and keys, returns the first attribute value found. 11 | // If not found, returns empty string and false. 12 | func AttributeValue(events []abcitypes.Event, eventType, attrKey string) (string, bool) { 13 | for _, event := range events { 14 | if event.Type != eventType { 15 | continue 16 | } 17 | for _, attr := range event.Attributes { 18 | if attr.Key == attrKey { 19 | return attr.Value, true 20 | } 21 | 22 | // tendermint < v0.37-alpha returns base64 encoded strings in events. 23 | key, err := base64.StdEncoding.DecodeString(attr.Key) 24 | if err != nil { 25 | continue 26 | } 27 | if string(key) == attrKey { 28 | value, err := base64.StdEncoding.DecodeString(attr.Value) 29 | if err != nil { 30 | continue 31 | } 32 | return string(value), true 33 | } 34 | } 35 | } 36 | return "", false 37 | } 38 | -------------------------------------------------------------------------------- /chain/penumbra/wallet.go: -------------------------------------------------------------------------------- 1 | package penumbra 2 | 3 | import ( 4 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 5 | ) 6 | 7 | var _ ibc.Wallet = &PenumbraWallet{} 8 | 9 | type PenumbraWallet struct { 10 | mnemonic string 11 | address []byte 12 | keyName string 13 | chainCfg ibc.ChainConfig 14 | } 15 | 16 | func NewWallet(keyname string, address []byte, mnemonic string, chainCfg ibc.ChainConfig) *PenumbraWallet { 17 | return &PenumbraWallet{ 18 | mnemonic: mnemonic, 19 | address: address, 20 | keyName: keyname, 21 | chainCfg: chainCfg, 22 | } 23 | } 24 | 25 | func (w *PenumbraWallet) KeyName() string { 26 | return w.keyName 27 | } 28 | 29 | // Get Address formatted with chain's prefix 30 | func (w *PenumbraWallet) FormattedAddress() string { 31 | return string(w.address) 32 | } 33 | 34 | // Get mnemonic, only used for relayer wallets 35 | func (w *PenumbraWallet) Mnemonic() string { 36 | return w.mnemonic 37 | } 38 | 39 | // Get Address 40 | func (w *PenumbraWallet) Address() []byte { 41 | return w.address 42 | } 43 | 44 | func (w *PenumbraWallet) FormattedAddressWithPrefix(prefix string) string { 45 | return string(w.address) 46 | } 47 | -------------------------------------------------------------------------------- /local-interchain/interchain/faket.go: -------------------------------------------------------------------------------- 1 | package interchain 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // This is a fake impl of testing.T so that ICtest is happy to use it for logging. 9 | 10 | type FakeI interface { 11 | testing.T 12 | } 13 | 14 | type FakeTesting struct { 15 | FakeName string 16 | } 17 | 18 | // impl all of testing.T for FakeTesting 19 | func (t FakeTesting) Name() string { 20 | return t.FakeName 21 | } 22 | 23 | func (t FakeTesting) Cleanup(func()) { 24 | } 25 | 26 | func (t FakeTesting) Skip(...any) { 27 | } 28 | 29 | func (t FakeTesting) Parallel() { 30 | } 31 | 32 | func (t FakeTesting) Failed() bool { 33 | return false 34 | } 35 | 36 | func (t FakeTesting) Skipped() bool { 37 | return false 38 | } 39 | 40 | func (t FakeTesting) Error(...any) { 41 | } 42 | 43 | func (t FakeTesting) Errorf(format string, args ...any) { 44 | } 45 | 46 | func (t FakeTesting) Fail() { 47 | } 48 | 49 | func (t FakeTesting) FailNow() { 50 | } 51 | 52 | func (t FakeTesting) Fatal(...any) { 53 | } 54 | 55 | func (t FakeTesting) Helper() { 56 | } 57 | 58 | func (t FakeTesting) Logf(format string, args ...any) { 59 | fmt.Printf(format, args...) 60 | } 61 | -------------------------------------------------------------------------------- /chain/cosmos/query.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | tmtypes "github.com/cometbft/cometbft/rpc/core/types" 8 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | ) 11 | 12 | type blockClient interface { 13 | Block(ctx context.Context, height *int64) (*tmtypes.ResultBlock, error) 14 | } 15 | 16 | // RangeBlockMessages iterates through all a block's transactions and each transaction's messages yielding to f. 17 | // Return true from f to stop iteration. 18 | func RangeBlockMessages(ctx context.Context, interfaceRegistry codectypes.InterfaceRegistry, client blockClient, height int64, done func(sdk.Msg) bool) error { 19 | h := int64(height) 20 | block, err := client.Block(ctx, &h) 21 | if err != nil { 22 | return fmt.Errorf("tendermint rpc get block: %w", err) 23 | } 24 | for _, txbz := range block.Block.Txs { 25 | tx, err := decodeTX(interfaceRegistry, txbz) 26 | if err != nil { 27 | return fmt.Errorf("decode tendermint tx: %w", err) 28 | } 29 | for _, m := range tx.GetMsgs() { 30 | if ok := done(m); ok { 31 | return nil 32 | } 33 | } 34 | } 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /blockdb/sql.go: -------------------------------------------------------------------------------- 1 | package blockdb 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "os" 8 | "path/filepath" 9 | "time" 10 | 11 | _ "modernc.org/sqlite" 12 | ) 13 | 14 | // ConnectDB connects to the sqlite database at databasePath. 15 | // Auto-creates directory path via MkdirAll. 16 | // Pings database once to ensure connection. 17 | // Pass :memory: as databasePath for in-memory database. 18 | func ConnectDB(ctx context.Context, databasePath string) (*sql.DB, error) { 19 | if databasePath != ":memory:" { 20 | if err := os.MkdirAll(filepath.Dir(databasePath), 0755); err != nil { 21 | return nil, err 22 | } 23 | } 24 | db, err := sql.Open("sqlite", databasePath) 25 | if err != nil { 26 | return nil, fmt.Errorf("open db %s: %w", databasePath, err) 27 | } 28 | // Sqlite does not handle >1 open connections per process well, 29 | // otherwise "database is locked" errors frequently occur. 30 | db.SetMaxOpenConns(1) 31 | err = db.PingContext(ctx) 32 | if err != nil { 33 | _ = db.Close() 34 | return nil, fmt.Errorf("ping db %s: %w", databasePath, err) 35 | } 36 | return db, err 37 | } 38 | 39 | func nowRFC3339() string { 40 | return time.Now().UTC().Format(time.RFC3339) 41 | } 42 | -------------------------------------------------------------------------------- /local-interchain/interchain/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 5 | ) 6 | 7 | type Config struct { 8 | Chains []Chain `json:"chains" yaml:"chains"` 9 | Relayer Relayer `json:"relayer" yaml:"relayer"` 10 | Server RestServer `json:"server" yaml:"server"` 11 | } 12 | 13 | type AppStartConfig struct { 14 | Address string 15 | Port uint16 16 | 17 | Cfg *Config 18 | 19 | Relayer Relayer 20 | AuthKey string // optional password for API interaction 21 | } 22 | 23 | type RestServer struct { 24 | Host string `json:"host" yaml:"host"` 25 | Port string `json:"port" yaml:"port"` 26 | } 27 | 28 | type DockerImage struct { 29 | Repository string `json:"repository" yaml:"repository"` 30 | Version string `json:"version" yaml:"version"` 31 | UidGid string `json:"uid_gid" yaml:"uid_gid"` 32 | } 33 | 34 | type Relayer struct { 35 | DockerImage DockerImage `json:"docker_image" yaml:"docker_image"` 36 | StartupFlags []string `json:"startup_flags" yaml:"startup_flags"` 37 | } 38 | 39 | type IBCChannel struct { 40 | ChainID string `json:"chain_id" yaml:"chain_id"` 41 | Channel *ibc.ChannelOutput `json:"channel" yaml:"channel"` 42 | } 43 | -------------------------------------------------------------------------------- /blockdb/tui/presenter/test_case_test.go: -------------------------------------------------------------------------------- 1 | package presenter 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | "time" 7 | 8 | "github.com/strangelove-ventures/interchaintest/v8/blockdb" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestTestCase(t *testing.T) { 13 | t.Parallel() 14 | 15 | t.Run("happy path", func(t *testing.T) { 16 | result := blockdb.TestCaseResult{ 17 | ID: 321, 18 | Name: "My Test", 19 | GitSha: "sha1", 20 | CreatedAt: time.Now(), 21 | ChainPKey: 789, 22 | ChainID: "chain1", 23 | ChainType: "cosmos", 24 | ChainHeight: sql.NullInt64{Int64: 77, Valid: true}, 25 | TxTotal: sql.NullInt64{Int64: 88, Valid: true}, 26 | } 27 | 28 | pres := TestCase{result} 29 | require.Equal(t, "321", pres.ID()) 30 | require.Equal(t, "My Test", pres.Name()) 31 | require.Equal(t, "sha1", pres.GitSha()) 32 | require.NotEmpty(t, pres.Date()) 33 | require.Equal(t, "chain1", pres.ChainID()) 34 | require.Equal(t, "77", pres.Height()) 35 | require.Equal(t, "88", pres.TxTotal()) 36 | }) 37 | 38 | t.Run("zero state", func(t *testing.T) { 39 | var pres TestCase 40 | 41 | require.Empty(t, pres.Height()) 42 | require.Empty(t, pres.TxTotal()) 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /cmd/interchaintest/matrix_test.go: -------------------------------------------------------------------------------- 1 | package interchaintest_test 2 | 3 | import ( 4 | _ "embed" 5 | "encoding/json" 6 | "testing" 7 | 8 | interchaintest "github.com/strangelove-ventures/interchaintest/v8" 9 | "github.com/stretchr/testify/require" 10 | "go.uber.org/zap/zaptest" 11 | ) 12 | 13 | // Embed the matrix files as strings since they aren't intended to be changed. 14 | var ( 15 | //go:embed example_matrix.json 16 | exampleMatrix string 17 | 18 | //go:embed example_matrix_custom.json 19 | exampleMatrixCustom string 20 | ) 21 | 22 | func TestMatrixValid(t *testing.T) { 23 | type matrix struct { 24 | ChainSets [][]*interchaintest.ChainSpec 25 | } 26 | 27 | for _, tc := range []struct { 28 | name string 29 | j string 30 | }{ 31 | {name: "example_matrix.json", j: exampleMatrix}, 32 | {name: "example_matrix_custom.json", j: exampleMatrixCustom}, 33 | } { 34 | t.Run(tc.name, func(t *testing.T) { 35 | var m matrix 36 | require.NoError(t, json.Unmarshal([]byte(tc.j), &m)) 37 | 38 | for i, cs := range m.ChainSets { 39 | for j, c := range cs { 40 | _, err := c.Config(zaptest.NewLogger(t)) 41 | require.NoErrorf(t, err, "failed to generate config from chainset at index %d-%d", i, j) 42 | } 43 | } 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /local-interchain/interchain/util/replace.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | // Replaces all string values within a complex struct. 9 | func ReplaceStringValues(data interface{}, oldStr, replacement string) { 10 | replaceStringFields(reflect.ValueOf(data), oldStr, replacement) 11 | } 12 | 13 | func replaceStringFields(value reflect.Value, oldStr, replacement string) { 14 | switch value.Kind() { 15 | case reflect.Ptr: 16 | if value.IsNil() { 17 | return 18 | } 19 | replaceStringFields(value.Elem(), oldStr, replacement) 20 | case reflect.Struct: 21 | for i := 0; i < value.NumField(); i++ { 22 | field := value.Field(i) 23 | replaceStringFields(field, oldStr, replacement) 24 | } 25 | case reflect.Slice, reflect.Array: 26 | for i := 0; i < value.Len(); i++ { 27 | replaceStringFields(value.Index(i), oldStr, replacement) 28 | } 29 | case reflect.String: 30 | currentStr := value.String() 31 | if strings.Contains(currentStr, oldStr) { 32 | updatedStr := strings.Replace(currentStr, oldStr, replacement, -1) 33 | value.SetString(updatedStr) 34 | } 35 | case reflect.Map: 36 | for _, key := range value.MapKeys() { 37 | replaceStringFields(value.MapIndex(key), oldStr, replacement) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /relayer/capability.go: -------------------------------------------------------------------------------- 1 | package relayer 2 | 3 | //go:generate go run golang.org/x/tools/cmd/stringer -type=Capability 4 | 5 | // While the relayer capability type may have made a little more sense inside the interchaintest package, 6 | // we would expect individual relayer implementations to specify their own capabilities. 7 | // The interchaintest package depends on the relayer implementations, 8 | // therefore the relayer capability type exists here to avoid a circular dependency. 9 | 10 | // Capability indicates a relayer's support of a given feature. 11 | type Capability int 12 | 13 | // The list of relayer capabilities that interchaintest understands. 14 | const ( 15 | TimestampTimeout Capability = iota 16 | HeightTimeout 17 | 18 | // Whether the relayer supports a one-off flush command. 19 | Flush 20 | ) 21 | 22 | // FullCapabilities returns a mapping of all known relayer features to true, 23 | // indicating that all features are supported. 24 | // FullCapabilities returns a new map every time it is called, 25 | // so callers are free to set one value to false if they support everything but one or two features. 26 | func FullCapabilities() map[Capability]bool { 27 | return map[Capability]bool{ 28 | TimestampTimeout: true, 29 | HeightTimeout: true, 30 | 31 | Flush: true, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chain/cosmos/wallet.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 6 | ) 7 | 8 | var _ ibc.Wallet = &CosmosWallet{} 9 | var _ User = &CosmosWallet{} 10 | 11 | type CosmosWallet struct { 12 | mnemonic string 13 | address []byte 14 | keyName string 15 | chainCfg ibc.ChainConfig 16 | } 17 | 18 | func NewWallet(keyname string, address []byte, mnemonic string, chainCfg ibc.ChainConfig) ibc.Wallet { 19 | return &CosmosWallet{ 20 | mnemonic: mnemonic, 21 | address: address, 22 | keyName: keyname, 23 | chainCfg: chainCfg, 24 | } 25 | } 26 | 27 | func (w *CosmosWallet) KeyName() string { 28 | return w.keyName 29 | } 30 | 31 | // Get formatted address, passing in a prefix 32 | func (w *CosmosWallet) FormattedAddress() string { 33 | return types.MustBech32ifyAddressBytes(w.chainCfg.Bech32Prefix, w.address) 34 | } 35 | 36 | // Get mnemonic, only used for relayer wallets 37 | func (w *CosmosWallet) Mnemonic() string { 38 | return w.mnemonic 39 | } 40 | 41 | // Get Address with chain's prefix 42 | func (w *CosmosWallet) Address() []byte { 43 | return w.address 44 | } 45 | 46 | func (w *CosmosWallet) FormattedAddressWithPrefix(prefix string) string { 47 | return types.MustBech32ifyAddressBytes(prefix, w.address) 48 | } 49 | -------------------------------------------------------------------------------- /local-interchain/rust/main/src/base.rs: -------------------------------------------------------------------------------- 1 | use std::path; 2 | 3 | // Use clap to parse args in the future 4 | pub const API_URL: &str = "http://127.0.0.1:8080"; 5 | 6 | /// local-interchain/rust directory 7 | /// # Panics 8 | /// 9 | /// Will panic if the current directory path is not found. 10 | #[must_use] 11 | pub fn get_current_dir() -> path::PathBuf { 12 | match std::env::current_dir() { 13 | Ok(p) => p, 14 | Err(e) => panic!("Could not get current dir: {e}"), 15 | } 16 | } 17 | 18 | /// local-interchain directory 19 | /// # Panics 20 | /// 21 | /// Will panic if the `local_interchain` directory is not found in the parent path. 22 | #[must_use] 23 | pub fn get_local_interchain_dir() -> path::PathBuf { 24 | let current_dir = get_current_dir(); 25 | let Some(parent_dir) = current_dir.parent() else { panic!("Could not get parent dir") }; 26 | parent_dir.to_path_buf() 27 | } 28 | 29 | /// local-interchain/contracts directory 30 | #[must_use] 31 | pub fn get_contract_path() -> path::PathBuf { 32 | get_local_interchain_dir().join("contracts") 33 | } 34 | 35 | /// local-interchain/configs/contract.json file 36 | #[must_use] 37 | pub fn get_contract_cache_path() -> path::PathBuf { 38 | get_local_interchain_dir() 39 | .join("configs") 40 | .join("contract.json") 41 | } 42 | -------------------------------------------------------------------------------- /blockdb/tui/presenter/highlight.go: -------------------------------------------------------------------------------- 1 | package presenter 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type Highlight struct { 11 | rx *regexp.Regexp 12 | } 13 | 14 | // NewHighlight returns a presenter that adds regions around text that matches searchTerm. 15 | func NewHighlight(searchTerm string) *Highlight { 16 | searchTerm = strings.TrimSpace(searchTerm) 17 | if searchTerm == "" { 18 | return &Highlight{} 19 | } 20 | // Should always be valid with regexp.QuoteMeta. 21 | return &Highlight{rx: regexp.MustCompile(fmt.Sprintf(`(?i)(%s)`, regexp.QuoteMeta(searchTerm)))} 22 | } 23 | 24 | // Text returns the text decorated with tview.TextView regions given the "searchTerm" from NewHighlight. 25 | // The second return value is the highlighted region ids for use with *(tview.TextView).Highlight. 26 | // See https://github.com/rivo/tview/wiki/TextView for more info about regions. 27 | func (h *Highlight) Text(text string) (string, []string) { 28 | if h.rx == nil { 29 | return text, nil 30 | } 31 | var ( 32 | region int 33 | regionIDs []string 34 | ) 35 | final := h.rx.ReplaceAllStringFunc(text, func(s string) string { 36 | id := strconv.Itoa(region) 37 | regionIDs = append(regionIDs, id) 38 | s = fmt.Sprintf(`["%s"]%s[""]`, id, s) 39 | region++ 40 | return s 41 | }) 42 | return final, regionIDs 43 | } 44 | -------------------------------------------------------------------------------- /chain/ethereum/wallet.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common/hexutil" 5 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 6 | ) 7 | 8 | var _ ibc.Wallet = &EthereumWallet{} 9 | 10 | type EthereumWallet struct { 11 | address string 12 | keyName string 13 | } 14 | 15 | func NewWallet(keyname string, address string) ibc.Wallet { 16 | return &EthereumWallet{ 17 | address: address, 18 | keyName: keyname, 19 | } 20 | } 21 | 22 | func (w *EthereumWallet) KeyName() string { 23 | return w.keyName 24 | } 25 | 26 | // Get formatted address, passing in a prefix 27 | func (w *EthereumWallet) FormattedAddress() string { 28 | return w.address 29 | } 30 | 31 | // Get mnemonic, only used for relayer wallets 32 | func (w *EthereumWallet) Mnemonic() string { 33 | return "" 34 | } 35 | 36 | // Get Address with chain's prefix 37 | func (w *EthereumWallet) Address() []byte { 38 | return hexutil.MustDecode(w.address) 39 | } 40 | 41 | type GenesisWallets struct { 42 | total uint32 43 | } 44 | 45 | func NewGenesisWallet() GenesisWallets { 46 | return GenesisWallets{ 47 | total: 2, // Start with 2 at genesis, one for faucet, one for relayer 48 | } 49 | } 50 | 51 | func (w *GenesisWallets) GetFaucetWallet(keyname string) ibc.Wallet { 52 | return NewWallet(keyname, "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") 53 | } 54 | -------------------------------------------------------------------------------- /dockerutil/busybox.go: -------------------------------------------------------------------------------- 1 | package dockerutil 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "sync" 8 | 9 | "github.com/docker/docker/api/types" 10 | "github.com/docker/docker/api/types/filters" 11 | "github.com/docker/docker/client" 12 | ) 13 | 14 | // Allow multiple goroutines to check for busybox 15 | // by using a protected package-level variable. 16 | // 17 | // A mutex allows for retries upon error, if we ever need that; 18 | // whereas a sync.Once would not be simple to retry. 19 | var ( 20 | ensureBusyboxMu sync.Mutex 21 | hasBusybox bool 22 | ) 23 | 24 | const busyboxRef = "busybox:stable" 25 | 26 | func EnsureBusybox(ctx context.Context, cli *client.Client) error { 27 | ensureBusyboxMu.Lock() 28 | defer ensureBusyboxMu.Unlock() 29 | 30 | if hasBusybox { 31 | return nil 32 | } 33 | 34 | images, err := cli.ImageList(ctx, types.ImageListOptions{ 35 | Filters: filters.NewArgs(filters.Arg("reference", busyboxRef)), 36 | }) 37 | if err != nil { 38 | return fmt.Errorf("listing images to check busybox presence: %w", err) 39 | } 40 | 41 | if len(images) > 0 { 42 | hasBusybox = true 43 | return nil 44 | } 45 | 46 | rc, err := cli.ImagePull(ctx, busyboxRef, types.ImagePullOptions{}) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | _, _ = io.Copy(io.Discard, rc) 52 | _ = rc.Close() 53 | 54 | hasBusybox = true 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /cmd/interchaintest/example_matrix_custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "Relayers": ["rly"], 3 | 4 | "ChainSets": [ 5 | [ 6 | { 7 | "NumValidators": 2, 8 | "NumFullNodes": 1, 9 | 10 | "Type": "cosmos", 11 | "Name": "gaia", 12 | "ChainID": "cosmoshub-1004", 13 | "Images": [ 14 | { 15 | "Repository": "ghcr.io/strangelove-ventures/heighliner/gaia", 16 | "Version": "latest", 17 | "UidGid": "1025:1025" 18 | } 19 | ], 20 | "Bin": "gaiad", 21 | "Bech32Prefix": "cosmos", 22 | "Denom": "uatom", 23 | "GasPrices": "0.01uatom", 24 | "GasAdjustment": 1.3, 25 | "TrustingPeriod": "504h" 26 | }, 27 | { 28 | "NumValidators": 2, 29 | "NumFullNodes": 1, 30 | 31 | "Type": "cosmos", 32 | "Name": "osmosis", 33 | "ChainID": "osmosis-1001", 34 | "Images": [ 35 | { 36 | "Repository": "ghcr.io/strangelove-ventures/heighliner/osmosis", 37 | "Version": "latest", 38 | "UidGid": "1025:1025" 39 | } 40 | ], 41 | "Bin": "osmosisd", 42 | "Bech32Prefix": "osmo", 43 | "Denom": "uosmo", 44 | "GasPrices": "0.0uosmo", 45 | "GasAdjustment": 1.3, 46 | "TrustingPeriod": "336h" 47 | } 48 | ] 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /relayer/hyperspace/hyperspace_test.go: -------------------------------------------------------------------------------- 1 | package hyperspace_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/strangelove-ventures/interchaintest/v8/relayer/hyperspace" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestKeys(t *testing.T) { 11 | bech32Prefix := "cosmos" 12 | coinType := "118" 13 | mnemonic := "taste shoot adapt slow truly grape gift need suggest midnight burger horn whisper hat vast aspect exit scorpion jewel axis great area awful blind" 14 | 15 | expectedKeyEntry := hyperspace.KeyEntry{ 16 | PublicKey: "xpub6G1GwQBqWwXuCRhri9q1JzxZ9eMWFazo2ssoZNkAsqusDTT6MPUXiPaXMJS9v4RVaSmYPhA1HK5RCD7WPutmUn3eeqXduM142X7YRVBx8bn", 17 | PrivateKey: "xprvA31vXtewgZybywdPc8Hzws1pbcX1r8GwfexCkzLZKWNtLf7worAHAbG3W3F1SagK47ng5877ihXkDvmNfZnVHSGw7Ad1JkzyPTKEtSpmSxa", 18 | Address: []byte{69, 6, 166, 110, 97, 215, 215, 210, 224, 48, 93, 126, 44, 86, 4, 36, 109, 137, 43, 242}, 19 | Account: "cosmos1g5r2vmnp6lta9cpst4lzc4syy3kcj2lj0nuhmy", 20 | } 21 | 22 | keyEntry := hyperspace.GenKeyEntry(bech32Prefix, coinType, mnemonic) 23 | require.Equal(t, expectedKeyEntry.PublicKey, keyEntry.PublicKey, "PublicKey is wrong") 24 | require.Equal(t, expectedKeyEntry.PrivateKey, keyEntry.PrivateKey, "PrivateKey is wrong") 25 | require.Equal(t, expectedKeyEntry.Account, keyEntry.Account, "Account is wrong") 26 | require.Equal(t, expectedKeyEntry.Address, keyEntry.Address, "Address is wrong") 27 | } 28 | -------------------------------------------------------------------------------- /blockdb/test_case.go: -------------------------------------------------------------------------------- 1 | package blockdb 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | ) 7 | 8 | // TestCase is a single test invocation. 9 | type TestCase struct { 10 | db *sql.DB 11 | id int64 12 | } 13 | 14 | // CreateTestCase starts tracking new test case with testName. 15 | func CreateTestCase(ctx context.Context, db *sql.DB, testName, gitSha string) (*TestCase, error) { 16 | res, err := db.ExecContext(ctx, `INSERT INTO test_case(name, created_at, git_sha) VALUES(?, ?, ?)`, testName, nowRFC3339(), gitSha) 17 | if err != nil { 18 | return nil, err 19 | } 20 | id, err := res.LastInsertId() 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &TestCase{ 25 | db: db, 26 | id: id, 27 | }, nil 28 | } 29 | 30 | // AddChain tracks and attaches a chain to the test case. 31 | // The chainID must be unique per test case. E.g. osmosis-1001, cosmos-1004 32 | // The chainType denotes which ecosystem the chain belongs to. E.g. cosmos, penumbra, composable, etc. 33 | func (tc *TestCase) AddChain(ctx context.Context, chainID, chainType string) (*Chain, error) { 34 | res, err := tc.db.ExecContext(ctx, `INSERT INTO chain(chain_id, chain_type, fk_test_id) VALUES(?, ?, ?)`, chainID, chainType, tc.id) 35 | if err != nil { 36 | return nil, err 37 | } 38 | id, err := res.LastInsertId() 39 | if err != nil { 40 | return nil, err 41 | } 42 | return &Chain{ 43 | id: id, 44 | db: tc.db, 45 | }, nil 46 | } 47 | -------------------------------------------------------------------------------- /chain/cosmos/module_slashing.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import ( 4 | "context" 5 | 6 | slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" 7 | ) 8 | 9 | // SlashingUnJail unjails a validator. 10 | func (tn *ChainNode) SlashingUnJail(ctx context.Context, keyName string) error { 11 | _, err := tn.ExecTx(ctx, 12 | keyName, "slashing", "unjail", 13 | ) 14 | return err 15 | } 16 | 17 | // SlashingGetParams returns slashing params 18 | func (c *CosmosChain) SlashingQueryParams(ctx context.Context) (*slashingtypes.Params, error) { 19 | res, err := slashingtypes.NewQueryClient(c.GetNode().GrpcConn). 20 | Params(ctx, &slashingtypes.QueryParamsRequest{}) 21 | return &res.Params, err 22 | } 23 | 24 | // SlashingSigningInfo returns signing info for a validator 25 | func (c *CosmosChain) SlashingQuerySigningInfo(ctx context.Context, consAddress string) (*slashingtypes.ValidatorSigningInfo, error) { 26 | res, err := slashingtypes.NewQueryClient(c.GetNode().GrpcConn). 27 | SigningInfo(ctx, &slashingtypes.QuerySigningInfoRequest{ConsAddress: consAddress}) 28 | return &res.ValSigningInfo, err 29 | } 30 | 31 | // SlashingSigningInfos returns all signing infos 32 | func (c *CosmosChain) SlashingQuerySigningInfos(ctx context.Context) ([]slashingtypes.ValidatorSigningInfo, error) { 33 | res, err := slashingtypes.NewQueryClient(c.GetNode().GrpcConn). 34 | SigningInfos(ctx, &slashingtypes.QuerySigningInfosRequest{}) 35 | return res.Info, err 36 | } 37 | -------------------------------------------------------------------------------- /relayer/options.go: -------------------------------------------------------------------------------- 1 | package relayer 2 | 3 | import ( 4 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 5 | ) 6 | 7 | // RelayerOpt is a functional option for configuring a relayer. 8 | type RelayerOpt func(relayer *DockerRelayer) 9 | 10 | // DockerImage overrides the default relayer docker image. 11 | func DockerImage(image *ibc.DockerImage) RelayerOpt { 12 | return func(r *DockerRelayer) { 13 | r.customImage = image 14 | } 15 | } 16 | 17 | // CustomDockerImage overrides the default relayer docker image. 18 | // uidGid is the uid:gid format owner that should be used within the container. 19 | // If uidGid is empty, root user will be assumed. 20 | func CustomDockerImage(repository string, version string, uidGid string) RelayerOpt { 21 | return DockerImage(&ibc.DockerImage{ 22 | Repository: repository, 23 | Version: version, 24 | UidGid: uidGid, 25 | }) 26 | } 27 | 28 | // HomeDir overrides the default relayer home directory. 29 | func HomeDir(homeDir string) RelayerOpt { 30 | return func(r *DockerRelayer) { 31 | r.homeDir = homeDir 32 | } 33 | } 34 | 35 | // ImagePull overrides whether the relayer image should be pulled on startup. 36 | func ImagePull(pull bool) RelayerOpt { 37 | return func(r *DockerRelayer) { 38 | r.pullImage = pull 39 | } 40 | } 41 | 42 | // StartupFlags overrides the default relayer startup flags. 43 | func StartupFlags(flags ...string) RelayerOpt { 44 | return func(r *DockerRelayer) { 45 | r.extraStartupFlags = flags 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOCKER := $(shell which docker) 2 | protoVer=0.13.2 3 | protoImageName=ghcr.io/cosmos/proto-builder:$(protoVer) 4 | protoImage=$(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace $(protoImageName) 5 | 6 | default: help 7 | 8 | .PHONY: help 9 | help: ## Print this help message 10 | @echo "Available make commands:"; grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 11 | 12 | .PHONY: interchaintest 13 | interchaintest: gen ## Build interchaintest binary into ./bin 14 | go test -ldflags "-X github.com/strangelove-ventures/interchaintest/v8/interchaintest.GitSha=$(shell git describe --always --dirty)" -c -o ./bin/interchaintest ./cmd/interchaintest 15 | 16 | .PHONY: test 17 | test: ## Run unit tests 18 | @go test -cover -short -race -timeout=60s ./... 19 | 20 | .PHONY: docker-reset 21 | docker-reset: ## Attempt to delete all running containers. Useful if interchaintest does not exit cleanly. 22 | @docker stop $(shell docker ps -q) &>/dev/null || true 23 | @docker rm --force $(shell docker ps -q) &>/dev/null || true 24 | 25 | .PHONY: docker-mac-nuke 26 | docker-mac-nuke: ## macOS only. Try docker-reset first. Kills and restarts Docker Desktop. 27 | killall -9 Docker && open /Applications/Docker.app 28 | 29 | .PHONY: gen 30 | gen: ## Run code generators 31 | go generate ./... 32 | 33 | .PHONY: proto-gen 34 | proto-gen: ## Generate code from protos 35 | @echo "Generating Protobuf files" 36 | @$(protoImage) sh ./scripts/protocgen.sh -------------------------------------------------------------------------------- /local-interchain/rust/localic-std/src/modules/bank.rs: -------------------------------------------------------------------------------- 1 | use cosmwasm_std::Coin; 2 | use serde_json::Value; 3 | 4 | use crate::{errors::LocalError, transactions::ChainRequestBuilder, types::get_coin_from_json}; 5 | 6 | /// # Errors 7 | /// 8 | /// Returns `Err` if the transaction fails (ex: not enough balance, fees, or gas). 9 | pub fn send( 10 | rb: &ChainRequestBuilder, 11 | from_key: &str, 12 | to_address: &str, 13 | tokens: &[Coin], 14 | fee: &Coin, 15 | ) -> Result { 16 | let str_coins = tokens 17 | .iter() 18 | .map(|coin| format!("{}{}", coin.amount, coin.denom)) 19 | .collect::>() 20 | .join(","); 21 | 22 | let cmd = 23 | format!("tx bank send {from_key} {to_address} {str_coins} --fees={fee} --output=json"); 24 | rb.tx(&cmd, true) 25 | } 26 | 27 | pub fn get_balance(req_builder: &ChainRequestBuilder, address: &str) -> Vec { 28 | let res = req_builder.query(&format!("q bank balances {address}"), false); 29 | let Some(balances) = res["balances"].as_array() else { return vec![] }; 30 | 31 | let coins: Vec = balances.iter().map(get_coin_from_json).collect(); 32 | coins 33 | } 34 | 35 | pub fn get_total_supply(req_builder: &ChainRequestBuilder) -> Vec { 36 | let res = req_builder.query("q bank total", false); 37 | let Some(supplies) = res["supply"].as_array() else { return vec![] }; 38 | 39 | let coins: Vec = supplies.iter().map(get_coin_from_json).collect(); 40 | coins 41 | } 42 | -------------------------------------------------------------------------------- /local-interchain/interchain/handlers/types.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 7 | ) 8 | 9 | type IbcChainConfigAlias struct { 10 | Type string `json:"type" yaml:"type"` 11 | Name string `json:"name" yaml:"name"` 12 | ChainID string `json:"chain_id" yaml:"chain_id"` 13 | Bin string `json:"bin" yaml:"bin"` 14 | Bech32Prefix string `json:"bech32_prefix" yaml:"bech32_prefix"` 15 | Denom string `json:"denom" yaml:"denom"` 16 | CoinType string `json:"coin_type" yaml:"coin_type"` 17 | GasPrices string `json:"gas_prices" yaml:"gas_prices"` 18 | GasAdjustment float64 `json:"gas_adjustment" yaml:"gas_adjustment"` 19 | TrustingPeriod string `json:"trusting_period" yaml:"trusting_period"` 20 | } 21 | 22 | func (c *IbcChainConfigAlias) Marshal() ([]byte, error) { 23 | return json.Marshal(c) 24 | } 25 | 26 | func MarshalIBCChainConfig(cfg ibc.ChainConfig) ([]byte, error) { 27 | jsonRes, err := json.MarshalIndent(IbcChainConfigAlias{ 28 | Type: cfg.Type, 29 | Name: cfg.Name, 30 | ChainID: cfg.ChainID, 31 | Bin: cfg.Bin, 32 | Bech32Prefix: cfg.Bech32Prefix, 33 | Denom: cfg.Denom, 34 | CoinType: cfg.CoinType, 35 | GasPrices: cfg.GasPrices, 36 | GasAdjustment: cfg.GasAdjustment, 37 | TrustingPeriod: cfg.TrustingPeriod, 38 | }, "", " ") 39 | if err != nil { 40 | return nil, err 41 | } 42 | return jsonRes, nil 43 | } 44 | -------------------------------------------------------------------------------- /local-interchain/rust/localic-std/src/errors.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug, Clone)] 4 | pub enum LocalError { 5 | #[error("{{msg}}")] 6 | Custom { msg: String }, 7 | 8 | #[error("This err returned is NotImplemented")] 9 | NotImplemented {}, 10 | 11 | #[error("the transaction hash was not found.")] 12 | TxHashNotFound {}, 13 | 14 | #[error("file upload failed for path: {path}. reason: {reason}")] 15 | UploadFailed { path: String, reason: String }, 16 | 17 | #[error("transaction was not successful. status: {code_status}. log: {raw_log}")] 18 | TxNotSuccessful { code_status: i64, raw_log: String }, 19 | 20 | #[error("contract address not found. {events}")] 21 | ContractAddressNotFound { events: String }, 22 | 23 | #[error("key_bech32 failed. reason: {reason}")] 24 | KeyBech32Failed { reason: String }, 25 | 26 | #[error("This CosmWasm object has no value for: {value_type}")] 27 | CWValueIsNone { value_type: String }, 28 | 29 | #[error("API URL is not found.")] 30 | ApiNotFound {}, 31 | 32 | #[error("Chain ID is not found.")] 33 | ChainIdNotFound {}, 34 | 35 | #[error("The local-interchain API server did not start in time.")] 36 | ServerDidNotStart {}, 37 | 38 | #[error("Could not find the SDK status code in the Tx data. {reason}. tx_res: {tx_res}")] 39 | SdkTransactionStatusCodeNotFound { reason: String, tx_res: String }, 40 | 41 | #[error("Could not get filesystem files. {error}")] 42 | GetFilesError { error: String }, 43 | } 44 | -------------------------------------------------------------------------------- /chain/cosmos/genesis.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/icza/dyno" 10 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 11 | ) 12 | 13 | type GenesisKV struct { 14 | Key string `json:"key"` 15 | Value interface{} `json:"value"` 16 | } 17 | 18 | func NewGenesisKV(key string, value interface{}) GenesisKV { 19 | return GenesisKV{ 20 | Key: key, 21 | Value: value, 22 | } 23 | } 24 | 25 | func ModifyGenesis(genesisKV []GenesisKV) func(ibc.ChainConfig, []byte) ([]byte, error) { 26 | return func(chainConfig ibc.ChainConfig, genbz []byte) ([]byte, error) { 27 | g := make(map[string]interface{}) 28 | if err := json.Unmarshal(genbz, &g); err != nil { 29 | return nil, fmt.Errorf("failed to unmarshal genesis file: %w", err) 30 | } 31 | 32 | for idx, values := range genesisKV { 33 | splitPath := strings.Split(values.Key, ".") 34 | 35 | path := make([]interface{}, len(splitPath)) 36 | for i, component := range splitPath { 37 | if v, err := strconv.Atoi(component); err == nil { 38 | path[i] = v 39 | } else { 40 | path[i] = component 41 | } 42 | } 43 | 44 | if err := dyno.Set(g, values.Value, path...); err != nil { 45 | return nil, fmt.Errorf("failed to set value (index:%d) in genesis json: %w", idx, err) 46 | } 47 | } 48 | 49 | out, err := json.Marshal(g) 50 | if err != nil { 51 | return nil, fmt.Errorf("failed to marshal genesis bytes to json: %w", err) 52 | } 53 | return out, nil 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /local-interchain/cmd/local-ic/chains.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "path" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | type chains struct { 13 | Configs []string `json:"chain_configs" yaml:"chain_configs"` 14 | } 15 | 16 | var chainsCmd = &cobra.Command{ 17 | Use: "chains [config.(json|yaml)]", 18 | Short: "List all current chains or outputs a current config information", 19 | Args: cobra.RangeArgs(0, 1), 20 | ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 21 | return GetFiles(), cobra.ShellCompDirectiveNoFileComp 22 | }, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | chainsDir := path.Join(GetDirectory(), "chains") 25 | 26 | if len(args) == 0 { 27 | configs := chains{GetFiles()} 28 | 29 | bz, _ := json.MarshalIndent(configs, "", " ") 30 | fmt.Printf("%s\n", bz) 31 | } else { 32 | config := args[0] 33 | filePath := path.Join(chainsDir, config) 34 | 35 | fc, err := os.ReadFile(filePath) 36 | if err != nil { 37 | fmt.Println(err) 38 | os.Exit(1) 39 | } 40 | 41 | fmt.Println(string(fc)) 42 | } 43 | }, 44 | } 45 | 46 | func GetFiles() []string { 47 | chainsDir := path.Join(GetDirectory(), "chains") 48 | 49 | files, err := os.ReadDir(chainsDir) 50 | if err != nil { 51 | fmt.Println(err) 52 | os.Exit(1) 53 | } 54 | 55 | var fileNames []string 56 | for _, file := range files { 57 | fileNames = append(fileNames, file.Name()) 58 | } 59 | 60 | return fileNames 61 | } 62 | -------------------------------------------------------------------------------- /penumbra.go: -------------------------------------------------------------------------------- 1 | package interchaintest 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/strangelove-ventures/interchaintest/v8/chain/penumbra" 8 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 9 | ) 10 | 11 | // ErrPclientdInitialization is returned if the CreateClientNode call fails while initializing a new instance of 12 | // pclientd for a newly created user account on Penumbra. 13 | var ErrPclientdInitialization = fmt.Errorf("failed to initialize new pclientd instance") 14 | 15 | // CreatePenumbraClient should be called after a new test user account has been created on Penumbra. 16 | // This function initializes a new instance of pclientd which allows private user state to be tracked and managed 17 | // via a client daemon i.e. it is used to sign and broadcast txs as well as querying private user state on chain. 18 | // 19 | // Note: this function cannot be called until the chain is started as pclientd attempts to dial the running pd instance, 20 | // so that it can sync with the current chain tip. It also should be noted that this function should ONLY be called 21 | // after a new test user has been generated via one of the GetAndFundTestUser helper functions or a call to the 22 | // chain.CreateKey or chain.RecoverKey methods. 23 | func CreatePenumbraClient(ctx context.Context, c ibc.Chain, keyName string) error { 24 | if pen, ok := c.(*penumbra.PenumbraChain); ok { 25 | err := pen.CreateClientNode(ctx, keyName) 26 | if err != nil { 27 | return fmt.Errorf("%w for keyname %s: %w", ErrPclientdInitialization, keyName, err) 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /blockdb/tui/presenter/highlight_test.go: -------------------------------------------------------------------------------- 1 | package presenter 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestHighlighter_Text(t *testing.T) { 10 | t.Run("happy path", func(t *testing.T) { 11 | h := NewHighlight("\tDay ") 12 | 13 | got, regions := h.Text(highlighterFixture) 14 | 15 | const want = `Tomorrow, and tomorrow, and tomorrow, 16 | Creeps in this petty pace from ["0"]day[""] to ["1"]day[""], 17 | To the last syllable of recorded time; 18 | And all our yester["2"]day[""]s have lighted fools 19 | The way to dusty death. Out, out, brief candle!` 20 | require.Equal(t, want, got) 21 | require.Equal(t, []string{"0", "1", "2"}, regions) 22 | }) 23 | 24 | t.Run("ignores regex meta characters", func(t *testing.T) { 25 | h := NewHighlight("(one paren") 26 | got, regions := h.Text("(one paren") 27 | 28 | require.Equal(t, `["0"](one paren[""]`, got) 29 | require.Equal(t, []string{"0"}, regions) 30 | }) 31 | 32 | t.Run("missing search term", func(t *testing.T) { 33 | h := NewHighlight("") 34 | got, regions := h.Text(highlighterFixture) 35 | 36 | require.Empty(t, regions) 37 | require.Equal(t, highlighterFixture, got) 38 | 39 | h = NewHighlight(" \t") 40 | got, regions = h.Text(highlighterFixture) 41 | 42 | require.Empty(t, regions) 43 | require.Equal(t, highlighterFixture, got) 44 | }) 45 | } 46 | 47 | const highlighterFixture = `Tomorrow, and tomorrow, and tomorrow, 48 | Creeps in this petty pace from day to day, 49 | To the last syllable of recorded time; 50 | And all our yesterdays have lighted fools 51 | The way to dusty death. Out, out, brief candle!` 52 | -------------------------------------------------------------------------------- /blockdb/tui/presenter/tx.go: -------------------------------------------------------------------------------- 1 | package presenter 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "strconv" 7 | "sync" 8 | 9 | "github.com/strangelove-ventures/interchaintest/v8/blockdb" 10 | ) 11 | 12 | var bufPool = sync.Pool{New: func() any { return new(bytes.Buffer) }} 13 | 14 | type Tx struct { 15 | Result blockdb.TxResult 16 | } 17 | 18 | func (tx Tx) Height() string { return strconv.FormatInt(tx.Result.Height, 10) } 19 | 20 | // Data attempts to pretty print JSON. If not valid JSON, returns tx data as-is which may not be human-readable. 21 | func (tx Tx) Data() string { 22 | buf := bufPool.Get().(*bytes.Buffer) 23 | defer bufPool.Put(buf) 24 | defer buf.Reset() 25 | 26 | if err := json.Indent(buf, tx.Result.Tx, "", " "); err != nil { 27 | return string(tx.Result.Tx) 28 | } 29 | return buf.String() 30 | } 31 | 32 | type Txs []blockdb.TxResult 33 | 34 | // ToJSON always renders valid JSON given the blockdb.TxResult. 35 | // If the tx data is not valid JSON, the tx data is represented as a base64 encoded string. 36 | func (txs Txs) ToJSON() []byte { 37 | type jsonObj struct { 38 | Height int64 39 | Tx json.RawMessage 40 | } 41 | type jsonBytes struct { 42 | Height int64 43 | Tx []byte 44 | } 45 | objs := make([]any, len(txs)) 46 | for i, tx := range txs { 47 | if !json.Valid(tx.Tx) { 48 | objs[i] = jsonBytes{ 49 | Height: tx.Height, 50 | Tx: tx.Tx, 51 | } 52 | continue 53 | } 54 | objs[i] = jsonObj{ 55 | Height: tx.Height, 56 | Tx: tx.Tx, 57 | } 58 | } 59 | b, err := json.Marshal(objs) 60 | if err != nil { 61 | // json.Valid check above should prevent an error here. 62 | panic(err) 63 | } 64 | return b 65 | } 66 | -------------------------------------------------------------------------------- /blockdb/tui/presenter/tx_test.go: -------------------------------------------------------------------------------- 1 | package presenter 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/strangelove-ventures/interchaintest/v8/blockdb" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestTx(t *testing.T) { 12 | t.Parallel() 13 | 14 | t.Run("json", func(t *testing.T) { 15 | tx := blockdb.TxResult{ 16 | Height: 13, 17 | Tx: []byte(`{"json":{"foo":true}}`), 18 | } 19 | require.True(t, json.Valid(tx.Tx)) // sanity check 20 | 21 | pres := Tx{tx} 22 | require.Equal(t, "13", pres.Height()) 23 | 24 | const want = `{ 25 | "json": { 26 | "foo": true 27 | } 28 | }` 29 | require.Equal(t, want, pres.Data()) 30 | }) 31 | 32 | t.Run("non-json", func(t *testing.T) { 33 | tx := blockdb.TxResult{ 34 | Tx: []byte(`some data`), 35 | } 36 | pres := Tx{tx} 37 | require.Equal(t, "some data", pres.Data()) 38 | }) 39 | } 40 | 41 | func TestTxs_ToJSON(t *testing.T) { 42 | t.Run("happy path", func(t *testing.T) { 43 | txs := Txs{ 44 | {Height: 1, Tx: []byte(`{"num":1}`)}, 45 | {Height: 3, Tx: []byte(`{"num":3}`)}, 46 | {Height: 5, Tx: []byte(`{"num":5}`)}, 47 | } 48 | 49 | const want = `[ 50 | { "Height": 1, "Tx": { "num": 1 } }, 51 | { "Height": 3, "Tx": { "num": 3 } }, 52 | { "Height": 5, "Tx": { "num": 5 } } 53 | ]` 54 | require.JSONEq(t, want, string(txs.ToJSON())) 55 | }) 56 | 57 | t.Run("invalid json", func(t *testing.T) { 58 | txs := Txs{ 59 | {Height: 1, Tx: []byte(`{"num":1}`)}, 60 | {Height: 2, Tx: []byte(`not valid`)}, 61 | } 62 | 63 | const want = `[ 64 | { "Height": 1, "Tx": { "num": 1 } }, 65 | { "Height": 2, "Tx": "bm90IHZhbGlk" } 66 | ]` 67 | require.JSONEq(t, want, string(txs.ToJSON())) 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /ibc/relayer_test.go: -------------------------------------------------------------------------------- 1 | package ibc 2 | 3 | import ( 4 | "testing" 5 | 6 | chantypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestChannelOptsConfigured(t *testing.T) { 11 | // Test the default channel opts 12 | opts := DefaultChannelOpts() 13 | require.NoError(t, opts.Validate()) 14 | 15 | // Test empty struct channel opts 16 | opts = CreateChannelOptions{} 17 | require.Error(t, opts.Validate()) 18 | 19 | // Test invalid Order type in channel opts 20 | opts = CreateChannelOptions{ 21 | SourcePortName: "transfer", 22 | DestPortName: "transfer", 23 | Order: 3, 24 | Version: "123", 25 | } 26 | require.Error(t, opts.Validate()) 27 | require.Equal(t, chantypes.ErrInvalidChannelOrdering, opts.Order.Validate()) 28 | 29 | // Test partial channel opts 30 | opts = CreateChannelOptions{ 31 | SourcePortName: "", 32 | DestPortName: "", 33 | Order: 0, 34 | } 35 | require.Error(t, opts.Validate()) 36 | } 37 | 38 | func TestClientOptsConfigured(t *testing.T) { 39 | // Test the default client opts 40 | opts := DefaultClientOpts() 41 | require.NoError(t, opts.Validate()) 42 | 43 | // Test empty struct client opts 44 | opts = CreateClientOptions{} 45 | require.NoError(t, opts.Validate()) 46 | 47 | // Test partial client opts 48 | opts = CreateClientOptions{ 49 | MaxClockDrift: "5m", 50 | } 51 | require.NoError(t, opts.Validate()) 52 | 53 | // Test invalid MaxClockDrift 54 | opts = CreateClientOptions{ 55 | MaxClockDrift: "invalid duration", 56 | } 57 | require.Error(t, opts.Validate()) 58 | 59 | // Test invalid TrustingPeriod 60 | opts = CreateClientOptions{ 61 | TrustingPeriod: "invalid duration", 62 | } 63 | require.Error(t, opts.Validate()) 64 | } 65 | -------------------------------------------------------------------------------- /local-interchain/chains/cosmoshub.json: -------------------------------------------------------------------------------- 1 | { 2 | "chains": [ 3 | { 4 | "name": "gaia", 5 | "chain_id": "localcosmos-1", 6 | "denom": "uatom", 7 | "binary": "gaiad", 8 | "bech32_prefix": "cosmos", 9 | "docker_image": { 10 | "version": "v10.0.1" 11 | }, 12 | "gas_prices": "0%DENOM%", 13 | "chain_type": "cosmos", 14 | "coin_type": 118, 15 | "trusting_period": "112h", 16 | "gas_adjustment": 2.0, 17 | "number_vals": 1, 18 | "number_node": 0, 19 | "debugging": true, 20 | "block_time": "500ms", 21 | "genesis": { 22 | "modify": [ 23 | { 24 | "key": "app_state.gov.voting_params.voting_period", 25 | "value": "15s" 26 | }, 27 | { 28 | "key": "app_state.gov.deposit_params.max_deposit_period", 29 | "value": "15s" 30 | }, 31 | { 32 | "key": "app_state.gov.deposit_params.min_deposit.0.denom", 33 | "value": "uatom" 34 | } 35 | ], 36 | "accounts": [ 37 | { 38 | "name": "acc0", 39 | "address": "cosmos1hj5fveer5cjtn4wd6wstzugjfdxzl0xpxvjjvr", 40 | "amount": "10000000000%DENOM%", 41 | "mnemonic": "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" 42 | } 43 | ] 44 | } 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /blockdb/tui/presenter/cosmos_message.go: -------------------------------------------------------------------------------- 1 | package presenter 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | "github.com/strangelove-ventures/interchaintest/v8/blockdb" 8 | ) 9 | 10 | // CosmosMessage presents a blockdb.CosmosMessageResult. 11 | type CosmosMessage struct { 12 | Result blockdb.CosmosMessageResult 13 | } 14 | 15 | func (msg CosmosMessage) Height() string { return strconv.FormatInt(msg.Result.Height, 10) } 16 | 17 | // Index is the message's ordered position within the tx. 18 | func (msg CosmosMessage) Index() string { return strconv.Itoa(msg.Result.Index) } 19 | 20 | // Type is a URI for the proto definition, e.g. /ibc.core.client.v1.MsgCreateClient 21 | func (msg CosmosMessage) Type() string { return msg.Result.Type } 22 | 23 | func (msg CosmosMessage) ClientChain() string { return msg.Result.ClientChainID.String } 24 | 25 | func (msg CosmosMessage) Clients() string { 26 | return msg.srcCounterpartyPair(msg.Result.ClientID.String, msg.Result.CounterpartyClientID.String) 27 | } 28 | 29 | func (msg CosmosMessage) Connections() string { 30 | return msg.srcCounterpartyPair(msg.Result.ConnID.String, msg.Result.CounterpartyConnID.String) 31 | } 32 | 33 | func (msg CosmosMessage) Channels() string { 34 | join := func(channel, port string) string { 35 | if channel+port == "" { 36 | return "" 37 | } 38 | return channel + ":" + port 39 | } 40 | return msg.srcCounterpartyPair( 41 | join(msg.Result.ChannelID.String, msg.Result.PortID.String), 42 | join(msg.Result.CounterpartyChannelID.String, msg.Result.CounterpartyPortID.String), 43 | ) 44 | } 45 | 46 | func (msg CosmosMessage) srcCounterpartyPair(source, counterparty string) string { 47 | if source != "" { 48 | source += " (source)" 49 | } 50 | if counterparty != "" { 51 | counterparty += " (counterparty)" 52 | } 53 | return strings.TrimSpace(source + " " + counterparty) 54 | } 55 | -------------------------------------------------------------------------------- /local-interchain/chains/stargaze.json: -------------------------------------------------------------------------------- 1 | { 2 | "chains": [ 3 | { 4 | "name": "stargaze", 5 | "chain_id": "localstars-1", 6 | "denom": "ustars", 7 | "binary": "starsd", 8 | "bech32_prefix": "stars", 9 | "docker_image": { 10 | "version": "v10.0.1" 11 | }, 12 | "gas_prices": "0%DENOM%", 13 | "chain_type": "cosmos", 14 | "coin_type": 118, 15 | "trusting_period": "112h", 16 | "gas_adjustment": 2.0, 17 | "number_vals": 1, 18 | "number_node": 0, 19 | "debugging": true, 20 | "block_time": "500ms", 21 | "encoding-options": ["wasm"], 22 | "genesis": { 23 | "modify": [ 24 | { 25 | "key": "app_state.gov.voting_params.voting_period", 26 | "value": "15s" 27 | }, 28 | { 29 | "key": "app_state.gov.deposit_params.max_deposit_period", 30 | "value": "15s" 31 | }, 32 | { 33 | "key": "app_state.gov.deposit_params.min_deposit.0.denom", 34 | "value": "ustars" 35 | } 36 | ], 37 | "accounts": [ 38 | { 39 | "name": "acc0", 40 | "address": "stars1hj5fveer5cjtn4wd6wstzugjfdxzl0xpjs908j", 41 | "amount": "10000000000%DENOM%", 42 | "mnemonic": "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" 43 | } 44 | ] 45 | } 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /testreporter/messages_test.go: -------------------------------------------------------------------------------- 1 | package testreporter_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | "time" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | "github.com/strangelove-ventures/interchaintest/v8/testreporter" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestWrappedMessage_RoundTrip(t *testing.T) { 14 | tcs := []struct { 15 | Message testreporter.Message 16 | }{ 17 | {Message: testreporter.BeginSuiteMessage{StartedAt: time.Now()}}, 18 | {Message: testreporter.FinishSuiteMessage{FinishedAt: time.Now()}}, 19 | { 20 | Message: testreporter.BeginTestMessage{ 21 | Name: "foo", 22 | StartedAt: time.Now(), 23 | }, 24 | }, 25 | {Message: testreporter.PauseTestMessage{Name: "foo", When: time.Now()}}, 26 | {Message: testreporter.ContinueTestMessage{Name: "foo", When: time.Now()}}, 27 | {Message: testreporter.FinishTestMessage{Name: "foo", FinishedAt: time.Now(), Skipped: true, Failed: true}}, 28 | {Message: testreporter.TestErrorMessage{Name: "foo", When: time.Now(), Message: "something failed"}}, 29 | {Message: testreporter.TestSkipMessage{Name: "foo", When: time.Now(), Message: "skipped for reasons"}}, 30 | { 31 | Message: testreporter.RelayerExecMessage{ 32 | Name: "foo", 33 | StartedAt: time.Now(), 34 | FinishedAt: time.Now().Add(time.Second), 35 | ContainerName: "relayer-exec-123", 36 | Command: []string{"rly", "version"}, 37 | Stdout: "relayer v1.2.3", 38 | ExitCode: 0, 39 | Error: "", 40 | }, 41 | }, 42 | } 43 | 44 | for _, tc := range tcs { 45 | wrapped := testreporter.JSONMessage(tc.Message) 46 | 47 | out, err := json.Marshal(wrapped) 48 | require.NoError(t, err) 49 | 50 | var unwrapped testreporter.WrappedMessage 51 | require.NoError(t, json.Unmarshal(out, &unwrapped)) 52 | 53 | diff := cmp.Diff(wrapped, unwrapped) 54 | require.Empty(t, diff) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /dockerutil/keyring.go: -------------------------------------------------------------------------------- 1 | package dockerutil 2 | 3 | import ( 4 | "archive/tar" 5 | "bytes" 6 | "context" 7 | "io" 8 | "os" 9 | "path" 10 | "path/filepath" 11 | 12 | "github.com/cosmos/cosmos-sdk/codec" 13 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 14 | cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" 15 | "github.com/cosmos/cosmos-sdk/crypto/keyring" 16 | "github.com/docker/docker/client" 17 | ) 18 | 19 | // NewLocalKeyringFromDockerContainer copies the contents of the given container directory into a specified local directory. 20 | // This allows test hosts to sign transactions on behalf of test users. 21 | func NewLocalKeyringFromDockerContainer(ctx context.Context, dc *client.Client, localDirectory, containerKeyringDir, containerId string) (keyring.Keyring, error) { 22 | reader, _, err := dc.CopyFromContainer(ctx, containerId, containerKeyringDir) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if err := os.Mkdir(filepath.Join(localDirectory, "keyring-test"), os.ModePerm); err != nil { 28 | return nil, err 29 | } 30 | tr := tar.NewReader(reader) 31 | for { 32 | hdr, err := tr.Next() 33 | if err == io.EOF { 34 | break // End of archive 35 | } 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | var fileBuff bytes.Buffer 41 | if _, err := io.Copy(&fileBuff, tr); err != nil { 42 | return nil, err 43 | } 44 | 45 | name := hdr.Name 46 | extractedFileName := path.Base(name) 47 | isDirectory := extractedFileName == "" 48 | if isDirectory { 49 | continue 50 | } 51 | 52 | filePath := filepath.Join(localDirectory, "keyring-test", extractedFileName) 53 | if err := os.WriteFile(filePath, fileBuff.Bytes(), os.ModePerm); err != nil { 54 | return nil, err 55 | } 56 | } 57 | 58 | registry := codectypes.NewInterfaceRegistry() 59 | cryptocodec.RegisterInterfaces(registry) 60 | cdc := codec.NewProtoCodec(registry) 61 | 62 | return keyring.New("", keyring.BackendTest, localDirectory, os.Stdin, cdc) 63 | } 64 | -------------------------------------------------------------------------------- /local-interchain/cmd/local-ic/root.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var ( 13 | // This must be global for the Makefile to build properly (ldflags). 14 | MakeFileInstallDirectory string 15 | ) 16 | 17 | var rootCmd = &cobra.Command{ 18 | Use: "local-ic", 19 | Short: "Your local IBC interchain of nodes program", 20 | CompletionOptions: cobra.CompletionOptions{ 21 | HiddenDefaultCmd: true, 22 | }, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | if err := cmd.Help(); err != nil { 25 | log.Fatal(err) 26 | } 27 | }, 28 | } 29 | 30 | func GetDirectory() string { 31 | cwd, err := os.Getwd() 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | 36 | p := path.Join(cwd, "chains") 37 | 38 | // if the current directory has the 'chains' folder, use that. If not, use the default location. 39 | if f, _ := os.Stat(p); f != nil && f.IsDir() { 40 | files, err := os.ReadDir(p) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | if len(files) > 0 { 46 | return cwd 47 | } 48 | } 49 | 50 | // Config variable override for the ICTEST_HOME 51 | if res := os.Getenv("ICTEST_HOME"); res != "" { 52 | MakeFileInstallDirectory = res 53 | } 54 | 55 | if MakeFileInstallDirectory == "" { 56 | homeDir, err := os.UserHomeDir() 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | 61 | MakeFileInstallDirectory = path.Join(homeDir, "local-interchain") 62 | } 63 | 64 | if err := directoryRequirementChecks(MakeFileInstallDirectory, "chains"); err != nil { 65 | log.Fatal(err) 66 | } 67 | 68 | return MakeFileInstallDirectory 69 | } 70 | 71 | func directoryRequirementChecks(parent string, subDirectories ...string) error { 72 | for _, subDirectory := range subDirectories { 73 | if _, err := os.Stat(path.Join(parent, subDirectory)); os.IsNotExist(err) { 74 | return fmt.Errorf("%s/ folder not found in %s", subDirectory, parent) 75 | } 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /dockerutil/filewriter_test.go: -------------------------------------------------------------------------------- 1 | package dockerutil_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | volumetypes "github.com/docker/docker/api/types/volume" 8 | interchaintest "github.com/strangelove-ventures/interchaintest/v8" 9 | "github.com/strangelove-ventures/interchaintest/v8/dockerutil" 10 | "github.com/stretchr/testify/require" 11 | "go.uber.org/zap/zaptest" 12 | ) 13 | 14 | func TestFileWriter(t *testing.T) { 15 | if testing.Short() { 16 | t.Skip("skipping due to short mode") 17 | } 18 | 19 | t.Parallel() 20 | 21 | cli, network := interchaintest.DockerSetup(t) 22 | 23 | ctx := context.Background() 24 | v, err := cli.VolumeCreate(ctx, volumetypes.CreateOptions{ 25 | Labels: map[string]string{dockerutil.CleanupLabel: t.Name()}, 26 | }) 27 | require.NoError(t, err) 28 | 29 | img := dockerutil.NewImage( 30 | zaptest.NewLogger(t), 31 | cli, 32 | network, 33 | t.Name(), 34 | "busybox", "stable", 35 | ) 36 | 37 | fw := dockerutil.NewFileWriter(zaptest.NewLogger(t), cli, t.Name()) 38 | 39 | t.Run("top-level file", func(t *testing.T) { 40 | require.NoError(t, fw.WriteFile(context.Background(), v.Name, "hello.txt", []byte("hello world"))) 41 | res := img.Run( 42 | ctx, 43 | []string{"sh", "-c", "cat /mnt/test/hello.txt"}, 44 | dockerutil.ContainerOptions{ 45 | Binds: []string{v.Name + ":/mnt/test"}, 46 | User: dockerutil.GetRootUserString(), 47 | }, 48 | ) 49 | require.NoError(t, res.Err) 50 | 51 | require.Equal(t, string(res.Stdout), "hello world") 52 | }) 53 | 54 | t.Run("create nested file", func(t *testing.T) { 55 | require.NoError(t, fw.WriteFile(context.Background(), v.Name, "a/b/c/d.txt", []byte(":D"))) 56 | res := img.Run( 57 | ctx, 58 | []string{"sh", "-c", "cat /mnt/test/a/b/c/d.txt"}, 59 | dockerutil.ContainerOptions{ 60 | Binds: []string{v.Name + ":/mnt/test"}, 61 | User: dockerutil.GetRootUserString(), 62 | }, 63 | ) 64 | require.NoError(t, err) 65 | 66 | require.Equal(t, string(res.Stdout), ":D") 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /proto/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | deps: 4 | - remote: buf.build 5 | owner: cosmos 6 | repository: cosmos-proto 7 | commit: 1935555c206d4afb9e94615dfd0fad31 8 | digest: shake256:c74d91a3ac7ae07d579e90eee33abf9b29664047ac8816500cf22c081fec0d72d62c89ce0bebafc1f6fec7aa5315be72606717740ca95007248425102c365377 9 | - remote: buf.build 10 | owner: cosmos 11 | repository: cosmos-sdk 12 | commit: e7a85cef453e4b999ad9aff8714ae05f 13 | digest: shake256:bc5f340b60991981d8aabefcc9c706780e25fa0d3ab7e1d9a3e75559f45c80038a913d05aa51928ad8dfebeee75feea1dc6e0e7a6311302e60c42fb3596ee262 14 | - remote: buf.build 15 | owner: cosmos 16 | repository: gogo-proto 17 | commit: 34d970b699f84aa382f3c29773a60836 18 | digest: shake256:3d3bee5229ba579e7d19ffe6e140986a228b48a8c7fe74348f308537ab95e9135210e81812489d42cd8941d33ff71f11583174ccc5972e86e6112924b6ce9f04 19 | - remote: buf.build 20 | owner: cosmos 21 | repository: ibc 22 | commit: 7ab44ae956a0488ea04e04511efa5f70 23 | digest: shake256:95cc5472ddf692d23654b7e5adfd79149469dcc90657f9a1f80ee3fea8af639cff59cd849efca0567d270118eea7c7ff2f7e60c562545bace410b8eece27577e 24 | - remote: buf.build 25 | owner: cosmos 26 | repository: ics23 27 | commit: 55085f7c710a45f58fa09947208eb70b 28 | digest: shake256:9bf0bc495b5a11c88d163d39ef521bc4b00bc1374a05758c91d82821bdc61f09e8c2c51dda8452529bf80137f34d852561eacbe9550a59015d51cecb0dacb628 29 | - remote: buf.build 30 | owner: googleapis 31 | repository: googleapis 32 | commit: 75b4300737fb4efca0831636be94e517 33 | digest: shake256:d865f55b8ceb838c90c28b09894ab43d07f42551108c23760004a6a4e28fe24d3a1f7380a3c9278edb329a338a9cc5db8ad9f394de548e70d534e98504972d67 34 | - remote: buf.build 35 | owner: penumbra-zone 36 | repository: penumbra 37 | commit: 60489c71c3b64f179b2537b24a587abe 38 | digest: shake256:296efd5bb8051beace006b4a2920e79b43d2fe033554c88504e10f813c87d777a712309cd3bbba366703d76aa3c5f93f1af10a2083e76b923ee54ae251f1fc8d 39 | -------------------------------------------------------------------------------- /dockerutil/fileretriever_test.go: -------------------------------------------------------------------------------- 1 | package dockerutil_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | volumetypes "github.com/docker/docker/api/types/volume" 8 | interchaintest "github.com/strangelove-ventures/interchaintest/v8" 9 | "github.com/strangelove-ventures/interchaintest/v8/dockerutil" 10 | "github.com/stretchr/testify/require" 11 | "go.uber.org/zap/zaptest" 12 | ) 13 | 14 | func TestFileRetriever(t *testing.T) { 15 | if testing.Short() { 16 | t.Skip("skipping due to short mode") 17 | } 18 | 19 | t.Parallel() 20 | 21 | cli, network := interchaintest.DockerSetup(t) 22 | 23 | ctx := context.Background() 24 | v, err := cli.VolumeCreate(ctx, volumetypes.CreateOptions{ 25 | Labels: map[string]string{dockerutil.CleanupLabel: t.Name()}, 26 | }) 27 | require.NoError(t, err) 28 | 29 | img := dockerutil.NewImage( 30 | zaptest.NewLogger(t), 31 | cli, 32 | network, 33 | t.Name(), 34 | "busybox", "stable", 35 | ) 36 | 37 | res := img.Run( 38 | ctx, 39 | []string{"sh", "-c", "chmod 0700 /mnt/test && printf 'hello world' > /mnt/test/hello.txt"}, 40 | dockerutil.ContainerOptions{ 41 | Binds: []string{v.Name + ":/mnt/test"}, 42 | User: dockerutil.GetRootUserString(), 43 | }, 44 | ) 45 | require.NoError(t, res.Err) 46 | res = img.Run( 47 | ctx, 48 | []string{"sh", "-c", "mkdir -p /mnt/test/foo/bar/ && printf 'test' > /mnt/test/foo/bar/baz.txt"}, 49 | dockerutil.ContainerOptions{ 50 | Binds: []string{v.Name + ":/mnt/test"}, 51 | User: dockerutil.GetRootUserString(), 52 | }, 53 | ) 54 | require.NoError(t, res.Err) 55 | 56 | fr := dockerutil.NewFileRetriever(zaptest.NewLogger(t), cli, t.Name()) 57 | 58 | t.Run("top-level file", func(t *testing.T) { 59 | b, err := fr.SingleFileContent(ctx, v.Name, "hello.txt") 60 | require.NoError(t, err) 61 | require.Equal(t, string(b), "hello world") 62 | }) 63 | 64 | t.Run("nested file", func(t *testing.T) { 65 | b, err := fr.SingleFileContent(ctx, v.Name, "foo/bar/baz.txt") 66 | require.NoError(t, err) 67 | require.Equal(t, string(b), "test") 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /cmd/interchaintest/flags.go: -------------------------------------------------------------------------------- 1 | package interchaintest 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "time" 8 | 9 | interchaintest "github.com/strangelove-ventures/interchaintest/v8" 10 | "go.uber.org/zap" 11 | "go.uber.org/zap/zapcore" 12 | ) 13 | 14 | // The value of the extra flags this test supports. 15 | type mainFlags struct { 16 | LogFile string 17 | LogFormat string 18 | LogLevel string 19 | MatrixFile string 20 | ReportFile string 21 | BlockDatabaseFile string 22 | } 23 | 24 | func (f mainFlags) Logger() (lc LoggerCloser, _ error) { 25 | var w zapcore.WriteSyncer 26 | switch f.LogFile { 27 | case "stderr", "": 28 | w = os.Stderr 29 | lc.FilePath = "stderr" 30 | case "stdout": 31 | w = os.Stdout 32 | lc.FilePath = "stdout" 33 | default: 34 | file, err := interchaintest.CreateLogFile(f.LogFile) 35 | if err != nil { 36 | return lc, fmt.Errorf("create log file: %w", err) 37 | } 38 | w = file 39 | lc.Closer = file 40 | lc.FilePath = file.Name() 41 | } 42 | lc.Logger = f.newZap(w) 43 | return lc, nil 44 | } 45 | 46 | func (f mainFlags) newZap(w zapcore.WriteSyncer) *zap.Logger { 47 | config := zap.NewProductionEncoderConfig() 48 | config.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) { 49 | encoder.AppendString(ts.UTC().Format("2006-01-02T15:04:05.000000Z07:00")) 50 | } 51 | config.LevelKey = "lvl" 52 | 53 | enc := zapcore.NewConsoleEncoder(config) 54 | if f.LogFormat == "json" { 55 | enc = zapcore.NewJSONEncoder(config) 56 | } 57 | 58 | lvl := zap.NewAtomicLevel() 59 | if err := lvl.UnmarshalText([]byte(f.LogLevel)); err != nil { 60 | lvl = zap.NewAtomicLevelAt(zap.InfoLevel) 61 | } 62 | return zap.New(zapcore.NewCore(enc, w, lvl)) 63 | } 64 | 65 | type LoggerCloser struct { 66 | *zap.Logger 67 | io.Closer 68 | FilePath string 69 | } 70 | 71 | func (lc LoggerCloser) Close() error { 72 | // ignore error because of https://github.com/uber-go/zap/issues/880 with stderr/stdout 73 | _ = lc.Logger.Sync() 74 | if lc.Closer == nil { 75 | return nil 76 | } 77 | return lc.Closer.Close() 78 | } 79 | -------------------------------------------------------------------------------- /chain/ethereum/unimplemented.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "runtime" 6 | 7 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 8 | ) 9 | 10 | func PanicFunctionName() { 11 | pc, _, _, _ := runtime.Caller(1) 12 | panic(runtime.FuncForPC(pc).Name() + " not implemented") 13 | } 14 | 15 | func (c *EthereumChain) ExportState(ctx context.Context, height int64) (string, error) { 16 | PanicFunctionName() 17 | return "", nil 18 | } 19 | 20 | func (c *EthereumChain) GetGRPCAddress() string { 21 | PanicFunctionName() 22 | return "" 23 | } 24 | 25 | func (c *EthereumChain) GetHostGRPCAddress() string { 26 | PanicFunctionName() 27 | return "" 28 | } 29 | 30 | func (*EthereumChain) GetHostPeerAddress() string { 31 | PanicFunctionName() 32 | return "" 33 | } 34 | 35 | // cast wallet import requires a password prompt which docker isn't properly handling. For now, we only use CreateKey(). 36 | func (c *EthereumChain) RecoverKey(ctx context.Context, keyName, mnemonic string) error { 37 | /*cmd := []string{"cast", "wallet", "import", keyName, "--mnemonic", mnemonic, "--password", ""} 38 | stdout, stderr, err := c.Exec(ctx, cmd, nil) 39 | fmt.Println("stdout: ", string(stdout)) 40 | fmt.Println("stderr: ", string(stderr)) 41 | if err != nil { 42 | return err 43 | }*/ 44 | PanicFunctionName() 45 | return nil 46 | } 47 | 48 | func (c *EthereumChain) GetGasFeesInNativeDenom(gasPaid int64) int64 { 49 | PanicFunctionName() 50 | return 0 51 | } 52 | 53 | func (c *EthereumChain) SendIBCTransfer(ctx context.Context, channelID, keyName string, amount ibc.WalletAmount, options ibc.TransferOptions) (ibc.Tx, error) { 54 | PanicFunctionName() 55 | return ibc.Tx{}, nil 56 | } 57 | 58 | func (c *EthereumChain) Acknowledgements(ctx context.Context, height int64) ([]ibc.PacketAcknowledgement, error) { 59 | PanicFunctionName() 60 | return nil, nil 61 | } 62 | 63 | func (c *EthereumChain) Timeouts(ctx context.Context, height int64) ([]ibc.PacketTimeout, error) { 64 | PanicFunctionName() 65 | return nil, nil 66 | } 67 | 68 | func (c *EthereumChain) BuildRelayerWallet(ctx context.Context, keyName string) (ibc.Wallet, error) { 69 | PanicFunctionName() 70 | return nil, nil 71 | } 72 | -------------------------------------------------------------------------------- /local-interchain/chains/base_ibc.json: -------------------------------------------------------------------------------- 1 | { 2 | "chains": [ 3 | { 4 | "name": "gaia", 5 | "chain_id": "localcosmos-1", 6 | "denom": "uatom", 7 | "binary": "gaiad", 8 | "bech32_prefix": "cosmos", 9 | "docker_image": { 10 | "version": "v9.1.0" 11 | }, 12 | "block_time": "2s", 13 | "gas_prices": "0%DENOM%", 14 | "gas_adjustment": 2.0, 15 | "ibc_paths": ["atom-juno", "atom-terra"], 16 | "genesis": { 17 | "accounts": [ 18 | { 19 | "name": "acc0", 20 | "address": "cosmos1hj5fveer5cjtn4wd6wstzugjfdxzl0xpxvjjvr", 21 | "amount": "10000000%DENOM%", 22 | "mnemonic": "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" 23 | } 24 | ] 25 | } 26 | }, 27 | { 28 | "name": "juno", 29 | "chain_id": "localjuno-1", 30 | "binary": "junod", 31 | "bech32_prefix": "juno", 32 | "denom": "ujuno", 33 | "docker_image": { 34 | "repository": "ghcr.io/cosmoscontracts/juno", 35 | "version": "v17.0.0" 36 | }, 37 | "block_time": "2s", 38 | "encoding-options": ["juno"], 39 | "gas_prices": "0%DENOM%", 40 | "gas_adjustment": 2.0, 41 | "ibc_paths": ["atom-juno", "juno-terra"] 42 | }, 43 | { 44 | "name": "terra", 45 | "chain_id": "localterra-1", 46 | "binary": "terrad", 47 | "bech32_prefix": "terra", 48 | "denom": "uluna", 49 | "docker_image": { 50 | "version": "v2.3.4" 51 | }, 52 | "block_time": "2s", 53 | "encoding-options": ["wasm"], 54 | "gas_prices": "0%DENOM%", 55 | "gas_adjustment": 2.0, 56 | "ibc_paths": ["atom-terra", "juno-terra"] 57 | } 58 | ] 59 | } -------------------------------------------------------------------------------- /chain/cosmos/08-wasm-types/module.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/cosmos/cosmos-sdk/client" 7 | "github.com/cosmos/cosmos-sdk/codec" 8 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 9 | "github.com/cosmos/cosmos-sdk/types/module" 10 | 11 | //grpc "github.com/cosmos/gogoproto/grpc" 12 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var _ module.AppModuleBasic = AppModuleBasic{} 17 | 18 | // AppModuleBasic defines the basic application module used by the tendermint light client. 19 | // Only the RegisterInterfaces function needs to be implemented. All other function perform 20 | // a no-op. 21 | type AppModuleBasic struct{} 22 | 23 | // Name returns the tendermint module name. 24 | func (AppModuleBasic) Name() string { 25 | return "08-wasm" 26 | } 27 | 28 | // RegisterLegacyAminoCodec performs a no-op. The Wasm client does not support amino. 29 | func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} 30 | 31 | // RegisterInterfaces registers module concrete types into protobuf Any. This allows core IBC 32 | // to unmarshal wasm light client types. 33 | func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { 34 | RegisterInterfaces(registry) 35 | } 36 | 37 | // DefaultGenesis performs a no-op. Genesis is not supported for the tendermint light client. 38 | func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { 39 | return nil 40 | } 41 | 42 | // ValidateGenesis performs a no-op. Genesis is not supported for the tendermint light client. 43 | func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { 44 | return nil 45 | } 46 | 47 | // RegisterGRPCGatewayRoutes performs a no-op. 48 | func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {} 49 | 50 | // GetTxCmd performs a no-op. Please see the 02-client cli commands. 51 | func (AppModuleBasic) GetTxCmd() *cobra.Command { 52 | return nil 53 | } 54 | 55 | // GetQueryCmd performs a no-op. Please see the 02-client cli commands. 56 | func (AppModuleBasic) GetQueryCmd() *cobra.Command { 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /dockerutil/setup_test.go: -------------------------------------------------------------------------------- 1 | package dockerutil_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | volumetypes "github.com/docker/docker/api/types/volume" 9 | "github.com/docker/docker/errdefs" 10 | "github.com/strangelove-ventures/interchaintest/v8/dockerutil" 11 | "github.com/strangelove-ventures/interchaintest/v8/mocktesting" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestDockerSetup_KeepVolumes(t *testing.T) { 16 | if testing.Short() { 17 | t.Skip("skipping due to short mode") 18 | } 19 | 20 | cli, _ := dockerutil.DockerSetup(t) 21 | 22 | origKeep := dockerutil.KeepVolumesOnFailure 23 | defer func() { 24 | dockerutil.KeepVolumesOnFailure = origKeep 25 | }() 26 | 27 | ctx := context.Background() 28 | 29 | for _, tc := range []struct { 30 | keep bool 31 | passed bool 32 | volumeKept bool 33 | }{ 34 | {keep: false, passed: false, volumeKept: false}, 35 | {keep: true, passed: false, volumeKept: true}, 36 | {keep: false, passed: true, volumeKept: false}, 37 | {keep: true, passed: true, volumeKept: false}, 38 | } { 39 | tc := tc 40 | state := "failed" 41 | if tc.passed { 42 | state = "passed" 43 | } 44 | 45 | testName := fmt.Sprintf("keep=%t, test %s", tc.keep, state) 46 | t.Run(testName, func(t *testing.T) { 47 | dockerutil.KeepVolumesOnFailure = tc.keep 48 | mt := mocktesting.NewT(t.Name()) 49 | 50 | var volumeName string 51 | mt.Simulate(func() { 52 | cli, _ := dockerutil.DockerSetup(mt) 53 | 54 | v, err := cli.VolumeCreate(ctx, volumetypes.CreateOptions{ 55 | Labels: map[string]string{dockerutil.CleanupLabel: mt.Name()}, 56 | }) 57 | require.NoError(t, err) 58 | 59 | volumeName = v.Name 60 | 61 | if !tc.passed { 62 | mt.Fail() 63 | } 64 | }) 65 | 66 | require.Equal(t, !tc.passed, mt.Failed()) 67 | 68 | _, err := cli.VolumeInspect(ctx, volumeName) 69 | if !tc.volumeKept { 70 | require.Truef(t, errdefs.IsNotFound(err), "expected not found error, got %v", err) 71 | return 72 | } 73 | 74 | require.NoError(t, err) 75 | if err := cli.VolumeRemove(ctx, volumeName, true); err != nil { 76 | t.Logf("failed to remove volume %s: %v", volumeName, err) 77 | } 78 | }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /local-interchain/python/helpers/relayer.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import httpx 4 | 5 | 6 | class Relayer: 7 | def __init__(self, api: str, chain_id: str, log_output: bool = False): 8 | self.api = api 9 | self.chain_id = chain_id 10 | self.log_output = log_output 11 | 12 | def execute(self, cmd: str, return_text: bool = False) -> dict: 13 | if self.api == "": 14 | raise Exception("send_request URL is empty") 15 | 16 | payload = { 17 | "chain_id": self.chain_id, 18 | "action": "relayer-exec", 19 | "cmd": cmd, 20 | } 21 | 22 | if self.log_output: 23 | print("[relayer]", payload["cmd"]) 24 | 25 | res = httpx.post( 26 | self.api, 27 | json=payload, 28 | headers={"Content-Type": "application/json"}, 29 | timeout=120, 30 | ) 31 | 32 | if return_text: 33 | return {"text": res.text} 34 | 35 | try: 36 | # Is there ever a case this does not work? 37 | return json.loads(res.text) 38 | except Exception: 39 | return {"parse_error": res.text} 40 | 41 | def create_wasm_connection( 42 | self, path: str, src: str, dst: str, order: str, version: str 43 | ): 44 | if not src.startswith("wasm."): 45 | src = f"wasm.{src}" 46 | 47 | if not dst.startswith("wasm."): 48 | dst = f"wasm.{dst}" 49 | 50 | self.execute( 51 | f"rly tx channel {path} --src-port {src} --dst-port {dst} --order {order} --version {version}" 52 | ) 53 | 54 | def flush(self, path: str, channel: str, log_output: bool = False) -> dict: 55 | res = self.execute( 56 | f"rly transact flush {path} {channel}", 57 | ) 58 | if log_output: 59 | print(res) 60 | 61 | return res 62 | 63 | def get_channels(self) -> dict: 64 | payload = { 65 | "chain_id": self.chain_id, 66 | "action": "get_channels", 67 | } 68 | 69 | res = httpx.post( 70 | self.api, 71 | json=payload, 72 | headers={"Content-Type": "application/json"}, 73 | ) 74 | return json.loads(res.text) 75 | -------------------------------------------------------------------------------- /chain/cosmos/osmosis.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/strangelove-ventures/interchaintest/v8/dockerutil" 11 | ) 12 | 13 | // OsmosisPoolParams defines parameters for creating an osmosis gamm liquidity pool 14 | type OsmosisPoolParams struct { 15 | Weights string `json:"weights"` 16 | InitialDeposit string `json:"initial-deposit"` 17 | SwapFee string `json:"swap-fee"` 18 | ExitFee string `json:"exit-fee"` 19 | FutureGovernor string `json:"future-governor"` 20 | } 21 | 22 | func OsmosisCreatePool(c *CosmosChain, ctx context.Context, keyName string, params OsmosisPoolParams) (string, error) { 23 | tn := c.getFullNode() 24 | poolbz, err := json.Marshal(params) 25 | if err != nil { 26 | return "", err 27 | } 28 | 29 | poolFile := "pool.json" 30 | 31 | fw := dockerutil.NewFileWriter(tn.logger(), tn.DockerClient, tn.TestName) 32 | if err := fw.WriteFile(ctx, tn.VolumeName, poolFile, poolbz); err != nil { 33 | return "", fmt.Errorf("failed to write pool file: %w", err) 34 | } 35 | 36 | if _, err := tn.ExecTx(ctx, keyName, 37 | "gamm", "create-pool", 38 | "--pool-file", filepath.Join(tn.HomeDir(), poolFile), 39 | ); err != nil { 40 | return "", fmt.Errorf("failed to create pool: %w", err) 41 | } 42 | 43 | stdout, _, err := tn.ExecQuery(ctx, "gamm", "num-pools") 44 | if err != nil { 45 | return "", fmt.Errorf("failed to query num pools: %w", err) 46 | } 47 | var res map[string]string 48 | if err := json.Unmarshal(stdout, &res); err != nil { 49 | return "", fmt.Errorf("failed to unmarshal query response: %w", err) 50 | } 51 | 52 | numPools, ok := res["num_pools"] 53 | if !ok { 54 | return "", fmt.Errorf("could not find number of pools in query response: %w", err) 55 | } 56 | return numPools, nil 57 | } 58 | 59 | func OsmosisSwapExactAmountIn(c *CosmosChain, ctx context.Context, keyName string, coinIn string, minAmountOut string, poolIDs []string, swapDenoms []string) (string, error) { 60 | return c.getFullNode().ExecTx(ctx, keyName, 61 | "gamm", "swap-exact-amount-in", 62 | coinIn, minAmountOut, 63 | "--swap-route-pool-ids", strings.Join(poolIDs, ","), 64 | "--swap-route-denoms", strings.Join(swapDenoms, ","), 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /local-interchain/interchain/ibc.go: -------------------------------------------------------------------------------- 1 | package interchain 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/strangelove-ventures/interchaintest/v8" 8 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 9 | types "github.com/strangelove-ventures/localinterchain/interchain/types" 10 | ) 11 | 12 | func VerifyIBCPaths(ibcpaths map[string][]int) error { 13 | for k, v := range ibcpaths { 14 | if len(v) == 1 { 15 | return fmt.Errorf("ibc path '%s' has only 1 chain", k) 16 | } 17 | if len(v) > 2 { 18 | return fmt.Errorf("ibc path '%s' has more than 2 chains", k) 19 | } 20 | } 21 | return nil 22 | } 23 | 24 | // TODO: Allow for a single chain to IBC between multiple chains 25 | func LinkIBCPaths(ibcpaths map[string][]int, chains []ibc.Chain, ic *interchaintest.Interchain, r ibc.Relayer) { 26 | for path, c := range ibcpaths { 27 | chain1 := chains[c[0]] 28 | chain2 := chains[c[1]] 29 | 30 | interLink := interchaintest.InterchainLink{ 31 | Chain1: chain1, 32 | Chain2: chain2, 33 | Path: path, 34 | Relayer: r, 35 | } 36 | 37 | ic = ic.AddLink(interLink) 38 | } 39 | } 40 | 41 | // TODO: Get all channels a chain is connected too. Map it to the said chain_id. Then output to Logs. 42 | func GetChannelConnections(ctx context.Context, ibcpaths map[string][]int, chains []ibc.Chain, ic *interchaintest.Interchain, r ibc.Relayer, eRep ibc.RelayerExecReporter) []types.IBCChannel { 43 | if len(ibcpaths) == 0 { 44 | return []types.IBCChannel{} 45 | } 46 | 47 | channels := []types.IBCChannel{} 48 | 49 | for _, c := range ibcpaths { 50 | chain1 := chains[c[0]] 51 | chain2 := chains[c[1]] 52 | 53 | channel1, err := ibc.GetTransferChannel(ctx, r, eRep, chain1.Config().ChainID, chain2.Config().ChainID) 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | channels = append(channels, types.IBCChannel{ 59 | ChainID: chain1.Config().ChainID, 60 | Channel: channel1, 61 | }) 62 | 63 | // this a duplicate? 64 | channel2, err := ibc.GetTransferChannel(ctx, r, eRep, chain2.Config().ChainID, chain1.Config().ChainID) 65 | if err != nil { 66 | panic(err) 67 | } 68 | channels = append(channels, types.IBCChannel{ 69 | ChainID: chain2.Config().ChainID, 70 | Channel: channel2, 71 | }) 72 | } 73 | 74 | return channels 75 | } 76 | -------------------------------------------------------------------------------- /chain/cosmos/module_feegrant.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | "cosmossdk.io/x/feegrant" 9 | ) 10 | 11 | // FeeGrant grants a fee grant. 12 | func (tn *ChainNode) FeeGrant(ctx context.Context, granterKey, grantee, spendLimit string, allowedMsgs []string, expiration time.Time, extraFlags ...string) error { 13 | cmd := []string{"feegrant", "grant", granterKey, grantee, "--spend-limit", spendLimit} 14 | 15 | if len(allowedMsgs) > 0 { 16 | msgs := make([]string, len(allowedMsgs)) 17 | for i, msg := range allowedMsgs { 18 | msg = PrefixMsgTypeIfRequired(msg) 19 | msgs[i] = msg 20 | } 21 | 22 | cmd = append(cmd, "--allowed-messages", strings.Join(msgs, ",")) 23 | } 24 | 25 | if expiration.After(time.Now()) { 26 | cmd = append(cmd, "--expiration", expiration.Format(time.RFC3339)) 27 | } 28 | 29 | cmd = append(cmd, extraFlags...) 30 | 31 | _, err := tn.ExecTx(ctx, granterKey, cmd...) 32 | return err 33 | } 34 | 35 | // FeeGrantRevoke revokes a fee grant. 36 | func (tn *ChainNode) FeeGrantRevoke(ctx context.Context, keyName, granterAddr, granteeAddr string) error { 37 | _, err := tn.ExecTx(ctx, keyName, "feegrant", "revoke", granterAddr, granteeAddr) 38 | return err 39 | } 40 | 41 | // FeeGrantGetAllowance returns the allowance of a granter and grantee pair. 42 | func (c *CosmosChain) FeeGrantQueryAllowance(ctx context.Context, granter, grantee string) (*feegrant.Grant, error) { 43 | res, err := feegrant.NewQueryClient(c.GetNode().GrpcConn).Allowance(ctx, &feegrant.QueryAllowanceRequest{ 44 | Granter: granter, 45 | Grantee: grantee, 46 | }) 47 | return res.Allowance, err 48 | } 49 | 50 | // FeeGrantGetAllowances returns all allowances of a grantee. 51 | func (c *CosmosChain) FeeGrantQueryAllowances(ctx context.Context, grantee string) ([]*feegrant.Grant, error) { 52 | res, err := feegrant.NewQueryClient(c.GetNode().GrpcConn).Allowances(ctx, &feegrant.QueryAllowancesRequest{ 53 | Grantee: grantee, 54 | }) 55 | return res.Allowances, err 56 | } 57 | 58 | // FeeGrantGetAllowancesByGranter returns all allowances of a granter. 59 | func (c *CosmosChain) FeeGrantQueryAllowancesByGranter(ctx context.Context, granter string) ([]*feegrant.Grant, error) { 60 | res, err := feegrant.NewQueryClient(c.GetNode().GrpcConn).AllowancesByGranter(ctx, &feegrant.QueryAllowancesByGranterRequest{ 61 | Granter: granter, 62 | }) 63 | return res.Allowances, err 64 | } 65 | -------------------------------------------------------------------------------- /.github/workflows/e2e-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run End-To-End Tests 2 | 3 | on: 4 | pull_request: 5 | 6 | # Ensures that only a single workflow per PR will run at a time. Cancels in-progress jobs if new commit is pushed. 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | test-conformance: 13 | name: test-conformance 14 | runs-on: ubuntu-latest 15 | steps: 16 | # Install and setup go 17 | - name: Set up Go 1.21 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version: '1.21' 21 | 22 | - name: checkout interchaintest 23 | uses: actions/checkout@v4 24 | 25 | # cleanup environment on self-hosted test runner 26 | - name: clean 27 | run: |- 28 | rm -rf ~/.interchaintest 29 | 30 | # run tests 31 | - name: run conformance tests 32 | run: (go test -race -timeout 30m -failfast -v -p 2 ./cmd/interchaintest) || (echo "\n\n*****CHAIN and RELAYER LOGS*****" && cat "$HOME/.interchaintest/logs/interchaintest.log" && exit 1) 33 | test-ibc-examples: 34 | name: test-ibc-examples 35 | runs-on: ubuntu-latest 36 | steps: 37 | # Install and setup go 38 | - name: Set up Go 1.21 39 | uses: actions/setup-go@v5 40 | with: 41 | go-version: '1.21' 42 | cache: false 43 | 44 | - name: checkout interchaintest 45 | uses: actions/checkout@v4 46 | 47 | # cleanup environment on self-hosted test runner 48 | - name: clean 49 | run: |- 50 | rm -rf ~/.interchaintest 51 | 52 | # run tests 53 | - name: run example ibc tests 54 | run: go test -race -timeout 30m -failfast -v -p 2 ./examples/ibc 55 | test-cosmos-examples: 56 | name: test-cosmos-examples 57 | runs-on: ubuntu-latest 58 | steps: 59 | # Install and setup go 60 | - name: Set up Go 1.21 61 | uses: actions/setup-go@v5 62 | with: 63 | go-version: '1.21' 64 | cache: false 65 | 66 | - name: checkout interchaintest 67 | uses: actions/checkout@v4 68 | 69 | # cleanup environment on self-hosted test runner 70 | - name: clean 71 | run: |- 72 | rm -rf ~/.interchaintest 73 | 74 | # run tests 75 | - name: run example cosmos tests 76 | run: go test -race -failfast -timeout 30m -v -p 2 ./examples/cosmos 77 | -------------------------------------------------------------------------------- /dockerutil/ports.go: -------------------------------------------------------------------------------- 1 | package dockerutil 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | "sync" 8 | 9 | "github.com/docker/go-connections/nat" 10 | ) 11 | 12 | var mu sync.RWMutex 13 | 14 | type Listeners []net.Listener 15 | 16 | func (l Listeners) CloseAll() { 17 | for _, listener := range l { 18 | listener.Close() 19 | } 20 | } 21 | 22 | // OpenListener opens a listener on a port. Set to 0 to get a random port. 23 | func OpenListener(port int) (*net.TCPListener, error) { 24 | addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", port)) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | mu.Lock() 30 | defer mu.Unlock() 31 | l, err := net.ListenTCP("tcp", addr) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return l, nil 37 | } 38 | 39 | // GetPort generates a docker PortBinding by using the port provided. 40 | // If port is set to 0, the next available port will be used. 41 | // The listener will be closed in the case of an error, otherwise it will be left open. 42 | // This allows multiple GetPort calls to find multiple available ports 43 | // before closing them so they are available for the PortBinding. 44 | func GetPort(port int) (nat.PortBinding, *net.TCPListener, error) { 45 | l, err := OpenListener(port) 46 | if err != nil { 47 | l.Close() 48 | return nat.PortBinding{}, nil, err 49 | } 50 | 51 | return nat.PortBinding{ 52 | HostIP: "0.0.0.0", 53 | HostPort: fmt.Sprint(l.Addr().(*net.TCPAddr).Port), 54 | }, l, nil 55 | } 56 | 57 | // GeneratePortBindings will find open ports on the local 58 | // machine and create a PortBinding for every port in the portSet. 59 | // If a port is already bound, it will use that port as an override. 60 | func GeneratePortBindings(pairs nat.PortMap) (nat.PortMap, Listeners, error) { 61 | m := make(nat.PortMap) 62 | listeners := make(Listeners, 0, len(pairs)) 63 | 64 | var pb nat.PortBinding 65 | var l *net.TCPListener 66 | var err error 67 | 68 | for p, bind := range pairs { 69 | if len(bind) == 0 { 70 | // random port 71 | pb, l, err = GetPort(0) 72 | } else { 73 | var pNum int 74 | if pNum, err = strconv.Atoi(bind[0].HostPort); err != nil { 75 | return nat.PortMap{}, nil, err 76 | } 77 | 78 | pb, l, err = GetPort(pNum) 79 | } 80 | 81 | if err != nil { 82 | listeners.CloseAll() 83 | return nat.PortMap{}, nil, err 84 | } 85 | 86 | listeners = append(listeners, l) 87 | m[p] = []nat.PortBinding{pb} 88 | } 89 | 90 | return m, listeners, nil 91 | } 92 | -------------------------------------------------------------------------------- /local-interchain/interchain/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | 8 | "github.com/gorilla/mux" 9 | "github.com/strangelove-ventures/interchaintest/v8" 10 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 11 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 12 | ictypes "github.com/strangelove-ventures/localinterchain/interchain/types" 13 | "github.com/strangelove-ventures/localinterchain/interchain/util" 14 | 15 | "github.com/strangelove-ventures/localinterchain/interchain/handlers" 16 | ) 17 | 18 | type Route struct { 19 | Path string `json:"path" yaml:"path"` 20 | Methods []string `json:"methods" yaml:"methods"` 21 | } 22 | 23 | func NewRouter( 24 | ctx context.Context, 25 | ic *interchaintest.Interchain, 26 | config *ictypes.Config, 27 | cosmosChains map[string]*cosmos.CosmosChain, 28 | vals map[string][]*cosmos.ChainNode, 29 | relayer ibc.Relayer, 30 | authKey string, 31 | eRep ibc.RelayerExecReporter, 32 | installDir string, 33 | ) *mux.Router { 34 | r := mux.NewRouter() 35 | 36 | infoH := handlers.NewInfo(config, installDir, ctx, ic, cosmosChains, vals, relayer, eRep) 37 | r.HandleFunc("/info", infoH.GetInfo).Methods(http.MethodGet) 38 | 39 | actionsH := handlers.NewActions(ctx, ic, cosmosChains, vals, relayer, eRep, authKey) 40 | r.HandleFunc("/", actionsH.PostActions).Methods(http.MethodPost) 41 | 42 | uploaderH := handlers.NewUploader(ctx, vals, authKey) 43 | r.HandleFunc("/upload", uploaderH.PostUpload).Methods(http.MethodPost) 44 | 45 | availableRoutes := getAllMethods(*r) 46 | r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 47 | jsonRes, err := json.MarshalIndent(availableRoutes, "", " ") 48 | if err != nil { 49 | util.WriteError(w, err) 50 | return 51 | } 52 | util.Write(w, jsonRes) 53 | }).Methods(http.MethodGet) 54 | 55 | return r 56 | } 57 | 58 | func getAllMethods(r mux.Router) []Route { 59 | endpoints := make([]Route, 0) 60 | 61 | err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { 62 | tpl, err1 := route.GetPathTemplate() 63 | met, err2 := route.GetMethods() 64 | if err1 != nil { 65 | return err1 66 | } 67 | if err2 != nil { 68 | return err2 69 | } 70 | 71 | // fmt.Println(tpl, met) 72 | endpoints = append(endpoints, Route{ 73 | Path: tpl, 74 | Methods: met, 75 | }) 76 | return nil 77 | }) 78 | 79 | if err != nil { 80 | panic(err) 81 | } 82 | 83 | return endpoints 84 | } 85 | -------------------------------------------------------------------------------- /dockerutil/strings_test.go: -------------------------------------------------------------------------------- 1 | package dockerutil 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/docker/docker/api/types" 8 | "github.com/docker/go-connections/nat" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestGetHostPort(t *testing.T) { 13 | for _, tt := range []struct { 14 | Container types.ContainerJSON 15 | PortID string 16 | Want string 17 | }{ 18 | { 19 | types.ContainerJSON{ 20 | NetworkSettings: &types.NetworkSettings{ 21 | NetworkSettingsBase: types.NetworkSettingsBase{ 22 | Ports: nat.PortMap{ 23 | nat.Port("test"): []nat.PortBinding{ 24 | {HostIP: "1.2.3.4", HostPort: "8080"}, 25 | {HostIP: "0.0.0.0", HostPort: "9999"}, 26 | }, 27 | }, 28 | }, 29 | }, 30 | }, "test", "1.2.3.4:8080", 31 | }, 32 | { 33 | types.ContainerJSON{ 34 | NetworkSettings: &types.NetworkSettings{ 35 | NetworkSettingsBase: types.NetworkSettingsBase{ 36 | Ports: nat.PortMap{ 37 | nat.Port("test"): []nat.PortBinding{ 38 | {HostIP: "0.0.0.0", HostPort: "3000"}, 39 | }, 40 | }, 41 | }, 42 | }, 43 | }, "test", "0.0.0.0:3000", 44 | }, 45 | 46 | {types.ContainerJSON{}, "", ""}, 47 | {types.ContainerJSON{NetworkSettings: &types.NetworkSettings{}}, "does-not-matter", ""}, 48 | } { 49 | require.Equal(t, tt.Want, GetHostPort(tt.Container, tt.PortID), tt) 50 | } 51 | } 52 | 53 | func TestRandLowerCaseLetterString(t *testing.T) { 54 | require.Empty(t, RandLowerCaseLetterString(0)) 55 | 56 | rand.Seed(1) 57 | require.Equal(t, "xvlbzgbaicmr", RandLowerCaseLetterString(12)) 58 | 59 | rand.Seed(1) 60 | require.Equal(t, "xvlbzgbaicmrajwwhthctcuaxhxkqf", RandLowerCaseLetterString(30)) 61 | } 62 | 63 | func TestCondenseHostName(t *testing.T) { 64 | for _, tt := range []struct { 65 | HostName, Want string 66 | }{ 67 | {"", ""}, 68 | {"test", "test"}, 69 | {"some-really-very-incredibly-long-hostname-that-is-greater-than-64-characters", "some-really-very-incredibly-lo_._-is-greater-than-64-characters"}, 70 | } { 71 | require.Equal(t, tt.Want, CondenseHostName(tt.HostName), tt) 72 | } 73 | } 74 | 75 | func TestSanitizeContainerName(t *testing.T) { 76 | for _, tt := range []struct { 77 | Name, Want string 78 | }{ 79 | {"hello-there", "hello-there"}, 80 | {"hello@there", "hello_there"}, 81 | {"hello@/there", "hello__there"}, 82 | // edge cases 83 | {"?", "_"}, 84 | {"", ""}, 85 | } { 86 | require.Equal(t, tt.Want, SanitizeContainerName(tt.Name), tt) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/cosmos/chain_export_test.go: -------------------------------------------------------------------------------- 1 | package cosmos_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/strangelove-ventures/interchaintest/v8" 9 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 10 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 11 | "github.com/strangelove-ventures/interchaintest/v8/testutil" 12 | "github.com/stretchr/testify/require" 13 | "go.uber.org/zap" 14 | ) 15 | 16 | func TestJunoStateExport(t *testing.T) { 17 | if testing.Short() { 18 | t.Skip("skipping in short mode") 19 | } 20 | 21 | chains := interchaintest.CreateChainWithConfig(t, numValsOne, numFullNodesZero, "juno", "v19.0.0-alpha.3", ibc.ChainConfig{}) 22 | chain := chains[0].(*cosmos.CosmosChain) 23 | 24 | enableBlockDB := false 25 | ctx, _, _, _ := interchaintest.BuildInitialChain(t, chains, enableBlockDB) 26 | 27 | HaltChainAndExportGenesis(ctx, t, chain, nil, 3) 28 | } 29 | 30 | func HaltChainAndExportGenesis(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, relayer ibc.Relayer, haltHeight int64) { 31 | timeoutCtx, timeoutCtxCancel := context.WithTimeout(ctx, time.Minute*2) 32 | defer timeoutCtxCancel() 33 | 34 | err := testutil.WaitForBlocks(timeoutCtx, int(haltHeight), chain) 35 | require.NoError(t, err, "chain did not halt at halt height") 36 | 37 | err = chain.StopAllNodes(ctx) 38 | require.NoError(t, err, "error stopping node(s)") 39 | 40 | state, err := chain.ExportState(ctx, int64(haltHeight)) 41 | require.NoError(t, err, "error exporting state") 42 | 43 | appToml := make(testutil.Toml) 44 | 45 | for _, node := range chain.Nodes() { 46 | err := node.OverwriteGenesisFile(ctx, []byte(state)) 47 | require.NoError(t, err) 48 | } 49 | 50 | for _, node := range chain.Nodes() { 51 | err := testutil.ModifyTomlConfigFile( 52 | ctx, 53 | zap.NewExample(), 54 | node.DockerClient, 55 | node.TestName, 56 | node.VolumeName, 57 | "config/app.toml", 58 | appToml, 59 | ) 60 | require.NoError(t, err) 61 | } 62 | 63 | err = chain.StartAllNodes(ctx) 64 | require.NoError(t, err, "error starting node(s)") 65 | 66 | timeoutCtx, timeoutCtxCancel = context.WithTimeout(ctx, time.Minute*2) 67 | defer timeoutCtxCancel() 68 | 69 | err = testutil.WaitForBlocks(timeoutCtx, int(5), chain) 70 | require.NoError(t, err, "chain did not produce blocks after halt") 71 | 72 | height, err := chain.Height(ctx) 73 | require.NoError(t, err, "error getting height after halt") 74 | 75 | require.Greater(t, int64(height), haltHeight, "height did not increment after halt") 76 | } 77 | -------------------------------------------------------------------------------- /blockdb/test_case_test.go: -------------------------------------------------------------------------------- 1 | package blockdb 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestCreateTestCase(t *testing.T) { 12 | t.Parallel() 13 | 14 | ctx := context.Background() 15 | 16 | t.Run("happy path", func(t *testing.T) { 17 | db := migratedDB() 18 | defer db.Close() 19 | 20 | tc, err := CreateTestCase(ctx, db, "SomeTest", "abc123") 21 | require.NoError(t, err) 22 | require.NotNil(t, tc) 23 | 24 | row := db.QueryRow(`SELECT name, created_at, git_sha FROM test_case LIMIT 1`) 25 | var ( 26 | gotName string 27 | gotTime string 28 | gotSha string 29 | ) 30 | err = row.Scan(&gotName, &gotTime, &gotSha) 31 | require.NoError(t, err) 32 | 33 | require.Equal(t, "SomeTest", gotName) 34 | require.Equal(t, "abc123", gotSha) 35 | 36 | ts, err := time.Parse(time.RFC3339, gotTime) 37 | require.NoError(t, err) 38 | require.WithinDuration(t, time.Now(), ts, 10*time.Second) 39 | }) 40 | 41 | t.Run("errors", func(t *testing.T) { 42 | db := emptyDB() 43 | _, err := CreateTestCase(ctx, db, "fail", "") 44 | require.Error(t, err) 45 | }) 46 | } 47 | 48 | func TestTestCase_AddChain(t *testing.T) { 49 | t.Parallel() 50 | 51 | ctx := context.Background() 52 | 53 | t.Run("happy path", func(t *testing.T) { 54 | db := migratedDB() 55 | defer db.Close() 56 | 57 | tc, err := CreateTestCase(ctx, db, "SomeTest", "abc") 58 | require.NoError(t, err) 59 | 60 | chain, err := tc.AddChain(ctx, "my-chain1", "penumbra") 61 | require.NoError(t, err) 62 | require.NotNil(t, chain) 63 | 64 | row := db.QueryRow(`SELECT chain_id, chain_type, fk_test_id, id FROM chain`) 65 | var ( 66 | gotChainID string 67 | gotChainType string 68 | gotTestID int 69 | gotPrimaryKey int64 70 | ) 71 | err = row.Scan(&gotChainID, &gotChainType, &gotTestID, &gotPrimaryKey) 72 | require.NoError(t, err) 73 | require.Equal(t, "my-chain1", gotChainID) 74 | require.Equal(t, "penumbra", gotChainType) 75 | require.Equal(t, 1, gotTestID) 76 | require.EqualValues(t, 1, gotPrimaryKey) 77 | 78 | _, err = tc.AddChain(ctx, "my-chain2", "test") 79 | require.NoError(t, err) 80 | }) 81 | 82 | t.Run("errors", func(t *testing.T) { 83 | db := migratedDB() 84 | defer db.Close() 85 | 86 | tc, err := CreateTestCase(ctx, db, "SomeTest", "abc") 87 | require.NoError(t, err) 88 | 89 | _, err = tc.AddChain(ctx, "my-chain", "cosmos") 90 | require.NoError(t, err) 91 | 92 | _, err = tc.AddChain(ctx, "my-chain", "cosmos") 93 | require.Error(t, err) 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /contract/cosmwasm/rust_optimizer.go: -------------------------------------------------------------------------------- 1 | package cosmwasm 2 | 3 | import ( 4 | "path/filepath" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | type Contract struct { 11 | DockerImage string 12 | Version string 13 | RelativePath string 14 | wasmBinPathChan chan string 15 | errChan chan error 16 | } 17 | 18 | // NewContract return a contract struct, populated with defaults and its relative path 19 | // relativePath is the relative path to the contract on local machine 20 | func NewContract(relativePath string) *Contract { 21 | return &Contract{ 22 | DockerImage: "cosmwasm/rust-optimizer", 23 | Version: "0.14.0", 24 | RelativePath: relativePath, 25 | } 26 | } 27 | 28 | // WithDockerImage sets a custom docker image to use 29 | func (c *Contract) WithDockerImage(image string) *Contract { 30 | c.DockerImage = image 31 | return c 32 | } 33 | 34 | // WithVersion sets a custom version to use 35 | func (c *Contract) WithVersion(version string) *Contract { 36 | c.Version = version 37 | return c 38 | } 39 | 40 | // Compile will compile the contract 41 | // cosmwasm/rust-optimizer is the expected docker image 42 | func (c *Contract) Compile() *Contract { 43 | c.wasmBinPathChan = make(chan string) 44 | c.errChan = make(chan error, 1) 45 | 46 | go func() { 47 | repoPathFull, err := compile(c.DockerImage, c.Version, c.RelativePath) 48 | if err != nil { 49 | c.errChan <- err 50 | return 51 | } 52 | 53 | // Form the path to the artifacts directory, used for checksum.txt and package.wasm 54 | artifactsPath := filepath.Join(repoPathFull, "artifacts") 55 | 56 | // Parse the checksums.txt for the create/wasm binary name 57 | checksumsPath := filepath.Join(artifactsPath, "checksums.txt") 58 | checksumsBz, err := os.ReadFile(checksumsPath) 59 | if err != nil { 60 | c.errChan <- fmt.Errorf("checksums read: %w", err) 61 | return 62 | } 63 | _, wasmBin, found := strings.Cut(string(checksumsBz), " ") 64 | if !found { 65 | c.errChan <- fmt.Errorf("wasm binary name not found") 66 | return 67 | } 68 | 69 | // Form the path to the wasm binary 70 | c.wasmBinPathChan <- filepath.Join(artifactsPath, strings.TrimSpace(wasmBin)) 71 | }() 72 | 73 | return c 74 | } 75 | 76 | // WaitForCompile will wait until compilation is complete, this can be called after chain setup 77 | // Successful compilation will return the binary location in a channel 78 | func (c *Contract) WaitForCompile() (string, error) { 79 | contractBinary := "" 80 | select { 81 | case err := <-c.errChan: 82 | return "", err 83 | case contractBinary = <-c.wasmBinPathChan: 84 | } 85 | return contractBinary, nil 86 | } -------------------------------------------------------------------------------- /chain/cosmos/codec.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import ( 4 | "cosmossdk.io/x/upgrade" 5 | "github.com/cosmos/cosmos-sdk/codec" 6 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/cosmos/cosmos-sdk/types/module/testutil" 9 | "github.com/cosmos/cosmos-sdk/x/auth" 10 | authTx "github.com/cosmos/cosmos-sdk/x/auth/tx" 11 | "github.com/cosmos/cosmos-sdk/x/bank" 12 | "github.com/cosmos/cosmos-sdk/x/consensus" 13 | distr "github.com/cosmos/cosmos-sdk/x/distribution" 14 | "github.com/cosmos/cosmos-sdk/x/genutil" 15 | genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" 16 | "github.com/cosmos/cosmos-sdk/x/gov" 17 | govclient "github.com/cosmos/cosmos-sdk/x/gov/client" 18 | "github.com/cosmos/cosmos-sdk/x/mint" 19 | "github.com/cosmos/cosmos-sdk/x/params" 20 | paramsclient "github.com/cosmos/cosmos-sdk/x/params/client" 21 | "github.com/cosmos/cosmos-sdk/x/slashing" 22 | "github.com/cosmos/cosmos-sdk/x/staking" 23 | "github.com/cosmos/ibc-go/modules/capability" 24 | 25 | transfer "github.com/cosmos/ibc-go/v8/modules/apps/transfer" 26 | ibccore "github.com/cosmos/ibc-go/v8/modules/core" 27 | ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" 28 | ccvprovider "github.com/cosmos/interchain-security/v5/x/ccv/provider" 29 | ibcwasm "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos/08-wasm-types" 30 | ) 31 | 32 | func DefaultEncoding() testutil.TestEncodingConfig { 33 | return testutil.MakeTestEncodingConfig( 34 | auth.AppModuleBasic{}, 35 | genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), 36 | bank.AppModuleBasic{}, 37 | capability.AppModuleBasic{}, 38 | staking.AppModuleBasic{}, 39 | mint.AppModuleBasic{}, 40 | distr.AppModuleBasic{}, 41 | gov.NewAppModuleBasic( 42 | []govclient.ProposalHandler{ 43 | paramsclient.ProposalHandler, 44 | }, 45 | ), 46 | params.AppModuleBasic{}, 47 | slashing.AppModuleBasic{}, 48 | upgrade.AppModuleBasic{}, 49 | consensus.AppModuleBasic{}, 50 | transfer.AppModuleBasic{}, 51 | ibccore.AppModuleBasic{}, 52 | ibctm.AppModuleBasic{}, 53 | ibcwasm.AppModuleBasic{}, 54 | ccvprovider.AppModuleBasic{}, 55 | ) 56 | } 57 | 58 | func decodeTX(interfaceRegistry codectypes.InterfaceRegistry, txbz []byte) (sdk.Tx, error) { 59 | cdc := codec.NewProtoCodec(interfaceRegistry) 60 | return authTx.DefaultTxDecoder(cdc)(txbz) 61 | } 62 | 63 | func encodeTxToJSON(interfaceRegistry codectypes.InterfaceRegistry, tx sdk.Tx) ([]byte, error) { 64 | cdc := codec.NewProtoCodec(interfaceRegistry) 65 | return authTx.DefaultJSONTxEncoder(cdc)(tx) 66 | } 67 | -------------------------------------------------------------------------------- /testutil/toml.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "reflect" 8 | 9 | "github.com/BurntSushi/toml" 10 | "github.com/docker/docker/client" 11 | "github.com/strangelove-ventures/interchaintest/v8/dockerutil" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | // Toml is used for holding the decoded state of a toml config file. 16 | type Toml map[string]any 17 | 18 | // RecursiveModifyToml will apply toml modifications at the current depth, 19 | // then recurse for new depths. 20 | func RecursiveModifyToml(c map[string]any, modifications Toml) error { 21 | for key, value := range modifications { 22 | if reflect.ValueOf(value).Kind() == reflect.Map { 23 | cV, ok := c[key] 24 | if !ok { 25 | // Did not find section in existing config, populating fresh. 26 | cV = make(Toml) 27 | } 28 | // Retrieve existing config to apply overrides to. 29 | cVM, ok := cV.(map[string]any) 30 | if !ok { 31 | // if the config does not exist, we should create a blank one to allow creation 32 | cVM = make(Toml) 33 | } 34 | if err := RecursiveModifyToml(cVM, value.(Toml)); err != nil { 35 | return err 36 | } 37 | c[key] = cVM 38 | } else { 39 | // Not a map, so we can set override value directly. 40 | c[key] = value 41 | } 42 | } 43 | return nil 44 | } 45 | 46 | // ModifyTomlConfigFile reads, modifies, then overwrites a toml config file, useful for config.toml, app.toml, etc. 47 | func ModifyTomlConfigFile( 48 | ctx context.Context, 49 | logger *zap.Logger, 50 | dockerClient *client.Client, 51 | testName string, 52 | volumeName string, 53 | filePath string, 54 | modifications Toml, 55 | ) error { 56 | fr := dockerutil.NewFileRetriever(logger, dockerClient, testName) 57 | config, err := fr.SingleFileContent(ctx, volumeName, filePath) 58 | if err != nil { 59 | return fmt.Errorf("failed to retrieve %s: %w", filePath, err) 60 | } 61 | 62 | var c Toml 63 | if err := toml.Unmarshal(config, &c); err != nil { 64 | return fmt.Errorf("failed to unmarshal %s: %w", filePath, err) 65 | } 66 | 67 | if err := RecursiveModifyToml(c, modifications); err != nil { 68 | return fmt.Errorf("failed to modify %s: %w", filePath, err) 69 | } 70 | 71 | buf := new(bytes.Buffer) 72 | if err := toml.NewEncoder(buf).Encode(c); err != nil { 73 | return fmt.Errorf("failed to encode %s: %w", filePath, err) 74 | } 75 | 76 | fw := dockerutil.NewFileWriter(logger, dockerClient, testName) 77 | if err := fw.WriteFile(ctx, volumeName, filePath, buf.Bytes()); err != nil { 78 | return fmt.Errorf("overwriting %s: %w", filePath, err) 79 | } 80 | 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /blockdb/chain.go: -------------------------------------------------------------------------------- 1 | package blockdb 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "hash/fnv" 8 | 9 | "golang.org/x/sync/singleflight" 10 | ) 11 | 12 | // Chain tracks its blocks and a block's transactions. 13 | type Chain struct { 14 | db *sql.DB 15 | id int64 16 | single singleflight.Group 17 | } 18 | 19 | type transactions []Tx 20 | 21 | func (txs transactions) Hash() []byte { 22 | h := fnv.New32() 23 | for _, tx := range txs { 24 | h.Write(tx.Data) 25 | } 26 | return h.Sum(nil) 27 | } 28 | 29 | // SaveBlock tracks a block at height with its transactions. 30 | // This method is idempotent and can be safely called multiple times with the same arguments. 31 | // The txs should be human-readable. 32 | func (chain *Chain) SaveBlock(ctx context.Context, height int64, txs []Tx) error { 33 | k := fmt.Sprintf("%d-%x", height, transactions(txs).Hash()) 34 | _, err, _ := chain.single.Do(k, func() (any, error) { 35 | return nil, chain.saveBlock(ctx, height, txs) 36 | }) 37 | return err 38 | } 39 | 40 | func (chain *Chain) saveBlock(ctx context.Context, height int64, txs transactions) error { 41 | dbTx, err := chain.db.BeginTx(ctx, nil) 42 | if err != nil { 43 | return err 44 | } 45 | defer func() { _ = dbTx.Rollback() }() 46 | 47 | res, err := dbTx.ExecContext(ctx, `INSERT OR REPLACE INTO block(height, fk_chain_id, created_at) VALUES (?, ?, ?)`, height, chain.id, nowRFC3339()) 48 | if err != nil { 49 | return fmt.Errorf("insert into block: %w", err) 50 | } 51 | 52 | blockID, err := res.LastInsertId() 53 | if err != nil { 54 | return err 55 | } 56 | for _, tx := range txs { 57 | txRes, err := dbTx.ExecContext(ctx, `INSERT INTO tx(data, fk_block_id) VALUES (?, ?)`, string(tx.Data), blockID) 58 | if err != nil { 59 | return fmt.Errorf("insert into tx: %w", err) 60 | } 61 | txID, err := txRes.LastInsertId() 62 | if err != nil { 63 | return err 64 | } 65 | 66 | for _, e := range tx.Events { 67 | eventRes, err := dbTx.ExecContext(ctx, `INSERT INTO tendermint_event(type, fk_tx_id) VALUES (?, ?)`, e.Type, txID) 68 | if err != nil { 69 | return fmt.Errorf("insert into tendermint_event: %w", err) 70 | } 71 | 72 | eventID, err := eventRes.LastInsertId() 73 | if err != nil { 74 | return err 75 | } 76 | 77 | for _, attr := range e.Attributes { 78 | _, err := dbTx.ExecContext(ctx, `INSERT INTO tendermint_event_attr(key, value, fk_event_id) VALUES (?, ?, ?)`, attr.Key, attr.Value, eventID) 79 | if err != nil { 80 | return fmt.Errorf("insert into tendermint_event_attr: %w", err) 81 | } 82 | } 83 | } 84 | } 85 | 86 | return dbTx.Commit() 87 | } 88 | -------------------------------------------------------------------------------- /blockdb/tui/help.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gdamore/tcell/v2" 7 | "github.com/rivo/tview" 8 | ) 9 | 10 | type keyBinding struct { 11 | Key string // Single key or combination of keys. 12 | Help string // Very short help text describing the key's action. 13 | } 14 | 15 | var baseHelpKeys = []keyBinding{ 16 | {"esc", "go back"}, 17 | {"ctl+c", "exit"}, 18 | } 19 | 20 | func bindingsWithBase(bindings ...[]keyBinding) []keyBinding { 21 | var all []keyBinding 22 | for i := range bindings { 23 | all = append(all, bindings[i]...) 24 | } 25 | return append(all, baseHelpKeys...) 26 | } 27 | 28 | var ( 29 | tableNavKeys = []keyBinding{ 30 | {fmt.Sprintf("%c/k", tcell.RuneUArrow), "move up"}, 31 | {fmt.Sprintf("%c/j", tcell.RuneDArrow), "move down"}, 32 | } 33 | textNavKeys = []keyBinding{ 34 | {fmt.Sprintf("%c/k", tcell.RuneUArrow), "scroll up"}, 35 | {fmt.Sprintf("%c/j", tcell.RuneDArrow), "scroll down"}, 36 | {"g", "go to top"}, 37 | {"shift+g", "go to bottom"}, 38 | {"ctrl+b", "page up"}, 39 | {"ctrl+f", "page down"}, 40 | } 41 | 42 | keyMap = map[mainContent][]keyBinding{ 43 | testCasesMain: bindingsWithBase([]keyBinding{{"m", "cosmos messages"}, {"enter", "view txs"}}, tableNavKeys), 44 | cosmosMessagesMain: bindingsWithBase(tableNavKeys), 45 | txDetailMain: bindingsWithBase([]keyBinding{ 46 | {"[", "previous tx"}, 47 | {"]", "next tx"}, 48 | {"/", "toggle search"}, 49 | {"c", "copy all txs"}, 50 | }, textNavKeys), 51 | errorModalMain: bindingsWithBase(nil), 52 | } 53 | ) 54 | 55 | type helpView struct { 56 | *tview.Table 57 | } 58 | 59 | func newHelpView() *helpView { 60 | tbl := tview.NewTable().SetBorders(false) 61 | tbl.SetBorder(false) 62 | return &helpView{tbl} 63 | } 64 | 65 | // Replace serves as a hook to clear all keys and update the help table view with new keys. 66 | func (view *helpView) Replace(keys []keyBinding) *helpView { 67 | view.Table.Clear() 68 | keyCell := func(s string) *tview.TableCell { 69 | return tview.NewTableCell("<" + s + ">"). 70 | SetTextColor(tcell.ColorBlue) 71 | } 72 | textCell := func(s string) *tview.TableCell { 73 | return tview.NewTableCell(s). 74 | SetStyle(textStyle.Attributes(tcell.AttrDim)) 75 | } 76 | var ( 77 | row int 78 | col int 79 | ) 80 | for _, binding := range keys { 81 | // Only allow 6 help items per row or else help items will not be visible. 82 | if row > 0 && row%6 == 0 { 83 | row = 0 84 | col += 2 85 | } 86 | view.Table.SetCell(row, col, keyCell(binding.Key)) 87 | view.Table.SetCell(row, col+1, textCell(binding.Help)) 88 | row++ 89 | } 90 | return view 91 | } 92 | -------------------------------------------------------------------------------- /chain/cosmos/module_vesting.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "path" 8 | 9 | vestingcli "github.com/cosmos/cosmos-sdk/x/auth/vesting/client/cli" 10 | "github.com/strangelove-ventures/interchaintest/v8/dockerutil" 11 | ) 12 | 13 | // VestingCreateAccount creates a new vesting account funded with an allocation of tokens. The account can either be a delayed or continuous vesting account, which is determined by the '--delayed' flag. 14 | // All vesting accounts created will have their start time set by the committed block's time. The end_time must be provided as a UNIX epoch timestamp. 15 | func (tn *ChainNode) VestingCreateAccount(ctx context.Context, keyName string, toAddr string, coin string, endTime int64, flags ...string) error { 16 | cmd := []string{ 17 | "vesting", "create-vesting-account", toAddr, coin, fmt.Sprintf("%d", endTime), 18 | } 19 | 20 | if len(flags) > 0 { 21 | cmd = append(cmd, flags...) 22 | } 23 | 24 | _, err := tn.ExecTx(ctx, keyName, cmd...) 25 | return err 26 | } 27 | 28 | // VestingCreatePermanentLockedAccount creates a new vesting account funded with an allocation of tokens that are locked indefinitely. 29 | func (tn *ChainNode) VestingCreatePermanentLockedAccount(ctx context.Context, keyName string, toAddr string, coin string, flags ...string) error { 30 | cmd := []string{ 31 | "vesting", "create-permanent-locked-account", toAddr, coin, 32 | } 33 | 34 | if len(flags) > 0 { 35 | cmd = append(cmd, flags...) 36 | } 37 | 38 | _, err := tn.ExecTx(ctx, keyName, cmd...) 39 | return err 40 | } 41 | 42 | // VestingCreatePeriodicAccount is a sequence of coins and period length in seconds. 43 | // Periods are sequential, in that the duration of of a period only starts at the end of the previous period. 44 | // The duration of the first period starts upon account creation. 45 | func (tn *ChainNode) VestingCreatePeriodicAccount(ctx context.Context, keyName string, toAddr string, periods vestingcli.VestingData, flags ...string) error { 46 | file := "periods.json" 47 | periodsJSON, err := json.MarshalIndent(periods, "", " ") 48 | if err != nil { 49 | return err 50 | } 51 | 52 | fw := dockerutil.NewFileWriter(tn.logger(), tn.DockerClient, tn.TestName) 53 | if err := fw.WriteFile(ctx, tn.VolumeName, file, periodsJSON); err != nil { 54 | return fmt.Errorf("writing periods JSON file to docker volume: %w", err) 55 | } 56 | 57 | cmd := []string{ 58 | "vesting", "create-periodic-vesting-account", toAddr, path.Join(tn.HomeDir(), file), 59 | } 60 | 61 | if len(flags) > 0 { 62 | cmd = append(cmd, flags...) 63 | } 64 | 65 | _, err = tn.ExecTx(ctx, keyName, cmd...) 66 | return err 67 | } 68 | -------------------------------------------------------------------------------- /test_user.go: -------------------------------------------------------------------------------- 1 | package interchaintest 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "cosmossdk.io/math" 9 | "github.com/strangelove-ventures/interchaintest/v8/dockerutil" 10 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 11 | "github.com/strangelove-ventures/interchaintest/v8/testutil" 12 | "github.com/stretchr/testify/require" 13 | "golang.org/x/sync/errgroup" 14 | ) 15 | 16 | // GetAndFundTestUserWithMnemonic restores a user using the given mnemonic 17 | // and funds it with the native chain denom. 18 | // The caller should wait for some blocks to complete before the funds will be accessible. 19 | func GetAndFundTestUserWithMnemonic( 20 | ctx context.Context, 21 | keyNamePrefix, mnemonic string, 22 | amount math.Int, 23 | chain ibc.Chain, 24 | ) (ibc.Wallet, error) { 25 | chainCfg := chain.Config() 26 | keyName := fmt.Sprintf("%s-%s-%s", keyNamePrefix, chainCfg.ChainID, dockerutil.RandLowerCaseLetterString(3)) 27 | user, err := chain.BuildWallet(ctx, keyName, mnemonic) 28 | if err != nil { 29 | return nil, fmt.Errorf("failed to get source user wallet: %w", err) 30 | } 31 | 32 | err = chain.SendFunds(ctx, FaucetAccountKeyName, ibc.WalletAmount{ 33 | Address: user.FormattedAddress(), 34 | Amount: amount, 35 | Denom: chainCfg.Denom, 36 | }) 37 | if err != nil { 38 | return nil, fmt.Errorf("failed to get funds from faucet: %w", err) 39 | } 40 | 41 | // If this chain is an instance of Penumbra we need to initialize a new pclientd instance for the 42 | // newly created test user account. 43 | err = CreatePenumbraClient(ctx, chain, keyName) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return user, nil 49 | } 50 | 51 | // GetAndFundTestUsers generates and funds chain users with the native chain denom. 52 | // The caller should wait for some blocks to complete before the funds will be accessible. 53 | func GetAndFundTestUsers( 54 | t *testing.T, 55 | ctx context.Context, 56 | keyNamePrefix string, 57 | amount math.Int, 58 | chains ...ibc.Chain, 59 | ) []ibc.Wallet { 60 | users := make([]ibc.Wallet, len(chains)) 61 | var eg errgroup.Group 62 | for i, chain := range chains { 63 | i := i 64 | chain := chain 65 | eg.Go(func() error { 66 | user, err := GetAndFundTestUserWithMnemonic(ctx, keyNamePrefix, "", amount, chain) 67 | if err != nil { 68 | return err 69 | } 70 | users[i] = user 71 | return nil 72 | }) 73 | } 74 | require.NoError(t, eg.Wait()) 75 | 76 | // TODO(nix 05-17-2022): Map with generics once using go 1.18 77 | chainHeights := make([]testutil.ChainHeighter, len(chains)) 78 | for i := range chains { 79 | chainHeights[i] = chains[i] 80 | } 81 | return users 82 | } 83 | -------------------------------------------------------------------------------- /docs/logging.md: -------------------------------------------------------------------------------- 1 | # Logging 2 | 3 | This code uses the [zap](https://pkg.go.dev/go.uber.org/zap) logging framework. 4 | 5 | Zap is a production quality logging library that is performant and highly configurable. 6 | Zap produces structured logging that can be easily machine-parsed, 7 | so that people can easily use tooling to filter through logs, 8 | or logs can easily be sent to log aggregation tooling. 9 | 10 | ## Conventions 11 | 12 | ### Instantiation 13 | 14 | Structs should hold a reference to their own `*zap.Logger` instance, as an unexported field named `log`. 15 | Callers should provide the logger instance as the first argument to a `NewFoo` method, setting any extra fields with the `With` method externally. 16 | For example: 17 | 18 | ```go 19 | path := "/tmp/foo" 20 | foo := NewFoo(log.With(zap.String("path", path)), path) 21 | ``` 22 | 23 | ### Usage 24 | 25 | Log messages should begin with an uppercase letter and should not end with any punctuation. 26 | Log messages should be constant string literals; dynamic content is to be set as log fields. 27 | Names of log fields should be all lowercase `snake_case` format. 28 | 29 | ```go 30 | // Do this: 31 | x.log.Debug("Updated height", zap.Int64("height", height)) 32 | 33 | // Don't do this: 34 | x.log.Debug(fmt.Sprintf("Updated height to %d.", height)) 35 | ``` 36 | 37 | Errors are intended to be logged with `zap.Error(err)`. 38 | Error values should only be logged when they are not returned. 39 | 40 | ```go 41 | // Do this: 42 | for _, x := range xs { 43 | if err := x.Validate(); err != nil { 44 | y.log.Info("Skipping x that failed validation", zap.String("id", x.ID), zap.Error(err)) 45 | continue 46 | } 47 | } 48 | 49 | // Don't do this: 50 | if err := x.Validate(); err != nil { 51 | y.log.Info("X failed validation", zap.String("id", x.ID), zap.Error(err)) 52 | return err // Bad! 53 | // Returning an error that has already been logged may cause the same error 54 | // to be logged many times. 55 | } 56 | ``` 57 | 58 | #### Log levels 59 | 60 | Debug level messages are off by default. 61 | Debug messages should be used sparingly; 62 | they are typically only intended for consumption by developers when actively debugging an issue. 63 | 64 | Info level messages and higher are on by default. 65 | They may contain any general information useful to an operator. 66 | It should be safe to discard all info level messages if an operator so desires. 67 | 68 | Warn level messages should be used to indicate a problem that may worsen without operator intervention. 69 | 70 | Error level messages should only be used to indicate a serious problem that cannot automatically recover. 71 | Error level messages should be reserved for events that are worthy of paging an engineer. 72 | 73 | Do not use Fatal or Panic level messages. -------------------------------------------------------------------------------- /tempdir_test.go: -------------------------------------------------------------------------------- 1 | package interchaintest_test 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strings" 7 | "testing" 8 | 9 | interchaintest "github.com/strangelove-ventures/interchaintest/v8" 10 | "github.com/strangelove-ventures/interchaintest/v8/mocktesting" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestTempDir_Cleanup(t *testing.T) { 15 | origKeep := interchaintest.KeepingTempDirOnFailure() 16 | defer func() { 17 | interchaintest.KeepTempDirOnFailure(origKeep) 18 | }() 19 | 20 | t.Run("keep=true", func(t *testing.T) { 21 | interchaintest.KeepTempDirOnFailure(true) 22 | 23 | t.Run("test passed", func(t *testing.T) { 24 | mt := mocktesting.NewT("t") 25 | 26 | dir := interchaintest.TempDir(mt) 27 | require.DirExists(t, dir) 28 | 29 | mt.RunCleanups() 30 | 31 | require.NoDirExists(t, dir) 32 | require.Empty(t, mt.Logs) 33 | }) 34 | 35 | t.Run("test failed", func(t *testing.T) { 36 | mt := mocktesting.NewT("t") 37 | 38 | dir := interchaintest.TempDir(mt) 39 | require.DirExists(t, dir) 40 | defer func() { _ = os.RemoveAll(dir) }() 41 | 42 | mt.Fail() 43 | 44 | mt.RunCleanups() 45 | 46 | // Directory still exists after cleanups. 47 | require.DirExists(t, dir) 48 | 49 | // And the last log message mentions the directory. 50 | require.NotEmpty(t, mt.Logs) 51 | require.Contains(t, mt.Logs[len(mt.Logs)-1], dir) 52 | }) 53 | }) 54 | 55 | t.Run("keep=false", func(t *testing.T) { 56 | interchaintest.KeepTempDirOnFailure(false) 57 | 58 | for name, failed := range map[string]bool{ 59 | "test passed": false, 60 | "test failed": true, 61 | } { 62 | failed := failed 63 | t.Run(name, func(t *testing.T) { 64 | mt := mocktesting.NewT("t") 65 | 66 | dir := interchaintest.TempDir(mt) 67 | require.DirExists(t, dir) 68 | 69 | if failed { 70 | mt.Fail() 71 | } 72 | 73 | mt.RunCleanups() 74 | 75 | require.NoDirExists(t, dir) 76 | require.Empty(t, mt.Logs) 77 | }) 78 | } 79 | }) 80 | } 81 | 82 | func TestTempDir_Naming(t *testing.T) { 83 | const testNamePrefix = "TestTempDir_Naming" 84 | tmpRoot := os.TempDir() 85 | 86 | for name, expDir := range map[string]string{ 87 | "A": "A", 88 | "Foo_Bar": "Foo_Bar", 89 | "1/2 full": "12_full", 90 | "/..": "..", // Gets prefix appended, so will not traverse upwards. 91 | "\x00\xFF": "", 92 | } { 93 | wantDir := filepath.Join(tmpRoot, testNamePrefix+expDir) 94 | t.Run(name, func(t *testing.T) { 95 | dir := interchaintest.TempDir(t) 96 | 97 | require.Truef( 98 | t, 99 | strings.HasPrefix(dir, wantDir), 100 | "directory %s should have started with %s", dir, wantDir, 101 | ) 102 | }) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /examples/cosmwasm/rust-optimizer/contract/src/contract.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::ContractError, 3 | msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, OwnerResponse}, 4 | state::OWNER, 5 | }; 6 | use cosmwasm_std::{ 7 | entry_point, 8 | to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, 9 | }; 10 | 11 | #[entry_point] 12 | pub fn instantiate( 13 | deps: DepsMut, 14 | _env: Env, 15 | info: MessageInfo, 16 | _msg: InstantiateMsg, 17 | ) -> Result { 18 | OWNER.save(deps.storage, &info.sender)?; 19 | 20 | Ok(Response::new() 21 | .add_attribute("action", "instantiate") 22 | .add_attribute("owner", info.sender)) 23 | } 24 | 25 | /// Allow contract to be able to migrate if admin is set. 26 | /// This provides option for migration, if admin is not set, this functionality will be disabled 27 | #[entry_point] 28 | pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { 29 | Ok(Response::new().add_attribute("action", "migrate")) 30 | } 31 | 32 | #[entry_point] 33 | pub fn execute( 34 | deps: DepsMut, 35 | _env: Env, 36 | info: MessageInfo, 37 | msg: ExecuteMsg, 38 | ) -> Result { 39 | match msg { 40 | // Admin functions 41 | ExecuteMsg::ChangeContractOwner { new_owner } => 42 | change_contract_owner(deps, info, new_owner), 43 | } 44 | } 45 | 46 | fn change_contract_owner( 47 | deps: DepsMut, 48 | info: MessageInfo, 49 | new_owner: String, 50 | ) -> Result { 51 | // Only allow current contract owner to change owner 52 | check_is_contract_owner(deps.as_ref(), info.sender)?; 53 | 54 | // validate that new owner is a valid address 55 | let new_owner_addr = deps.api.addr_validate(&new_owner)?; 56 | 57 | // update the contract owner in the contract config 58 | OWNER.save(deps.storage, &new_owner_addr)?; 59 | 60 | // return OK 61 | Ok(Response::new() 62 | .add_attribute("action", "change_contract_owner") 63 | .add_attribute("new_owner", new_owner)) 64 | } 65 | 66 | fn check_is_contract_owner(deps: Deps, sender: Addr) -> Result<(), ContractError> { 67 | let owner = OWNER.load(deps.storage)?; 68 | if owner != sender { 69 | Err(ContractError::Unauthorized {}) 70 | } else { 71 | Ok(()) 72 | } 73 | } 74 | 75 | #[entry_point] 76 | pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { 77 | match msg { 78 | QueryMsg::Owner {} => to_binary(&query_owner(deps)?), 79 | } 80 | } 81 | 82 | fn query_owner(deps: Deps) -> StdResult { 83 | let owner = OWNER.load(deps.storage)?; 84 | Ok(OwnerResponse { 85 | address: owner.to_string(), 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /local-interchain/interchain/handlers/uploader.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 12 | "github.com/strangelove-ventures/localinterchain/interchain/util" 13 | ) 14 | 15 | type upload struct { 16 | ctx context.Context 17 | vals map[string][]*cosmos.ChainNode 18 | 19 | authKey string 20 | } 21 | 22 | type Uploader struct { 23 | ChainId string `json:"chain_id"` 24 | NodeIndex int `json:"node_index"` 25 | FilePath string `json:"file_path"` 26 | 27 | // Upload-Type: cosmwasm only 28 | KeyName string `json:"key_name,omitempty"` 29 | AuthKey string `json:"auth_key,omitempty"` 30 | } 31 | 32 | func NewUploader(ctx context.Context, vals map[string][]*cosmos.ChainNode, authKey string) *upload { 33 | return &upload{ 34 | ctx: ctx, 35 | vals: vals, 36 | authKey: authKey, 37 | } 38 | } 39 | 40 | func (u *upload) PostUpload(w http.ResponseWriter, r *http.Request) { 41 | var upload Uploader 42 | err := json.NewDecoder(r.Body).Decode(&upload) 43 | if err != nil { 44 | util.WriteError(w, err) 45 | return 46 | } 47 | 48 | if u.authKey != "" && u.authKey != upload.AuthKey { 49 | util.WriteError(w, fmt.Errorf("invalid `auth_key`")) 50 | return 51 | } 52 | 53 | srcPath := upload.FilePath 54 | if _, err := os.Stat(srcPath); os.IsNotExist(err) { 55 | util.WriteError(w, fmt.Errorf("file %s does not exist on the source machine", srcPath)) 56 | return 57 | } 58 | 59 | chainId := upload.ChainId 60 | if _, ok := u.vals[chainId]; !ok { 61 | util.Write(w, []byte(fmt.Sprintf(`{"error":"chain_id %s not found"}`, chainId))) 62 | return 63 | } 64 | 65 | nodeIdx := upload.NodeIndex 66 | if len(u.vals[chainId]) <= nodeIdx { 67 | util.Write(w, []byte(fmt.Sprintf(`{"error":"node_index %d not found"}`, nodeIdx))) 68 | return 69 | } 70 | 71 | val := u.vals[chainId][nodeIdx] 72 | 73 | headerType := r.Header.Get("Upload-Type") 74 | switch headerType { 75 | case "cosmwasm": 76 | // Upload & Store the contract on chain. 77 | codeId, err := val.StoreContract(u.ctx, upload.KeyName, srcPath) 78 | if err != nil { 79 | util.WriteError(w, err) 80 | return 81 | } 82 | 83 | util.Write(w, []byte(fmt.Sprintf(`{"code_id":%s}`, codeId))) 84 | return 85 | default: 86 | // Upload the file to the docker volume (val[0]). 87 | _, file := filepath.Split(srcPath) 88 | if err := val.CopyFile(u.ctx, srcPath, file); err != nil { 89 | util.WriteError(w, fmt.Errorf(`{"error":"writing contract file to docker volume: %w"}`, err)) 90 | return 91 | } 92 | 93 | home := val.HomeDir() 94 | fileLoc := filepath.Join(home, file) 95 | util.Write(w, []byte(fmt.Sprintf(`{"success":"file uploaded to %s","location":"%s"}`, chainId, fileLoc))) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract1/src/contract.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::ContractError, 3 | msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, OwnerResponse}, 4 | state::OWNER, 5 | }; 6 | use cosmwasm_std::{ 7 | entry_point, 8 | to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, 9 | }; 10 | 11 | #[entry_point] 12 | pub fn instantiate( 13 | deps: DepsMut, 14 | _env: Env, 15 | info: MessageInfo, 16 | _msg: InstantiateMsg, 17 | ) -> Result { 18 | OWNER.save(deps.storage, &info.sender)?; 19 | 20 | Ok(Response::new() 21 | .add_attribute("action", "instantiate") 22 | .add_attribute("owner", info.sender)) 23 | } 24 | 25 | /// Allow contract to be able to migrate if admin is set. 26 | /// This provides option for migration, if admin is not set, this functionality will be disabled 27 | #[entry_point] 28 | pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { 29 | Ok(Response::new().add_attribute("action", "migrate")) 30 | } 31 | 32 | #[entry_point] 33 | pub fn execute( 34 | deps: DepsMut, 35 | _env: Env, 36 | info: MessageInfo, 37 | msg: ExecuteMsg, 38 | ) -> Result { 39 | match msg { 40 | // Admin functions 41 | ExecuteMsg::ChangeContractOwner { new_owner } => 42 | change_contract_owner(deps, info, new_owner), 43 | } 44 | } 45 | 46 | fn change_contract_owner( 47 | deps: DepsMut, 48 | info: MessageInfo, 49 | new_owner: String, 50 | ) -> Result { 51 | // Only allow current contract owner to change owner 52 | check_is_contract_owner(deps.as_ref(), info.sender)?; 53 | 54 | // validate that new owner is a valid address 55 | let new_owner_addr = deps.api.addr_validate(&new_owner)?; 56 | 57 | // update the contract owner in the contract config 58 | OWNER.save(deps.storage, &new_owner_addr)?; 59 | 60 | // return OK 61 | Ok(Response::new() 62 | .add_attribute("action", "change_contract_owner") 63 | .add_attribute("new_owner", new_owner)) 64 | } 65 | 66 | fn check_is_contract_owner(deps: Deps, sender: Addr) -> Result<(), ContractError> { 67 | let owner = OWNER.load(deps.storage)?; 68 | if owner != sender { 69 | Err(ContractError::Unauthorized {}) 70 | } else { 71 | Ok(()) 72 | } 73 | } 74 | 75 | #[entry_point] 76 | pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { 77 | match msg { 78 | QueryMsg::Owner {} => to_binary(&query_owner(deps)?), 79 | } 80 | } 81 | 82 | fn query_owner(deps: Deps) -> StdResult { 83 | let owner = OWNER.load(deps.storage)?; 84 | Ok(OwnerResponse { 85 | address: owner.to_string(), 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /blockdb/tui/model.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/atotto/clipboard" 8 | "github.com/rivo/tview" 9 | "github.com/strangelove-ventures/interchaintest/v8/blockdb" 10 | ) 11 | 12 | //go:generate go run golang.org/x/tools/cmd/stringer -type=mainContent 13 | 14 | // mainContent is the primary content for user interaction in the UI akin to html
. 15 | type mainContent int 16 | 17 | const ( 18 | testCasesMain mainContent = iota 19 | cosmosMessagesMain 20 | txDetailMain 21 | errorModalMain 22 | ) 23 | 24 | type mainStack []mainContent 25 | 26 | func (stack mainStack) Push(s mainContent) []mainContent { return append(stack, s) } 27 | func (stack mainStack) Current() mainContent { return stack[len(stack)-1] } 28 | func (stack mainStack) Pop() []mainContent { return stack[:len(stack)-1] } 29 | 30 | // QueryService fetches data from a database. 31 | type QueryService interface { 32 | CosmosMessages(ctx context.Context, chainPkey int64) ([]blockdb.CosmosMessageResult, error) 33 | Transactions(ctx context.Context, chainPkey int64) ([]blockdb.TxResult, error) 34 | } 35 | 36 | // Model encapsulates state that updates a view. 37 | type Model struct { 38 | querySvc QueryService 39 | databasePath string 40 | schemaVersion string 41 | schemaDate time.Time 42 | testCases []blockdb.TestCaseResult 43 | 44 | layout *tview.Flex 45 | 46 | // stack keeps tracks of primary content pushed and popped 47 | stack mainStack 48 | 49 | // write to the system clipboard 50 | clipboard func(text string) error 51 | } 52 | 53 | // NewModel returns a valid *Model. 54 | func NewModel( 55 | querySvc QueryService, 56 | databasePath string, 57 | schemaVersion string, 58 | schemaDate time.Time, 59 | testCases []blockdb.TestCaseResult, 60 | ) *Model { 61 | m := &Model{ 62 | querySvc: querySvc, 63 | databasePath: databasePath, 64 | schemaVersion: schemaVersion, 65 | schemaDate: schemaDate, 66 | testCases: testCases, 67 | stack: mainStack{testCasesMain}, 68 | clipboard: clipboard.WriteAll, 69 | } 70 | 71 | flex := tview.NewFlex().SetDirection(tview.FlexRow) 72 | flex.SetBackgroundColor(backgroundColor).SetBorder(false) 73 | // Setting fixed size keeps the header height stable, so it will show all help keys. 74 | flex.AddItem(headerView(m), 6, 1, false) 75 | 76 | // The primary view is a page view to act like a stack where we can push and pop views. 77 | // Flex and grid views do not allow a "stack-like" behavior. 78 | pages := tview.NewPages() 79 | pages.AddAndSwitchToPage(m.stack[0].String(), testCasesView(m), true) 80 | flex.AddItem(pages, 0, 10, true) 81 | 82 | m.layout = flex 83 | 84 | return m 85 | } 86 | 87 | // RootView is a root view for a tview.Application. 88 | func (m *Model) RootView() *tview.Flex { 89 | return m.layout 90 | } 91 | -------------------------------------------------------------------------------- /examples/cosmwasm/workspace-optimizer/workspace/contracts/contract2/src/contract.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::ContractError, 3 | msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, OwnerResponse}, 4 | state::OWNER, 5 | }; 6 | use cosmwasm_std::{ 7 | entry_point, 8 | to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, 9 | }; 10 | 11 | #[entry_point] 12 | pub fn instantiate( 13 | deps: DepsMut, 14 | _env: Env, 15 | info: MessageInfo, 16 | _msg: InstantiateMsg, 17 | ) -> Result { 18 | OWNER.save(deps.storage, &info.sender)?; 19 | 20 | Ok(Response::new() 21 | .add_attribute("action", "instantiate-contract2") 22 | .add_attribute("owner", info.sender)) 23 | } 24 | 25 | /// Allow contract to be able to migrate if admin is set. 26 | /// This provides option for migration, if admin is not set, this functionality will be disabled 27 | #[entry_point] 28 | pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { 29 | Ok(Response::new().add_attribute("action", "migrate")) 30 | } 31 | 32 | #[entry_point] 33 | pub fn execute( 34 | deps: DepsMut, 35 | _env: Env, 36 | info: MessageInfo, 37 | msg: ExecuteMsg, 38 | ) -> Result { 39 | match msg { 40 | // Admin functions 41 | ExecuteMsg::ChangeContractOwner { new_owner } => 42 | change_contract_owner(deps, info, new_owner), 43 | } 44 | } 45 | 46 | fn change_contract_owner( 47 | deps: DepsMut, 48 | info: MessageInfo, 49 | new_owner: String, 50 | ) -> Result { 51 | // Only allow current contract owner to change owner 52 | check_is_contract_owner(deps.as_ref(), info.sender)?; 53 | 54 | // validate that new owner is a valid address 55 | let new_owner_addr = deps.api.addr_validate(&new_owner)?; 56 | 57 | // update the contract owner in the contract config 58 | OWNER.save(deps.storage, &new_owner_addr)?; 59 | 60 | // return OK 61 | Ok(Response::new() 62 | .add_attribute("action", "change_contract2_owner") 63 | .add_attribute("new_owner", new_owner)) 64 | } 65 | 66 | fn check_is_contract_owner(deps: Deps, sender: Addr) -> Result<(), ContractError> { 67 | let owner = OWNER.load(deps.storage)?; 68 | if owner != sender { 69 | Err(ContractError::Unauthorized {}) 70 | } else { 71 | Ok(()) 72 | } 73 | } 74 | 75 | #[entry_point] 76 | pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { 77 | match msg { 78 | QueryMsg::Owner {} => to_binary(&query_owner(deps)?), 79 | } 80 | } 81 | 82 | fn query_owner(deps: Deps) -> StdResult { 83 | let owner = OWNER.load(deps.storage)?; 84 | Ok(OwnerResponse { 85 | address: owner.to_string(), 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /local-interchain/interchain/logs.go: -------------------------------------------------------------------------------- 1 | package interchain 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "path/filepath" 7 | "time" 8 | 9 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 10 | "github.com/strangelove-ventures/interchaintest/v8/chain/ethereum" 11 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 12 | types "github.com/strangelove-ventures/localinterchain/interchain/types" 13 | "go.uber.org/zap" 14 | "go.uber.org/zap/zapcore" 15 | ) 16 | 17 | func WriteRunningChains(configsDir string, bz []byte) { 18 | path := filepath.Join(configsDir, "configs") 19 | if _, err := os.Stat(path); os.IsNotExist(err) { 20 | if err := os.MkdirAll(path, os.ModePerm); err != nil { 21 | panic(err) 22 | } 23 | } 24 | 25 | file := filepath.Join(path, "logs.json") 26 | _ = os.WriteFile(file, bz, 0644) 27 | } 28 | 29 | func DumpChainsInfoToLogs(configDir string, config *types.Config, chains []ibc.Chain, connections []types.IBCChannel) { 30 | mainLogs := types.MainLogs{ 31 | StartTime: uint64(time.Now().Unix()), 32 | Chains: []types.LogOutput{}, 33 | Channels: connections, 34 | } 35 | 36 | // Iterate chain config & get the ibc chain's to save data to logs. 37 | for idx, chain := range config.Chains { 38 | 39 | switch chains[idx].(type) { 40 | case *cosmos.CosmosChain: 41 | chainObj := chains[idx].(*cosmos.CosmosChain) 42 | 43 | ibcPaths := chain.IBCPaths 44 | if ibcPaths == nil { 45 | ibcPaths = []string{} 46 | } 47 | 48 | log := types.LogOutput{ 49 | ChainID: chainObj.Config().ChainID, 50 | ChainName: chainObj.Config().Name, 51 | RPCAddress: chainObj.GetHostRPCAddress(), 52 | RESTAddress: chainObj.GetHostAPIAddress(), 53 | GRPCAddress: chainObj.GetHostGRPCAddress(), 54 | P2PAddress: chainObj.GetHostPeerAddress(), 55 | IBCPath: ibcPaths, 56 | } 57 | 58 | mainLogs.Chains = append(mainLogs.Chains, log) 59 | case *ethereum.EthereumChain: 60 | chainObj := chains[idx].(*ethereum.EthereumChain) 61 | 62 | log := types.LogOutput{ 63 | ChainID: chainObj.Config().ChainID, 64 | ChainName: chainObj.Config().Name, 65 | RPCAddress: chainObj.GetHostRPCAddress(), 66 | } 67 | 68 | mainLogs.Chains = append(mainLogs.Chains, log) 69 | } 70 | } 71 | 72 | bz, _ := json.MarshalIndent(mainLogs, "", " ") 73 | WriteRunningChains(configDir, []byte(bz)) 74 | } 75 | 76 | // == Zap Logger == 77 | func getLoggerConfig() zap.Config { 78 | config := zap.NewDevelopmentConfig() 79 | 80 | config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder 81 | config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 82 | 83 | return config 84 | } 85 | 86 | func InitLogger() (*zap.Logger, error) { 87 | config := getLoggerConfig() 88 | logger, err := config.Build() 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | return logger, nil 94 | } 95 | -------------------------------------------------------------------------------- /chain/penumbra/penumbra_node.go: -------------------------------------------------------------------------------- 1 | package penumbra 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "sync" 7 | 8 | dockerclient "github.com/docker/docker/client" 9 | "github.com/strangelove-ventures/interchaintest/v8/chain/internal/tendermint" 10 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 11 | "go.uber.org/zap" 12 | ) 13 | 14 | type PenumbraNode struct { 15 | TendermintNode *tendermint.TendermintNode 16 | PenumbraAppNode *PenumbraAppNode 17 | PenumbraClientNodes map[string]*PenumbraClientNode 18 | clientsMu sync.Locker 19 | address []byte 20 | addrString string 21 | } 22 | 23 | type PenumbraNodes []*PenumbraNode 24 | 25 | // NewChainNode returns a penumbra chain node with tendermint and penumbra nodes 26 | // with docker volumes created. 27 | func NewPenumbraNode( 28 | ctx context.Context, 29 | i int, 30 | c *PenumbraChain, 31 | dockerClient *dockerclient.Client, 32 | networkID string, 33 | testName string, 34 | tendermintImage ibc.DockerImage, 35 | penumbraImage ibc.DockerImage, 36 | ) (PenumbraNode, error) { 37 | tn, err := tendermint.NewTendermintNode(ctx, c.log, i, c, dockerClient, networkID, testName, tendermintImage) 38 | if err != nil { 39 | return PenumbraNode{}, err 40 | } 41 | 42 | pn, err := NewPenumbraAppNode(ctx, c.log, c, i, testName, dockerClient, networkID, penumbraImage) 43 | if err != nil { 44 | return PenumbraNode{}, err 45 | } 46 | 47 | return PenumbraNode{ 48 | TendermintNode: tn, 49 | PenumbraAppNode: pn, 50 | PenumbraClientNodes: make(map[string]*PenumbraClientNode), 51 | clientsMu: &sync.Mutex{}, 52 | }, nil 53 | } 54 | 55 | func (p *PenumbraNode) CreateClientNode( 56 | ctx context.Context, 57 | log *zap.Logger, 58 | dockerClient *dockerclient.Client, 59 | networkID string, 60 | image ibc.DockerImage, 61 | testName string, 62 | index int, 63 | keyName string, 64 | spendKey string, 65 | fullViewingKey string, 66 | ) error { 67 | p.clientsMu.Lock() 68 | clientNode, err := NewClientNode( 69 | ctx, 70 | log, 71 | p.PenumbraAppNode.Chain, 72 | keyName, 73 | index, 74 | testName, 75 | image, 76 | dockerClient, 77 | networkID, 78 | p.address, 79 | p.addrString, 80 | ) 81 | if err != nil { 82 | p.clientsMu.Unlock() 83 | return err 84 | } 85 | p.PenumbraClientNodes[keyName] = clientNode 86 | p.clientsMu.Unlock() 87 | 88 | if err := clientNode.Initialize(ctx, spendKey, fullViewingKey); err != nil { 89 | return err 90 | } 91 | 92 | if err := clientNode.CreateNodeContainer( 93 | ctx, 94 | "tcp://"+p.PenumbraAppNode.HostName()+":"+strings.Split(grpcPort, "/")[0], 95 | ); err != nil { 96 | return err 97 | } 98 | 99 | if err := clientNode.StartContainer(ctx); err != nil { 100 | return err 101 | } 102 | 103 | return nil 104 | } 105 | -------------------------------------------------------------------------------- /local-interchain/interchain/genesis.go: -------------------------------------------------------------------------------- 1 | package interchain 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "strings" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 10 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 11 | types "github.com/strangelove-ventures/localinterchain/interchain/types" 12 | ) 13 | 14 | func AddGenesisKeysToKeyring(ctx context.Context, config *types.Config, chains []ibc.Chain) { 15 | for idx, chain := range config.Chains { 16 | switch chains[idx].(type) { 17 | case *cosmos.CosmosChain: 18 | chainObj := chains[idx].(*cosmos.CosmosChain) 19 | for _, acc := range chain.Genesis.Accounts { 20 | if acc.Mnemonic != "" { 21 | if err := chainObj.RecoverKey(ctx, acc.Name, acc.Mnemonic); err != nil { 22 | panic(err) 23 | } 24 | } 25 | } 26 | default: 27 | continue 28 | } 29 | 30 | } 31 | } 32 | 33 | func PostStartupCommands(ctx context.Context, config *types.Config, chains []ibc.Chain) { 34 | for idx, chain := range config.Chains { 35 | switch chains[idx].(type) { 36 | case *cosmos.CosmosChain: 37 | chainObj := chains[idx].(*cosmos.CosmosChain) 38 | 39 | for _, cmd := range chain.Genesis.StartupCommands { 40 | log.Println("Running startup command", chainObj.Config().ChainID, cmd) 41 | 42 | cmd = strings.ReplaceAll(cmd, "%HOME%", chainObj.Validators[0].HomeDir()) 43 | cmd = strings.ReplaceAll(cmd, "%CHAIN_ID%", chainObj.Config().ChainID) 44 | 45 | stdout, stderr, err := chainObj.Exec(ctx, strings.Split(cmd, " "), []string{}) 46 | 47 | output := stdout 48 | if len(output) == 0 { 49 | output = stderr 50 | } else if err != nil { 51 | log.Println("Error running startup command", chainObj.Config().ChainID, cmd, err) 52 | } 53 | 54 | log.Println("Startup command output", chainObj.Config().ChainID, cmd, string(output)) 55 | } 56 | } 57 | 58 | } 59 | } 60 | 61 | func SetupGenesisWallets(config *types.Config, chains []ibc.Chain) map[ibc.Chain][]ibc.WalletAmount { 62 | // iterate all chains chain's configs & setup accounts 63 | additionalWallets := make(map[ibc.Chain][]ibc.WalletAmount) 64 | for idx, chain := range config.Chains { 65 | switch chains[idx].(type) { 66 | case *cosmos.CosmosChain: 67 | chainObj := chains[idx].(*cosmos.CosmosChain) 68 | for _, acc := range chain.Genesis.Accounts { 69 | amount, err := sdk.ParseCoinsNormalized(acc.Amount) 70 | if err != nil { 71 | panic(err) 72 | } 73 | 74 | for _, coin := range amount { 75 | additionalWallets[chainObj] = append(additionalWallets[chainObj], ibc.WalletAmount{ 76 | Address: acc.Address, 77 | Amount: coin.Amount, 78 | Denom: coin.Denom, 79 | }) 80 | } 81 | } 82 | // case *ethereum.EthereumChain: 83 | default: 84 | continue 85 | } 86 | 87 | } 88 | return additionalWallets 89 | } 90 | -------------------------------------------------------------------------------- /local-interchain/python/helpers/example_req.bash: -------------------------------------------------------------------------------- 1 | 2 | MAKE_REQUEST() { 3 | curl http://127.0.0.1:8080/ --include --header "Content-Type: application/json" -X $1 --data "$2" 4 | } 5 | 6 | MAKE_GET() { 7 | curl http://127.0.0.1:8080/info --include --header "Content-Type: application/json" -X GET --data "$1" 8 | } 9 | 10 | # Example with Auth 11 | # MAKE_REQUEST POST '{"chain_id":"localjuno-1","action":"q","cmd":"bank balances juno10r39fueph9fq7a6lgswu4zdsg8t3gxlq670lt0","auth_key":"mySecretKeyExample"}' 12 | 13 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","action":"q","cmd":"bank total"}' 14 | 15 | ## ---- test interacting with multiple nodes (0 and 1) -- 16 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","node_index":0, "action":"bin","cmd":"keys add testKey1 --keyring-backend=test"}' 17 | MAKE_REQUEST POST '{"chain_id":"localjuno-1", "action":"bin","cmd":"keys list --keyring-backend=test"}' # default is 0 18 | 19 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","node_index":1, "action":"bin","cmd":"keys add testKey1 --keyring-backend=test"}' 20 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","node_index":1, "action":"bin","cmd":"keys list --keyring-backend=test"}' 21 | 22 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","node_index":999, "action":"bin","cmd":"keys list --keyring-backend=test"}' # fails 23 | ## ---- 24 | 25 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","action":"get_channels"}' 26 | 27 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","action":"relayer-exec","cmd":"rly q channels localjuno-1"}' 28 | 29 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","action":"relayer-exec","cmd":"rly keys list localjuno-1"}' 30 | 31 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","action":"relayer-exec","cmd":"rly keys add localjuno-1 testkey"}' 32 | 33 | 34 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","action":"relayer-exec","cmd":"rly paths list"}' 35 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","action":"relayer-exec","cmd":"rly transact flush juno-ibc-1 channel-1"}' 36 | 37 | # wasm contract relaying. 38 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","action":"relayer-exec","cmd":"rly transact channel juno-ibc-1 --src-port transfer --dst-port transfer --order unordered --version ics20-1"}' 39 | 40 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","action":"stop"}' 41 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","action":"start","cmd":"juno-ibc-1"}' 42 | 43 | MAKE_REQUEST POST '{"action":"kill-all"}' 44 | 45 | 46 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","action":"q","cmd":"bank balances juno10r39fueph9fq7a6lgswu4zdsg8t3gxlq670lt0"}' 47 | MAKE_REQUEST POST '{"chain_id":"localjuno-1","action":"faucet","cmd":"amount=100;address=juno10r39fueph9fq7a6lgswu4zdsg8t3gxlq670lt0"}' 48 | 49 | # Get requests from info 50 | curl -G -d "chain_id=localjuno-1" -d "request=peer" http://127.0.0.1:8080/info 51 | curl -G -d "chain_id=localjuno-1" -d "request=height" http://127.0.0.1:8080/info 52 | curl -G -d "chain_id=localjuno-1" -d "request=genesis_file_content" http://127.0.0.1:8080/info -------------------------------------------------------------------------------- /chain/cosmos/module_upgrade.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | upgradetypes "cosmossdk.io/x/upgrade/types" 8 | ) 9 | 10 | // UpgradeSoftware executes the upgrade software command. 11 | func (tn *ChainNode) UpgradeSoftware(ctx context.Context, keyName, name, info string, height int, extraFlags ...string) error { 12 | cmd := []string{"upgrade", "software-upgrade", name} 13 | if height > 0 { 14 | cmd = append(cmd, "--upgrade-height", fmt.Sprintf("%d", height)) 15 | } 16 | if info != "" { 17 | cmd = append(cmd, "--upgrade-info", info) 18 | } 19 | 20 | if len(extraFlags) > 0 { 21 | cmd = append(cmd, extraFlags...) 22 | } 23 | 24 | _, err := tn.ExecTx(ctx, keyName, cmd...) 25 | return err 26 | } 27 | 28 | // UpgradeCancel executes the upgrade cancel command. 29 | func (tn *ChainNode) UpgradeCancel(ctx context.Context, keyName string, extraFlags ...string) error { 30 | cmd := []string{"upgrade", "cancel-software-upgrade"} 31 | 32 | if len(extraFlags) > 0 { 33 | cmd = append(cmd, extraFlags...) 34 | } 35 | 36 | _, err := tn.ExecTx(ctx, keyName, cmd...) 37 | return err 38 | } 39 | 40 | // UpgradeQueryPlan queries the current upgrade plan. 41 | func (c *CosmosChain) UpgradeQueryPlan(ctx context.Context) (*upgradetypes.Plan, error) { 42 | res, err := upgradetypes.NewQueryClient(c.GetNode().GrpcConn).CurrentPlan(ctx, &upgradetypes.QueryCurrentPlanRequest{}) 43 | return res.Plan, err 44 | } 45 | 46 | // UpgradeQueryAppliedPlan queries a previously applied upgrade plan by its name. 47 | func (c *CosmosChain) UpgradeQueryAppliedPlan(ctx context.Context, name string) (*upgradetypes.QueryAppliedPlanResponse, error) { 48 | res, err := upgradetypes.NewQueryClient(c.GetNode().GrpcConn).AppliedPlan(ctx, &upgradetypes.QueryAppliedPlanRequest{ 49 | Name: name, 50 | }) 51 | return res, err 52 | 53 | } 54 | 55 | // UpgradeQueryAuthority returns the account with authority to conduct upgrades 56 | func (c *CosmosChain) UpgradeQueryAuthority(ctx context.Context) (string, error) { 57 | res, err := upgradetypes.NewQueryClient(c.GetNode().GrpcConn).Authority(ctx, &upgradetypes.QueryAuthorityRequest{}) 58 | return res.Address, err 59 | } 60 | 61 | // UpgradeQueryAllModuleVersions queries the list of module versions from state. 62 | func (c *CosmosChain) UpgradeQueryAllModuleVersions(ctx context.Context) ([]*upgradetypes.ModuleVersion, error) { 63 | res, err := upgradetypes.NewQueryClient(c.GetNode().GrpcConn).ModuleVersions(ctx, &upgradetypes.QueryModuleVersionsRequest{}) 64 | return res.ModuleVersions, err 65 | } 66 | 67 | // UpgradeQueryModuleVersion queries a specific module version from state. 68 | func (c *CosmosChain) UpgradeQueryModuleVersion(ctx context.Context, module string) (*upgradetypes.ModuleVersion, error) { 69 | res, err := upgradetypes.NewQueryClient(c.GetNode().GrpcConn).ModuleVersions(ctx, &upgradetypes.QueryModuleVersionsRequest{ 70 | ModuleName: module, 71 | }) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | return res.ModuleVersions[0], err 77 | } 78 | --------------------------------------------------------------------------------